diff --git a/crypto/smartcont/tolk-stdlib/common.tolk b/crypto/smartcont/tolk-stdlib/common.tolk index 46068a206..5311ec2f7 100644 --- a/crypto/smartcont/tolk-stdlib/common.tolk +++ b/crypto/smartcont/tolk-stdlib/common.tolk @@ -1,7 +1,7 @@ // Standard library for Tolk (LGPL licence). // It contains common functions that are available out of the box, the user doesn't have to import anything. // More specific functions are required to be imported explicitly, like "@stdlib/tvm-dicts". -tolk 0.7 +tolk 0.8 /** Tuple manipulation primitives. @@ -21,23 +21,32 @@ fun tuplePush(mutate self: tuple, value: T): void asm "TPUSH"; /// Returns the first element of a non-empty tuple. +/// `t.0` is actually the same as `t.tupleFirst()` @pure -fun tupleFirst(t: tuple): T +fun tupleFirst(self: tuple): T asm "FIRST"; /// Returns the [`index`]-th element of a tuple. +/// `t.i` is actually the same as `t.tupleAt(i)` @pure -fun tupleAt(t: tuple, index: int): T +fun tupleAt(self: tuple, index: int): T + builtin; + +/// Sets the [`index`]-th element of a tuple to a specified value +/// (element with this index must already exist, a new element isn't created). +/// `t.i = value` is actually the same as `t.tupleSetAt(value, i)` +@pure +fun tupleSetAt(mutate self: tuple, value: T, index: int): void builtin; /// Returns the size of a tuple (elements count in it). @pure -fun tupleSize(t: tuple): int +fun tupleSize(self: tuple): int asm "TLEN"; /// Returns the last element of a non-empty tuple. @pure -fun tupleLast(t: tuple): T +fun tupleLast(self: tuple): T asm "LAST"; diff --git a/crypto/smartcont/tolk-stdlib/gas-payments.tolk b/crypto/smartcont/tolk-stdlib/gas-payments.tolk index 83893354d..2ac32f489 100644 --- a/crypto/smartcont/tolk-stdlib/gas-payments.tolk +++ b/crypto/smartcont/tolk-stdlib/gas-payments.tolk @@ -1,5 +1,5 @@ // A part of standard library for Tolk -tolk 0.7 +tolk 0.8 /** Gas and payment related primitives. diff --git a/crypto/smartcont/tolk-stdlib/lisp-lists.tolk b/crypto/smartcont/tolk-stdlib/lisp-lists.tolk index 429f0cbfd..0cb178417 100644 --- a/crypto/smartcont/tolk-stdlib/lisp-lists.tolk +++ b/crypto/smartcont/tolk-stdlib/lisp-lists.tolk @@ -1,5 +1,5 @@ // A part of standard library for Tolk -tolk 0.7 +tolk 0.8 /** Lisp-style lists are nested 2-elements tuples: `(1, (2, (3, null)))` represents list `[1, 2, 3]`. diff --git a/crypto/smartcont/tolk-stdlib/tvm-dicts.tolk b/crypto/smartcont/tolk-stdlib/tvm-dicts.tolk index a47fe5426..5c4362392 100644 --- a/crypto/smartcont/tolk-stdlib/tvm-dicts.tolk +++ b/crypto/smartcont/tolk-stdlib/tvm-dicts.tolk @@ -1,5 +1,5 @@ // A part of standard library for Tolk -tolk 0.7 +tolk 0.8 /** Dictionaries are represented as `cell` data type (cells can store anything, dicts in particular). diff --git a/crypto/smartcont/tolk-stdlib/tvm-lowlevel.tolk b/crypto/smartcont/tolk-stdlib/tvm-lowlevel.tolk index ef7c2afe9..72a54aac2 100644 --- a/crypto/smartcont/tolk-stdlib/tvm-lowlevel.tolk +++ b/crypto/smartcont/tolk-stdlib/tvm-lowlevel.tolk @@ -1,5 +1,5 @@ // A part of standard library for Tolk -tolk 0.7 +tolk 0.8 /// Usually `c3` has a continuation initialized by the whole code of the contract. It is used for function calls. /// The primitive returns the current value of `c3`. diff --git a/tolk-tester/tests/a10.tolk b/tolk-tester/tests/a10.tolk index 7301f1d50..755a3bfbc 100644 --- a/tolk-tester/tests/a10.tolk +++ b/tolk-tester/tests/a10.tolk @@ -78,6 +78,17 @@ fun testStartBalanceCodegen2() { return first; } +global cur: [int, int, int]; +global next: [int, int, int]; + +@method_id(95) +fun test95() { + cur = [1, 2, 3]; + next = [2, 3, 4]; + (cur, next) = (next, [3, 4, 5]); + return (cur, next); +} + /** method_id | in | out @testcase | 0 | 101 15 | 100 1 @@ -90,6 +101,7 @@ fun testStartBalanceCodegen2() { @testcase | 89 | 4 | 1 4 1 4 @testcase | 91 | | 10 @testcase | 92 | | 10 32 +@testcase | 95 | | [ 2 3 4 ] [ 3 4 5 ] @fif_codegen """ @@ -104,9 +116,9 @@ fun testStartBalanceCodegen2() { testDumpDontPolluteStack PROC:<{ ... DUMPSTK - x{6d79} PUSHSLICE // f s _9 + x{6d79} PUSHSLICE // f s '5 STRDUMP DROP - SBITS // f _11 + SBITS // f '6 }> """ @@ -127,4 +139,20 @@ fun testStartBalanceCodegen2() { FIRST // first }> """ + +@fif_codegen +""" + test95 PROC:<{ + ... + next GETGLOB // '10 + 3 PUSHINT // '10 '12=3 + 4 PUSHINT // '10 '12=3 '13=4 + 5 PUSHINT // '10 '12=3 '13=4 '14=5 + TRIPLE // '15 '16 + next SETGLOB + cur SETGLOB + cur GETGLOB // '17 + next GETGLOB // '17 '18 + }> +""" */ diff --git a/tolk-tester/tests/a6.tolk b/tolk-tester/tests/a6.tolk index 32fd3364c..944945464 100644 --- a/tolk-tester/tests/a6.tolk +++ b/tolk-tester/tests/a6.tolk @@ -1,6 +1,9 @@ fun f(a: int, b: int, c: int, d: int, e: int, f: int): (int, int) { // solve a 2x2 linear equation var D: int = a*d - b*c;;;; var Dx: int = e*d-b*f ;;;; var Dy: int = a * f - e * c; + __expect_type(D, "int"); + __expect_type(D*D, "int"); + __expect_type(calc_phi, "() -> int"); return (Dx/D,Dy/D); };;;; diff --git a/tolk-tester/tests/a6_1.tolk b/tolk-tester/tests/a6_1.tolk index 4995c42d3..8079972b1 100644 --- a/tolk-tester/tests/a6_1.tolk +++ b/tolk-tester/tests/a6_1.tolk @@ -7,7 +7,7 @@ fun main(a: int, b: int, c: int, d: int, e: int, f: int): (int, int) { @method_id(101) fun testDivMod(x: int, y: int) { - return [divMod(x, y), modDiv(x, y), mulDivMod(x, y, 10)]; + return (divMod(x, y), modDiv(x, y), mulDivMod(x, y, 10)); } /** @@ -18,5 +18,5 @@ fun testDivMod(x: int, y: int) { @testcase | 0 | 448 -433 -444 792 150012 -356232 | -218 -572 @testcase | 0 | -40 -821 433 -734 -721629 -741724 | -206 889 @testcase | 0 | -261 -98 -494 868 -166153 733738 | 263 995 -@testcase | 101 | 112 3 | [ 37 1 1 37 33 6 ] +@testcase | 101 | 112 3 | 37 1 1 37 33 6 */ diff --git a/tolk-tester/tests/allow_post_modification.tolk b/tolk-tester/tests/allow_post_modification.tolk index e374f62b3..df758a1ec 100644 --- a/tolk-tester/tests/allow_post_modification.tolk +++ b/tolk-tester/tests/allow_post_modification.tolk @@ -89,12 +89,14 @@ fun test_if_else(x: int): (int, int, int, int, int) { @method_id(21) fun test_assign_with_inner(x: int) { - return (x, x += 10, [(x, x += 20, eq(x -= 50), x)], eq2((x, x *= eq(x /= 2)))); + var result = (x, x += 10, [x, x += 20, eq(x -= 50), x], eq2((x, x *= eq(x /= 2)))); + return result; } @method_id(22) fun test_assign_with_mutate(x: int) { - return (x, mul2(mutate x, x += 5), x.`~inc`(mul2(mutate x, x)), x); + var (result, _) = ((x, mul2(mutate x, x += 5), x.`~inc`(mul2(mutate x, x)), x), 0); + return result; } @method_id(23) @@ -138,5 +140,12 @@ fun main() { inc CALLDICT // self newY }> """ + +@fif_codegen +""" + test_assign_tensor_global PROC:<{ + // x.0 x.1 +""" + @code_hash 7627024945492125068389905298530400936797031708759561372406088054030801992712 */ diff --git a/tolk-tester/tests/assignment-tests.tolk b/tolk-tester/tests/assignment-tests.tolk index 89de8cf44..40761939c 100644 --- a/tolk-tester/tests/assignment-tests.tolk +++ b/tolk-tester/tests/assignment-tests.tolk @@ -14,6 +14,18 @@ fun autoInferIntNull(x: int) { return x; } +fun typesAsIdentifiers(builder: builder) { + var int = 1; + var cell = builder.endCell(); + var slice = cell.beginParse(); + { + var cell: cell = cell; + var tuple: tuple = createEmptyTuple(); + var bool: bool = tuple.tupleAt(0) > 0; + } + return int; +} + fun main(value: int) { var (x: int, y) = (autoInferIntNull(value), autoInferIntNull(value * 2)); if (x == null && y == null) { return null; } diff --git a/tolk-tester/tests/bit-operators.tolk b/tolk-tester/tests/bit-operators.tolk index 4cb8e1ba1..b01068839 100644 --- a/tolk-tester/tests/bit-operators.tolk +++ b/tolk-tester/tests/bit-operators.tolk @@ -127,10 +127,10 @@ fun testBoolCompareOptimized(x: bool) { """ boolWithBitwiseConst PROC:<{ // - 0 PUSHINT // _3 - -1 PUSHINT // _3 _5 - 0 PUSHINT // _3 _5 _7 - -1 PUSHINT // _3 _5 _7 _8 + 0 PUSHINT // '3 + -1 PUSHINT // '3 '5 + 0 PUSHINT // '3 '5 '7 + -1 PUSHINT // '3 '5 '7 '8 }> """ @@ -142,22 +142,22 @@ fun testBoolCompareOptimized(x: bool) { UNTIL:<{ INC // i n cnt s2 PUSH // i n cnt i - NOT // i n cnt _6 + NOT // i n cnt '6 }> // i n cnt UNTIL:<{ INC // i n cnt s2 PUSH // i n cnt i - NOT // i n cnt _9 + NOT // i n cnt '9 }> // i n cnt UNTIL:<{ INC // i n cnt OVER // i n cnt n - 0 EQINT // i n cnt _12 + 0 EQINT // i n cnt '12 }> // i n cnt s0 s2 XCHG // cnt n i - NOT // cnt n _13 - SWAP // cnt _13 n - 0 EQINT // cnt _13 _14 + NOT // cnt n '13 + SWAP // cnt '13 n + 0 EQINT // cnt '13 '14 }> """ @@ -165,12 +165,12 @@ fun testBoolCompareOptimized(x: bool) { """ testConstNegateCodegen PROC:<{ // - TRUE // _0 - FALSE // _0 _1 - FALSE // _0 _1 _2 - TRUE // _0 _1 _2 _3 - TRUE // _0 _1 _2 _3 _4 - FALSE // _0 _1 _2 _3 _4 _5 + TRUE // '0 + FALSE // '0 '1 + FALSE // '0 '1 '2 + TRUE // '0 '1 '2 '3 + TRUE // '0 '1 '2 '3 '4 + FALSE // '0 '1 '2 '3 '4 '5 }> """ @@ -179,11 +179,11 @@ fun testBoolCompareOptimized(x: bool) { testBoolNegateOptimized PROC:<{ // x DUP // x x - NOT // x _1 - OVER // x _1 x - NOT // x _1 _2 + NOT // x '1 + OVER // x '1 x + NOT // x '1 '2 s2 s(-1) PUXC - TRUE // x _1 x _2 _3 + TRUE // x '1 x '2 '3 }> """ @@ -192,13 +192,13 @@ fun testBoolCompareOptimized(x: bool) { testBoolCompareOptimized PROC:<{ // x DUP // x x - NOT // x _1 - OVER // x _1 x - eqX CALLDICT // x _1 _2 - NOT // x _1 _3 - s2 PUSH // x _1 _3 x - eqX CALLDICT // x _1 _3 _4 - s3 PUSH // x _1 _3 _4 x + NOT // x '1 + OVER // x '1 x + eqX CALLDICT // x '1 '2 + NOT // x '1 '3 + s2 PUSH // x '1 '3 x + eqX CALLDICT // x '1 '3 '4 + s3 PUSH // x '1 '3 '4 x }> """ */ diff --git a/tolk-tester/tests/cells-slices.tolk b/tolk-tester/tests/cells-slices.tolk index 6f316f2e6..19e2e2153 100644 --- a/tolk-tester/tests/cells-slices.tolk +++ b/tolk-tester/tests/cells-slices.tolk @@ -216,16 +216,16 @@ Note, that since 'compute-asm-ltr' became on be default, chaining methods codege """ test6 PROC:<{ // - NEWC // _0 - 1 PUSHINT // _0 _1=1 - SWAP // _1=1 _0 - 32 STU // _0 - 2 PUSHINT // _0 _5=2 - SWAP // _5=2 _0 - 32 STU // _0 - 3 PUSHINT // _0 _9=3 - SWAP // _9=3 _0 - 32 STU // _0 + NEWC // '0 + 1 PUSHINT // '0 '1=1 + SWAP // '1=1 '0 + 32 STU // '0 + 2 PUSHINT // '0 '4=2 + SWAP // '4=2 '0 + 32 STU // '0 + 3 PUSHINT // '0 '7=3 + SWAP // '7=3 '0 + 32 STU // '0 }> """ */ diff --git a/tolk-tester/tests/codegen_check_demo.tolk b/tolk-tester/tests/codegen_check_demo.tolk index e40f03779..b355a9b77 100644 --- a/tolk-tester/tests/codegen_check_demo.tolk +++ b/tolk-tester/tests/codegen_check_demo.tolk @@ -35,7 +35,7 @@ Below, I just give examples of @fif_codegen tag: """ main PROC:<{ // s - 17 PUSHINT // s _1=17 + 17 PUSHINT // s '1=17 OVER // s z=17 t WHILE:<{ ... @@ -63,7 +63,7 @@ main PROC:<{ @fif_codegen """ OVER - 0 GTINT // s z t _5 + 0 GTINT // s z t '5 """ @fif_codegen @@ -83,7 +83,7 @@ FALSE }> """ -@fif_codegen NOT // _8 +@fif_codegen NOT // '8 @fif_codegen main PROC:<{ @fif_codegen_avoid PROCINLINE diff --git a/tolk-tester/tests/generics-1.tolk b/tolk-tester/tests/generics-1.tolk index 0d872cc19..453ec2823 100644 --- a/tolk-tester/tests/generics-1.tolk +++ b/tolk-tester/tests/generics-1.tolk @@ -14,13 +14,19 @@ fun getTwo(): X { return 2 as X; } fun takeInt(a: int) { return a; } @method_id(102) -fun test102(): (int, int, int, [(int, int)]) { +fun test102(): (int, int, int, [int, int]) { var a: int = getTwo(); var _: int = getTwo(); var b = getTwo() as int; var c: int = 1 ? getTwo() : getTwo(); var c redef = getTwo(); - return (eq1(a), eq2(b), takeInt(getTwo()), [(getTwo(), getTwo())]); + var ab_tens = (0, (1, 2)); + ab_tens.0 = getTwo(); + ab_tens.1.1 = getTwo(); + var ab_tup = [0, [1, 2]]; + ab_tup.0 = getTwo(); + ab_tup.1.1 = getTwo(); + return (eq1(a), eq2(b), takeInt(getTwo()), [getTwo(), ab_tens.1.1]); } @method_id(103) @@ -41,10 +47,12 @@ fun manyEq(a: T1, b: T2, c: T3): [T1, T2, T3] { @method_id(104) fun test104(f: int) { - return ( + var result = ( manyEq(1 ? 1 : 1, f ? 0 : null, !f ? getTwo() as int : null), - manyEq((f ? null as int : eq2(2), beginCell().storeBool(true).endCell().beginParse().loadBool()), 0, eq4(f)) + manyEq(f ? null as int : eq2(2), beginCell().storeBool(true).endCell().beginParse().loadBool(), eq4(f)) ); + __expect_type(result, "([int, int, int], [int, bool, int])"); + return result; } fun calcSum(x: X, y: X) { return x + y; } @@ -68,10 +76,12 @@ fun abstractTransform(xToY: (X) -> Y, yToR: (((Y))) -> R, initialX: X): @method_id(106) fun test106() { var c = beginCell().storeInt(106, 32).endCell(); + __expect_type(calcYPlus1, "(int) -> int"); return [ abstractTransform(cellToSlice, calcLoad32, c), abstractTransform(calcYPlus1, calcYPlus1, 0), - abstractTransform(calcTensorPlus1, calcTensorMul2, (2, 2)) + abstractTransform(calcTensorPlus1, calcTensorMul2, (2, 2)).0, + abstractTransform(calcTensorPlus1, calcTensorMul2, (2, 2)).1 ]; } @@ -132,7 +142,7 @@ fun main(x: int): (int, [[int, int]]) { @testcase | 101 | 0 | 0 0 0 [ 0 0 ] 0 0 0 [ 0 0 ] 0 0 0 [] @testcase | 102 | | 2 2 2 [ 2 2 ] @testcase | 103 | 0 | 0 100 100 -@testcase | 104 | 0 | [ 1 (null) 2 ] [ 2 -1 0 0 ] +@testcase | 104 | 0 | [ 1 (null) 2 ] [ 2 -1 0 ] @testcase | 105 | | 3 @testcase | 106 | | [ 106 2 6 6 ] @testcase | 107 | | 6 6 1 1 6 6 diff --git a/tolk-tester/tests/if_stmt.tolk b/tolk-tester/tests/if_stmt.tolk index 2c51ac515..0f78a5162 100644 --- a/tolk-tester/tests/if_stmt.tolk +++ b/tolk-tester/tests/if_stmt.tolk @@ -54,13 +54,13 @@ fun main() { test3 PROC:<{ // x DUP // x x - 20 NEQINT // x _2 + 20 NEQINT // x '2 IFNOTJMP:<{ // x DROP // - 20 PUSHINT // _3=20 + 20 PUSHINT // '3=20 }> // x DUP // x x - 50 EQINT // x _5 + 50 EQINT // x '5 IFNOTJMP:<{ // x """ */ diff --git a/tolk-tester/tests/indexed-access.tolk b/tolk-tester/tests/indexed-access.tolk new file mode 100644 index 000000000..38094fa5f --- /dev/null +++ b/tolk-tester/tests/indexed-access.tolk @@ -0,0 +1,287 @@ + +fun increment(mutate self: int) { + self += 1; +} + +fun increment2(mutate a: int, mutate b: int) { + a += 1; + b += 1; +} + +fun assign1020(mutate a: int, mutate b: int) { + a = 10; + b = 20; +} + +fun plus(mutate self: int, y: int): int { + val newVals = (self + y, y * 10); + self = newVals.0; + return newVals.1; +} + +fun eq(v: X): X { return v; } + +@method_id(101) +fun test101() { + var t = (1, (2, 3), [4, 5, [6, 7]], 8); + (t.0, t.1.0, t.2.0) = (2, 3, 5); + t.3.increment(); + t.2.1 += (t.1.1 += 1) - t.1.1 + 1; + increment2(mutate t.2.2.0, mutate t.2.2.1); + return t; +} + +global t102: (int, (int, int), [int, int, [int, int]], int); + +@method_id(102) +fun test102() { + t102 = (1, (2, 3), [4, 5, [6, 7]], 8); + (t102.0, t102.1.0, t102.2.0) = (2, 3, 5); + t102.3.increment(); + t102.2.1 += (t102.1.1 += 1) - t102.1.1 + 1; + increment2(mutate t102.2.2.0, mutate t102.2.2.1); + return t102; +} + +global t103: (int, int); + +@method_id(103) +fun test103() { + t103 = (5, 5); + assign1020(mutate t103.0, mutate t103.1); + var t = (5, 5); + assign1020(mutate t.0, mutate t.1); + return (t103, t); +} + +global t104: [[int, int]]; + +@method_id(104) +fun test104() { + var m = [[5, 5]]; + (m.0.0, m.0.1) = (10, 20); + t104 = [[5, 5]]; + (t104.0.0, t104.0.1) = (10, 20); + return (t104, m); +} + +@method_id(105) +fun test105(x: int, y: int): (tuple, int, (int, int), int, int) { + var ab = (createEmptyTuple(), (x, y), tupleSize); + ab.0.tuplePush(1); + tuplePush(mutate ab.0, 2); + ab.1.0 = null; + ab.1.1 += 10; + var cb = ab.2; + return (ab.0, ab.0.1, ab.1, cb(ab.0), ab.2(ab.0)); +} + +@method_id(106) +fun test106(x: int, y: int) { + var ab = [createEmptyTuple(), [x, y], tupleSize]; + ab.0.tuplePush(1); + tuplePush(mutate ab.0, 2); + ab.1.0 = null; + ab.1.1 += 10; + var cb = ab.2; + return (ab.0, ab.1, cb(ab.0), ab.2(ab.0)); +} + +@method_id(107) +fun test107() { + var ab = createEmptyTuple(); + ab.tuplePush(1); + ab.tuplePush(beginCell().storeInt(1, 32)); + return (ab.0 as int, getBuilderBitsCount(ab.1)); +} + +global t108: [int, [int, [int]]]; + +@method_id(108) +fun test108(last: int) { + t108 = [1, [2, [last]]]; + t108.1.1.0.increment(); + var t = [1, [2, [last]]]; + t.1.1.0.increment(); + return (t108, t); +} + +@method_id(109) +fun test109(x: (int, int)): (int, int, int, int, int, int, int) { + return (x.1, x.1.plus(x.1 / 20), x.1, x.1 = x.1 * 2, x.1, x.1 += 1, x.1); +} + +@method_id(110) +fun test110(f: int, s: int) { + var x = [f, s]; + return (x, x.1, x.1.plus(x.1 / 20), x.1, x.1 = x.1 * 2, x.1, x.1 += 1, x.1, x); +} + +global xx: (int, int); + +@method_id(111) +fun test111(x: (int, int)) { + xx = x; + return (x, xx.1, xx.1.plus(xx.1 / 20), eq(xx.1 += (x.1 *= 0)), xx.1 = xx.1 * 2, xx.1, xx.1 += 1, xx.1, x); +} + +global yy: [int, int]; + +@method_id(112) +fun test112(f: int, s: int) { + yy = [f, s]; + return (yy, yy.1, yy.1.plus(yy.1 / 20), eq(yy.1 += (yy.1 *= 0)), yy.1 = yy.1 * 2, yy.1, yy.1 += 1, yy.1, yy); +} + +@pure +fun getConstTuple() { + return [1,2]; +} + +fun testCodegenNoPureIndexedAccess() { + (getConstTuple().1, getConstTuple().0) = (3, 4); + return 0; +} + +@method_id(113) +fun test113() { + var x = [[1, 2]]; + return (x, x.0, plus(mutate x.0.0, 10), x.0, x, x.0 = [10, 20], x); +} + +@method_id(114) +fun test114(f: int, s: int) { + var x = ((), (f, s), ()); + return (x, x.1, plus(mutate x.1.0, 10), x.1, x, x.1 = (10, 20), x); +} + +@method_id(115) +fun test115() { + var y = [[[[true]]]]; + return (y, y.0.0.0.0 = !y.0.0.0.0, y.0); +} + +@method_id(116) +fun test116() { + var t = createEmptyTuple(); + t.tuplePush(1); + try { + return t.100500 as int; + } catch(excNo) { + return excNo; + } +} + +@method_id(117) +fun test117() { + var t = createEmptyTuple(); + t.tuplePush(1); + try { + return (t.0 as tuple).0 as int; + } catch(excNo) { + return excNo; + } +} + +@method_id(118) +fun testCodegenIndexPostfix1(x: (int, int)) { + var ab = (x.1, x.0); + return ab; +} + +@method_id(119) +fun testCodegenIndexPostfix2(x: (int, (int, int), int)) { + var y = x; + return (y.2, y.0, y.1.1); +} + +fun getT() { return (1, 2); } + +@method_id(120) +fun test120() { + return (getT().0 = 3, getT().0 = 4, [getT().0 = 5, getT().0 = 6]); +} + +@method_id(121) +fun test121(zero: int) { + var t = createEmptyTuple(); + t.tuplePush(-100); + t.tupleSetAt(0, zero); + (t.0 as int).increment(); + (((t.0) as int) as int).increment(); + increment(mutate t.0 as int); + return t; +} + +fun main(){} + + +/** +@testcase | 101 | | 2 3 4 [ 5 6 [ 7 8 ] ] 9 +@testcase | 102 | | 2 3 4 [ 5 6 [ 7 8 ] ] 9 +@testcase | 103 | | 10 20 10 20 +@testcase | 104 | | [ [ 10 20 ] ] [ [ 10 20 ] ] +@testcase | 105 | 5 6 | [ 1 2 ] 2 (null) 16 2 2 +@testcase | 106 | 5 6 | [ 1 2 ] [ (null) 16 ] 2 2 +@testcase | 107 | | 1 32 +@testcase | 108 | 3 | [ 1 [ 2 [ 4 ] ] ] [ 1 [ 2 [ 4 ] ] ] +@testcase | 109 | 0 100 | 100 50 105 210 210 211 211 +@testcase | 110 | 0 100 | [ 0 100 ] 100 50 105 210 210 211 211 [ 0 211 ] +@testcase | 111 | 0 100 | 0 100 100 50 105 210 210 211 211 0 0 +@testcase | 112 | 0 100 | [ 0 100 ] 100 50 105 210 210 211 211 [ 0 211 ] +@testcase | 113 | | [ [ 1 2 ] ] [ 1 2 ] 100 [ 11 2 ] [ [ 11 2 ] ] [ 10 20 ] [ [ 10 20 ] ] +@testcase | 114 | 1 2 | 1 2 1 2 100 11 2 11 2 10 20 10 20 +@testcase | 115 | | [ [ [ [ -1 ] ] ] ] 0 [ [ [ 0 ] ] ] +@testcase | 116 | | 5 +@testcase | 117 | | 7 +@testcase | 118 | 1 2 | 2 1 +@testcase | 119 | 1 2 3 4 | 4 1 3 +@testcase | 120 | | 3 4 [ 5 6 ] +@testcase | 121 | 0 | [ 3 ] + +@fif_codegen +""" + testCodegenNoPureIndexedAccess PROC:<{ + // + 0 PUSHINT // '8=0 + }> +""" + +@fif_codegen +""" + test104 PROC:<{ + // + 5 PUSHINT // '2=5 + DUP // '2=5 '3=5 + PAIR // '1 + SINGLE // m + 10 PUSHINT // m '5=10 + 20 PUSHINT // m '5=10 '6=20 + s2 PUSH // m '5=10 '6=20 m + 0 INDEX // m '10=10 '12=20 '8 + SWAP // m '10=10 '8 '12=20 + 1 SETINDEX // m '10=10 '8 + SWAP // m '8 '10=10 + 0 SETINDEX // m '8 + 0 SETINDEX // m + ... +""" + +@fif_codegen +""" + testCodegenIndexPostfix1 PROC:<{ + // x.0 x.1 + // ab.1 ab.0 + SWAP // ab.0 ab.1 + }> +""" + +@fif_codegen +""" + testCodegenIndexPostfix2 PROC:<{ + // x.0 x.1.0 x.1.1 x.2 + s2 POP // y.0 y.2 y.1.1 + s1 s2 XCHG // y.2 y.0 y.1.1 + }> +""" + */ diff --git a/tolk-tester/tests/inference-tests.tolk b/tolk-tester/tests/inference-tests.tolk new file mode 100644 index 000000000..3d4515816 --- /dev/null +++ b/tolk-tester/tests/inference-tests.tolk @@ -0,0 +1,94 @@ +// the goal of this file is not only to @testcase results — +// but to check that this file compiles + +fun eq(value: X): X { return value; } + +fun test1(x: int, y: int) { + __expect_type(0, "int"); + __expect_type("0"c, "int"); + __expect_type(x, "int"); + __expect_type(x + y, "int"); + __expect_type(x * y, "int"); + __expect_type(x & y, "int"); + __expect_type(x << y, "int"); + __expect_type((((x))), "int"); + __expect_type(x = x, "int"); + __expect_type(x += x, "int"); + __expect_type(x &= x, "int"); + __expect_type(random() ? x : y, "int"); + __expect_type(eq(x), "int"); + __expect_type(eq(x), "int"); + __expect_type(eq(null), "int"); + __expect_type(x as int, "int"); + __expect_type(+x, "int"); + __expect_type(~x, "int"); + { + var x: slice = beginCell().endCell().beginParse(); + __expect_type(x, "slice"); + __expect_type(beginCell(), "builder"); + __expect_type(beginCell().endCell(), "cell"); + } +} + +fun test2(x: int, y: bool) { + __expect_type(!x, "bool"); + __expect_type(x != x, "bool"); + __expect_type(x <= x, "bool"); + __expect_type(x <=> x, "bool"); + __expect_type(x <=> x, "bool"); + __expect_type(!random(), "bool"); + __expect_type(!!(x != null), "bool"); + __expect_type(x ? x != null : null == x, "bool"); + __expect_type(y & true, "bool"); + __expect_type(y ^= false, "bool"); + __expect_type(x && y, "bool"); + __expect_type(true && false && true, "bool"); + __expect_type(x || x, "bool"); + __expect_type(x || !x || (true & false), "bool"); +} + +fun test3() { + __expect_type(true as int, "int"); + __expect_type(!random() as int, "int"); +} + +fun test4(x: int) { + __expect_type((), "()"); + __expect_type((x, x), "(int, int)"); + __expect_type((x, (x, x), x), "(int, (int, int), int)"); +} + +fun test5(x: int) { + __expect_type([], "[]"); + __expect_type([x], "[int]"); + __expect_type([x, x >= 1], "[int, bool]"); + __expect_type([x, x >= 1, null as slice], "[int, bool, slice]"); + __expect_type((x, [x], [[x], x]), "(int, [int], [[int], int])"); + __expect_type(getMyOriginalBalanceWithExtraCurrencies(), "[int, cell]"); +} + +fun test6() { + var t = createEmptyTuple(); + __expect_type(t, "tuple"); + t.tuplePush(1); + __expect_type(t, "tuple"); +} + +fun test7() { + __expect_type(test3(), "void"); + __expect_type(test3, "() -> void"); + var cb = test1; + __expect_type(cb, "(int, int) -> void"); + var t = createEmptyTuple(); + __expect_type(beginCell().endCell, "(builder) -> cell"); + // __expect_type(eq<(int, slice)>, "(int, slice) -> (int, slice)"); +} + + +fun main() { + return 0; +} + +/** +@testcase | 0 | | 0 +*/ diff --git a/tolk-tester/tests/invalid-assign-1.tolk b/tolk-tester/tests/invalid-assign-1.tolk new file mode 100644 index 000000000..f605056ee --- /dev/null +++ b/tolk-tester/tests/invalid-assign-1.tolk @@ -0,0 +1,9 @@ +fun main() { + var c = 1; + (c, c) = (2, 3); +} + +/** +@compilation_should_fail +@stderr one variable modified twice inside the same expression +*/ diff --git a/tolk-tester/tests/invalid-assign-2.tolk b/tolk-tester/tests/invalid-assign-2.tolk new file mode 100644 index 000000000..2838ed9ac --- /dev/null +++ b/tolk-tester/tests/invalid-assign-2.tolk @@ -0,0 +1,11 @@ +fun incThree(mutate a: int, mutate b: int, mutate c: int) {} + +fun main() { + var c = [[[1, 2]]]; + incThree(mutate c.0.0.0, mutate c.0.0.1, mutate c.0.0.0); +} + +/** +@compilation_should_fail +@stderr one variable modified twice inside the same expression +*/ diff --git a/tolk-tester/tests/invalid-assign-3.tolk b/tolk-tester/tests/invalid-assign-3.tolk new file mode 100644 index 000000000..d3f5d1f1a --- /dev/null +++ b/tolk-tester/tests/invalid-assign-3.tolk @@ -0,0 +1,10 @@ +global gg: (int, int); + +fun main() { + [gg.0, gg.1, gg.0] = [0, 1, 0]; +} + +/** +@compilation_should_fail +@stderr one variable modified twice inside the same expression +*/ diff --git a/tolk-tester/tests/invalid-assign-4.tolk b/tolk-tester/tests/invalid-assign-4.tolk new file mode 100644 index 000000000..67340b20b --- /dev/null +++ b/tolk-tester/tests/invalid-assign-4.tolk @@ -0,0 +1,10 @@ +global gg: (int, [int, int]); + +fun main() { + (gg.1.0, gg.1, gg.1.1) = (0, [1, 2], 3); +} + +/** +@compilation_should_fail +@stderr one variable both modified and read inside the same expression +*/ diff --git a/tolk-tester/tests/invalid-assign-5.tolk b/tolk-tester/tests/invalid-assign-5.tolk new file mode 100644 index 000000000..f3fe59f73 --- /dev/null +++ b/tolk-tester/tests/invalid-assign-5.tolk @@ -0,0 +1,9 @@ +fun main() { + var ab = (1, 2); + (ab, ab.1) = ((2, 3), 4); +} + +/** +@compilation_should_fail +@stderr one variable both modified and read inside the same expression +*/ diff --git a/tolk-tester/tests/invalid-assign-6.tolk b/tolk-tester/tests/invalid-assign-6.tolk new file mode 100644 index 000000000..59d769e96 --- /dev/null +++ b/tolk-tester/tests/invalid-assign-6.tolk @@ -0,0 +1,9 @@ +fun main() { + var t = createEmptyTuple(); + t.0 = (1, 2); +} + +/** +@compilation_should_fail +@stderr can not put `(int, int)` into a tuple, because it occupies 2 stack slots in TVM, not 1 +*/ diff --git a/tolk-tester/tests/invalid-assign-7.tolk b/tolk-tester/tests/invalid-assign-7.tolk new file mode 100644 index 000000000..6a33e6968 --- /dev/null +++ b/tolk-tester/tests/invalid-assign-7.tolk @@ -0,0 +1,8 @@ +fun main(cs: slice) { + var cb = cs.tupleSize; +} + +/** +@compilation_should_fail +@stderr referencing a method for `tuple` with object of type `slice` +*/ diff --git a/tolk-tester/tests/invalid-call-1.tolk b/tolk-tester/tests/invalid-call-1.tolk index 3542f5809..7435bb3cf 100644 --- a/tolk-tester/tests/invalid-call-1.tolk +++ b/tolk-tester/tests/invalid-call-1.tolk @@ -6,5 +6,5 @@ fun main(x: int) { /** @compilation_should_fail -@stderr calling a non-function +@stderr non-existing method `asdf` of type `int` */ diff --git a/tolk-tester/tests/invalid-call-10.tolk b/tolk-tester/tests/invalid-call-10.tolk new file mode 100644 index 000000000..9a28c0043 --- /dev/null +++ b/tolk-tester/tests/invalid-call-10.tolk @@ -0,0 +1,11 @@ +fun takeInvalidTuple(t: [int, (int, builder), int]) { +} + +fun main() { + takeInvalidTuple([1, (2, beginCell()), 0]); +} + +/** +@compilation_should_fail +@stderr can not put `(int, builder)` into a tuple, because it occupies 2 stack slots in TVM, not 1 + */ diff --git a/tolk-tester/tests/invalid-call-11.tolk b/tolk-tester/tests/invalid-call-11.tolk new file mode 100644 index 000000000..f631c546c --- /dev/null +++ b/tolk-tester/tests/invalid-call-11.tolk @@ -0,0 +1,11 @@ +fun main() { + var functions = (beginCell, beginCell); + var b = functions.1(); // ok + var c = functions.2(); // error +} + +/** +@compilation_should_fail +@stderr invalid tensor index, expected 0..1 +@stderr functions.2() + */ diff --git a/tolk-tester/tests/invalid-call-7.tolk b/tolk-tester/tests/invalid-call-7.tolk index 4ad038c9e..cf8c788c1 100644 --- a/tolk-tester/tests/invalid-call-7.tolk +++ b/tolk-tester/tests/invalid-call-7.tolk @@ -9,6 +9,6 @@ fun main() { /** @compilation_should_fail -@stderr undefined symbol `storeUnexisting` +@stderr non-existing method `storeUnexisting` of type `builder` @stderr .storeUnexisting() */ diff --git a/tolk-tester/tests/invalid-call-8.tolk b/tolk-tester/tests/invalid-call-8.tolk index c613d7d9c..199aa681a 100644 --- a/tolk-tester/tests/invalid-call-8.tolk +++ b/tolk-tester/tests/invalid-call-8.tolk @@ -1,8 +1,10 @@ +fun get_incoming_value() { return 3; } + fun main() { var incoming_ton: int = get_incoming_value().3(); } /** @compilation_should_fail -@stderr expected method name, got `3` +@stderr type `int` is not indexable */ diff --git a/tolk-tester/tests/invalid-catch-1.tolk b/tolk-tester/tests/invalid-catch-1.tolk index 756722bb8..54f6e182b 100644 --- a/tolk-tester/tests/invalid-catch-1.tolk +++ b/tolk-tester/tests/invalid-catch-1.tolk @@ -1,12 +1,12 @@ fun main() { try { - } catch(int, arg) {} + } catch(if, arg) {} return 0; } /** @compilation_should_fail -@stderr expected identifier, got `int` -@stderr catch(int +@stderr expected identifier, got `if` +@stderr catch(if */ diff --git a/tolk-tester/tests/invalid-declaration-2.tolk b/tolk-tester/tests/invalid-declaration-2.tolk index 700632517..07ffb6838 100644 --- a/tolk-tester/tests/invalid-declaration-2.tolk +++ b/tolk-tester/tests/invalid-declaration-2.tolk @@ -4,5 +4,5 @@ fun main(int): int { /** @compilation_should_fail -@stderr expected parameter name, got `int` +@stderr expected `: `, got `)` */ diff --git a/tolk-tester/tests/invalid-declaration-4.tolk b/tolk-tester/tests/invalid-declaration-4.tolk index 183dda96f..62fd6c568 100644 --- a/tolk-tester/tests/invalid-declaration-4.tolk +++ b/tolk-tester/tests/invalid-declaration-4.tolk @@ -4,5 +4,5 @@ fun main() { /** @compilation_should_fail -@stderr probably, you use FunC-like declarations; valid syntax is `var x: int = ...` +@stderr expected `;`, got `x` */ diff --git a/tolk-tester/tests/invalid-generics-12.tolk b/tolk-tester/tests/invalid-generics-12.tolk new file mode 100644 index 000000000..62a6f5da4 --- /dev/null +++ b/tolk-tester/tests/invalid-generics-12.tolk @@ -0,0 +1,15 @@ +fun getTwo(): X { return 2; } + +fun cantDeduceNonArgumentGeneric() { + var t1: [int] = [0]; + t1.0 = getTwo(); // ok + var t2 = createEmptyTuple(); + t2.tuplePush(0); + t2.0 = getTwo(); // error, can't decude X +} + +/** +@compilation_should_fail +@stderr can not deduce X for generic function `getTwo` +@stderr t2.0 = getTwo(); + */ diff --git a/tolk-tester/tests/invalid-typing-13.tolk b/tolk-tester/tests/invalid-typing-13.tolk new file mode 100644 index 000000000..e356d0f33 --- /dev/null +++ b/tolk-tester/tests/invalid-typing-13.tolk @@ -0,0 +1,9 @@ +fun failAssignToInvalidTupleIndex() { + var ab = [1, 2]; + ab.100500 = 5; +} + +/** +@compilation_should_fail +@stderr invalid tuple index, expected 0..1 + */ diff --git a/tolk-tester/tests/known-bugs.tolk b/tolk-tester/tests/known-bugs.tolk deleted file mode 100644 index 4de6a3752..000000000 --- a/tolk-tester/tests/known-bugs.tolk +++ /dev/null @@ -1,27 +0,0 @@ -fun increment(mutate x: int): int { - x = x + 1; - return x; -} - -@method_id(101) -fun bugWithModifyingMethodInsideSameExpression() { - /* - The same bug existed in FunC: -#pragma allow-post-modification; -(int, int) ~increment(int x) { x = x + 5; return (x, x); } -int main() { int x = 0; x += x~increment(); return x; } - It's related to using a variable modified by ~method inside the same expression. - */ - var x = 0; - x = x + increment(mutate x); - return x; -} - -fun main() { - -} - -/** -// correct: 2 -@testcase | 101 | | 1 - */ diff --git a/tolk-tester/tests/logical-operators.tolk b/tolk-tester/tests/logical-operators.tolk index fb437bb34..29cd1d104 100644 --- a/tolk-tester/tests/logical-operators.tolk +++ b/tolk-tester/tests/logical-operators.tolk @@ -210,15 +210,15 @@ fun main() { compileTimeEval1 PROC:<{ // x DUP // x x - 0 EQINT // x _1 - FALSE // x _1 _4 - TRUE // x _1 _4 _7 - FALSE // x _1 _4 _7 _11 - s0 s4 XCHG // _11 _1 _4 _7 x - 0 EQINT // _11 _1 _4 _7 _12 - -10 EQINT // _11 _1 _4 _7 _14 + 0 EQINT // x '1 + FALSE // x '1 '4 + TRUE // x '1 '4 '7 + FALSE // x '1 '4 '7 '11 + s0 s4 XCHG // '11 '1 '4 '7 x + 0 EQINT // '11 '1 '4 '7 '12 + -10 EQINT // '11 '1 '4 '7 '14 s3 s4 XCHG - s1 s3 s0 XCHG3 // _1 _4 _7 _11 _14 + s1 s3 s0 XCHG3 // '1 '4 '7 '11 '14 }> """ @@ -230,15 +230,15 @@ fun main() { OVER // x y x IFNOTJMP:<{ // x y 2DROP // - 10 PUSHINT // _2=10 + 10 PUSHINT // '2=10 }> // x y DUP // x y y IFNOTJMP:<{ // x y 2DROP // - 20 PUSHINT // _3=20 + 20 PUSHINT // '3=20 RETALT }> // x y - ADD // _4 + ADD // '4 }> """ @@ -297,10 +297,10 @@ These are moments of future optimizations. For now, it's more than enough. // a b SWAP // b a IF:<{ // b - 0 NEQINT // _2 + 0 NEQINT // '2 }>ELSE<{ // b DROP // - 0 PUSHINT // _2=0 + 0 PUSHINT // '2=0 }> }> """ @@ -310,13 +310,13 @@ These are moments of future optimizations. For now, it's more than enough. testOrSimpleCodegen PROC:<{ // a b SWAP // b a - 0 GTINT // b _3 + 0 GTINT // b '3 IF:<{ // b DROP // - -1 PUSHINT // _4=-1 + -1 PUSHINT // '4=-1 }>ELSE<{ // b - 0 GTINT // _7 - 0 NEQINT // _4 + 0 GTINT // '7 + 0 NEQINT // '4 }> }> """ @@ -332,15 +332,15 @@ These are moments of future optimizations. For now, it's more than enough. DUP // x x IFNOTJMP:<{ // x DROP // - 1 PUSHINT // _7=1 + 1 PUSHINT // '5=1 }> // x DUP // x x IFNOTJMP:<{ // x DROP // - 1 PUSHINT // _8=1 + 1 PUSHINT // '6=1 }> // x 100 THROWIFNOT - -4 PUSHINT // _12=-4 + -4 PUSHINT // '9=-4 }> """ diff --git a/tolk-tester/tests/method_id.tolk b/tolk-tester/tests/method_id.tolk index c2d0b9aad..e7e70d245 100644 --- a/tolk-tester/tests/method_id.tolk +++ b/tolk-tester/tests/method_id.tolk @@ -4,6 +4,8 @@ fun foo1(): int { return 111; } fun foo2(): int { return 222; } @method_id(10) fun foo3(): int { return 333; } +@method_id(11) +fun slice(slice: slice): slice { return slice; } fun main(): int { return 999; } /** diff --git a/tolk-tester/tests/mutate-methods.tolk b/tolk-tester/tests/mutate-methods.tolk index 816e4c8d2..ebd07aca7 100644 --- a/tolk-tester/tests/mutate-methods.tolk +++ b/tolk-tester/tests/mutate-methods.tolk @@ -307,7 +307,7 @@ fun main(){} ... incrementTwoInPlace CALLDICT // x y sum1 -ROT - 10 PUSHINT // sum1 x y _8=10 + 10 PUSHINT // sum1 x y '10=10 incrementTwoInPlace CALLDICT // sum1 x y sum2 s1 s3 s0 XCHG3 // x y sum1 sum2 }> @@ -317,8 +317,8 @@ fun main(){} """ load_next PROC:<{ // cs - 32 LDI // _3 cs - SWAP // cs _3 + 32 LDI // '4 cs + SWAP // cs '4 }> """ @@ -326,7 +326,7 @@ fun main(){} """ testStoreUintPureUnusedResult PROC:<{ // - 0 PUSHINT // _11=0 + 0 PUSHINT // '11=0 }> """ @@ -335,9 +335,9 @@ fun main(){} testStoreUintImpureUnusedResult PROC:<{ // NEWC // b - STIX // _2 + STIX // '2 DROP // - 0 PUSHINT // _11=0 + 0 PUSHINT // '11=0 }> """ diff --git a/tolk-tester/tests/no-spaces.tolk b/tolk-tester/tests/no-spaces.tolk index da7338989..409bb342c 100644 --- a/tolk-tester/tests/no-spaces.tolk +++ b/tolk-tester/tests/no-spaces.tolk @@ -104,10 +104,10 @@ fun`main`(){} DUP // fst1=-1 snd1=-1 2 PUSHINT // fst1=-1 snd1=-1 trd1=2 s1 s1 s0 PUSH3 // fst1=-1 snd1=-1 trd1=2 fst2=-1 snd2=-1 trd2=2 - add3 CALLDICT // fst1=-1 snd1=-1 trd1=2 _13 - 3 -ROLL // _13 fst1=-1 snd1=-1 trd1=2 - add3 CALLDICT // _13 _14 - PAIR // _12 + add3 CALLDICT // fst1=-1 snd1=-1 trd1=2 '13 + 3 -ROLL // '13 fst1=-1 snd1=-1 trd1=2 + add3 CALLDICT // '13 '14 + PAIR // '12 }> """ diff --git a/tolk-tester/tests/null-keyword.tolk b/tolk-tester/tests/null-keyword.tolk index 9ace99956..69678434d 100644 --- a/tolk-tester/tests/null-keyword.tolk +++ b/tolk-tester/tests/null-keyword.tolk @@ -99,21 +99,21 @@ fun main() { test1 PROC:<{ // PUSHNULL // numbers - 1 PUSHINT // numbers _2=1 - SWAP // _2=1 numbers + 1 PUSHINT // numbers '2=1 + SWAP // '2=1 numbers CONS // numbers - 2 PUSHINT // numbers _4=2 - SWAP // _4=2 numbers + 2 PUSHINT // numbers '4=2 + SWAP // '4=2 numbers CONS // numbers - 3 PUSHINT // numbers _6=3 - SWAP // _6=3 numbers + 3 PUSHINT // numbers '6=3 + SWAP // '6=3 numbers CONS // numbers - 4 PUSHINT // numbers _8=4 - SWAP // _8=4 numbers + 4 PUSHINT // numbers '8=4 + SWAP // '8=4 numbers CONS // numbers UNCONS // h numbers DUP // h numbers numbers - CAR // h numbers _12 + CAR // h numbers '13 """ @fif_codegen @@ -121,11 +121,11 @@ fun main() { main PROC:<{ // PUSHNULL // i - ISNULL // _2 + ISNULL // '2 IFJMP:<{ // - 1 PUSHINT // _3=1 + 1 PUSHINT // '3=1 }> // - 10 PUSHINT // _4=10 + 10 PUSHINT // '4=10 }> """ @@ -133,14 +133,14 @@ fun main() { """ test7 PROC:<{ ... - LDOPTREF // b _18 _17 + LDOPTREF // b '8 '7 DROP // b c - ISNULL // b _11 - 10 MULCONST // b _13 - SWAP // _13 b - ISNULL // _13 _14 - NOT // _13 _15 - ADD // _16 + ISNULL // b '11 + 10 MULCONST // b '13 + SWAP // '13 b + ISNULL // '13 '14 + NOT // '13 '15 + ADD // '16 }> """ */ diff --git a/tolk-tester/tests/op-priority.tolk b/tolk-tester/tests/op-priority.tolk index 8a57b3940..e3c6bb6e8 100644 --- a/tolk-tester/tests/op-priority.tolk +++ b/tolk-tester/tests/op-priority.tolk @@ -95,26 +95,26 @@ fun main() { unary_minus_1 PROC:<{ // a b c -ROT // c a b - ADD // c _3 - NEGATE // c _4 - SWAP // _4 c - MUL // _5 + ADD // c '3 + NEGATE // c '4 + SWAP // '4 c + MUL // '5 }> unary_minus_2 PROC:<{ // a b c -ROT // c a b - ADD // c _3 - NEGATE // c _4 - SWAP // _4 c - MUL // _5 + ADD // c '3 + NEGATE // c '4 + SWAP // '4 c + MUL // '5 }> unary_minus_3 PROC:<{ // a b c -ROT // c a b - ADD // c _3 - SWAP // _3 c - MUL // _4 - NEGATE // _5 + ADD // c '3 + SWAP // '3 c + MUL // '4 + NEGATE // '5 }> """ diff --git a/tolk-tester/tests/use-before-declare.tolk b/tolk-tester/tests/use-before-declare.tolk index 384569b93..d3e6b165c 100644 --- a/tolk-tester/tests/use-before-declare.tolk +++ b/tolk-tester/tests/use-before-declare.tolk @@ -43,7 +43,7 @@ const demo_20: int = 20; """ test1 PROC:<{ // - 30 PUSHINT // _10 + 30 PUSHINT // '10 }> """ */ diff --git a/tolk-tester/tests/var-apply.tolk b/tolk-tester/tests/var-apply.tolk index a0918c181..168635602 100644 --- a/tolk-tester/tests/var-apply.tolk +++ b/tolk-tester/tests/var-apply.tolk @@ -129,6 +129,15 @@ fun testVarApply3() { return (getIntAt(t, 0), getTupleFirstInt(t), getTupleLastTuple(t), getTupleLastGetter()(t)); } +@method_id(107) +fun testIndexedAccessApply() { + var functions1 = (beginCell, endCell); + var functions2 = [beginParse]; + var b = functions1.0().storeInt(1, 16); + b.storeInt(1, 16); + return functions2.0(functions1.1(b)).loadInt(32); +} + fun main() {} /** @@ -138,4 +147,5 @@ fun main() {} @testcase | 104 | | 240 @testcase | 105 | | 1 @testcase | 106 | | 1 1 [ 2 ] [ 2 ] +@testcase | 107 | | 65537 */ diff --git a/tolk/abscode.cpp b/tolk/abscode.cpp index 7bcb0f84f..b465b72b4 100644 --- a/tolk/abscode.cpp +++ b/tolk/abscode.cpp @@ -26,32 +26,28 @@ namespace tolk { * */ -void TmpVar::dump(std::ostream& os) const { - show(os); - os << " : " << v_type << " (width "; - os << v_type->calc_width_on_stack(); - os << ")"; - if (coord > 0) { - os << " = _" << (coord >> 8) << '.' << (coord & 255); - } else if (coord < 0) { - int n = (~coord >> 8), k = (~coord & 0xff); - if (k) { - os << " = (_" << n << ".._" << (n + k - 1) << ")"; - } else { - os << " = ()"; - } +void TmpVar::show_as_stack_comment(std::ostream& os) const { + if (!name.empty()) { + os << name; + } else { + os << '\'' << ir_idx; } - os << std::endl; +#ifdef TOLK_DEBUG + // uncomment for detailed stack output, like `'15(binary-op) '16(glob-var)` + // if (desc) os << desc; +#endif } -void TmpVar::show(std::ostream& os, int omit_idx) const { - if (v_sym) { - os << v_sym->name; - if (omit_idx >= 2) { - return; - } +void TmpVar::show(std::ostream& os) const { + os << '\'' << ir_idx; // vars are printed out as `'1 '2` (in stack comments, debug info, etc.) + if (!name.empty()) { + os << '_' << name; + } +#ifdef TOLK_DEBUG + if (desc) { + os << ' ' << desc; // "origin" of implicitly created tmp var, like `'15 (binary-op) '16 (glob-var)` } - os << '_' << idx; +#endif } std::ostream& operator<<(std::ostream& os, const TmpVar& var) { @@ -105,7 +101,7 @@ void VarDescr::show(std::ostream& os, const char* name) const { if (name) { os << name; } - os << '_' << idx; + os << '\'' << idx; show_value(os); } @@ -182,47 +178,6 @@ void VarDescrList::show(std::ostream& os) const { os << " ]\n"; } -void Op::split_vars(const std::vector& vars) { - split_var_list(left, vars); - split_var_list(right, vars); - for (auto& op : block0) { - op.split_vars(vars); - } - for (auto& op : block1) { - op.split_vars(vars); - } -} - -void Op::split_var_list(std::vector& var_list, const std::vector& vars) { - int new_size = 0, changes = 0; - for (var_idx_t v : var_list) { - int c = vars.at(v).coord; - if (c < 0) { - ++changes; - new_size += (~c & 0xff); - } else { - ++new_size; - } - } - if (!changes) { - return; - } - std::vector new_var_list; - new_var_list.reserve(new_size); - for (var_idx_t v : var_list) { - int c = vars.at(v).coord; - if (c < 0) { - int n = (~c >> 8), k = (~c & 0xff); - while (k-- > 0) { - new_var_list.push_back(n++); - } - } else { - new_var_list.push_back(v); - } - } - var_list = std::move(new_var_list); -} - void Op::show(std::ostream& os, const std::vector& vars, std::string pfx, int mode) const { if (mode & 2) { os << pfx << " ["; @@ -384,7 +339,7 @@ void Op::show_var_list(std::ostream& os, const std::vector& idx_list, } else { os << "(" << vars.at(idx_list[0]); for (std::size_t i = 1; i < idx_list.size(); i++) { - os << "," << vars.at(idx_list[i]); + os << ", " << vars.at(idx_list[i]); } os << ")"; } @@ -429,11 +384,12 @@ void CodeBlob::print(std::ostream& os, int flags) const { os << "CODE BLOB: " << var_cnt << " variables, " << in_var_cnt << " input\n"; if ((flags & 8) != 0) { for (const auto& var : vars) { - var.dump(os); - if (var.where.is_defined() && (flags & 1) != 0) { - var.where.show(os); + var.show(os); + os << " : " << var.v_type << std::endl; + if (var.loc.is_defined() && (flags & 1) != 0) { + var.loc.show(os); os << " defined here:\n"; - var.where.show_context(os); + var.loc.show_context(os); } } } @@ -444,26 +400,26 @@ void CodeBlob::print(std::ostream& os, int flags) const { os << "-------- END ---------\n\n"; } -var_idx_t CodeBlob::create_var(TypePtr var_type, const LocalVarData* v_sym, SrcLocation location) { - vars.emplace_back(var_cnt, var_type, v_sym, location); - return var_cnt++; -} - -bool CodeBlob::import_params(FormalArgList&& arg_list) { - if (var_cnt || in_var_cnt) { - return false; - } - std::vector list; - for (const auto& par : arg_list) { - TypePtr arg_type; - const LocalVarData* arg_sym; - SrcLocation arg_loc; - std::tie(arg_type, arg_sym, arg_loc) = par; - list.push_back(create_var(arg_type, arg_sym, arg_loc)); - } - emplace_back(loc, Op::_Import, list); - in_var_cnt = var_cnt; - return true; +std::vector CodeBlob::create_var(TypePtr var_type, SrcLocation loc, std::string name) { + std::vector ir_idx; + int stack_w = var_type->calc_width_on_stack(); + ir_idx.reserve(stack_w); + if (const TypeDataTensor* t_tensor = var_type->try_as()) { + for (int i = 0; i < t_tensor->size(); ++i) { + std::string sub_name = name.empty() ? name : name + "." + std::to_string(i); + std::vector nested = create_var(t_tensor->items[i], loc, std::move(sub_name)); + ir_idx.insert(ir_idx.end(), nested.begin(), nested.end()); + } + } else if (var_type != TypeDataVoid::create()) { +#ifdef TOLK_DEBUG + tolk_assert(stack_w == 1); +#endif + vars.emplace_back(var_cnt, var_type, std::move(name), loc); + ir_idx.emplace_back(var_cnt); + var_cnt++; + } + tolk_assert(static_cast(ir_idx.size()) == stack_w); + return ir_idx; } } // namespace tolk diff --git a/tolk/analyzer.cpp b/tolk/analyzer.cpp index 8539afdd2..9303bc832 100644 --- a/tolk/analyzer.cpp +++ b/tolk/analyzer.cpp @@ -26,40 +26,6 @@ namespace tolk { * */ -int CodeBlob::split_vars(bool strict) { - int n = var_cnt, changes = 0; - for (int j = 0; j < var_cnt; j++) { - TmpVar& var = vars[j]; - int width_j = var.v_type->calc_width_on_stack(); - if (strict && width_j < 0) { - throw ParseError{var.where, "variable does not have fixed width, cannot manipulate it"}; - } - if (width_j == 1) { - continue; - } - std::vector comp_types; - var.v_type->extract_components(comp_types); - tolk_assert(width_j <= 254 && n <= 0x7fff00); - tolk_assert((unsigned)width_j == comp_types.size()); - var.coord = ~((n << 8) + width_j); - for (int i = 0; i < width_j; i++) { - auto v = create_var(comp_types[i], vars[j].v_sym, vars[j].where); - tolk_assert(v == n + i); - tolk_assert(vars[v].idx == v); - vars[v].coord = ((int)j << 8) + i + 1; - } - n += width_j; - ++changes; - } - if (!changes) { - return 0; - } - for (auto& op : ops) { - op.split_vars(vars); - } - return changes; -} - bool CodeBlob::compute_used_code_vars() { VarDescrList empty_var_info; return compute_used_code_vars(ops, empty_var_info, true); diff --git a/tolk/asmops.cpp b/tolk/asmops.cpp index 547922dad..2618ed26e 100644 --- a/tolk/asmops.cpp +++ b/tolk/asmops.cpp @@ -302,24 +302,13 @@ Const AsmOpList::get_const(const_idx_t idx) { } } -void AsmOpList::show_var(std::ostream& os, var_idx_t idx) const { - if (!var_names_ || (unsigned)idx >= var_names_->size()) { - os << '_' << idx; - } else { - var_names_->at(idx).show(os, 2); - } -} - void AsmOpList::show_var_ext(std::ostream& os, std::pair idx_pair) const { - auto i = idx_pair.first; - auto j = idx_pair.second; + var_idx_t i = idx_pair.first; + const_idx_t j = idx_pair.second; if (!var_names_ || (unsigned)i >= var_names_->size()) { - os << '_' << i; + os << '\'' << i; } else { - var_names_->at(i).show(os, 2); - // if (!var_names_->at(i).v_type->is_int()) { - // os << '<'; var_names_->at(i).v_type->print(os); os << '>'; - // } + var_names_->at(i).show_as_stack_comment(os); } if ((unsigned)j < constants_.size() && constants_[j].not_null()) { os << '=' << constants_[j]; diff --git a/tolk/ast-from-tokens.cpp b/tolk/ast-from-tokens.cpp index 58592011e..f5855bc1f 100644 --- a/tolk/ast-from-tokens.cpp +++ b/tolk/ast-from-tokens.cpp @@ -111,16 +111,6 @@ static void diagnose_addition_in_bitshift(SrcLocation loc, std::string_view bits } } -// fire an error for FunC-style variable declaration, like "int i" -GNU_ATTRIBUTE_NORETURN GNU_ATTRIBUTE_COLD -static void fire_error_FunC_style_var_declaration(Lexer& lex) { - SrcLocation loc = lex.cur_location(); - std::string type_str = static_cast(lex.cur_str()); // int / slice / etc. - lex.next(); - std::string var_name = lex.tok() == tok_identifier ? static_cast(lex.cur_str()) : "name"; - throw ParseError(loc, "can't parse; probably, you use FunC-like declarations; valid syntax is `var " + var_name + ": " + type_str + " = ...`"); -} - // replace (a == null) and similar to isNull(a) (call of a built-in function) static AnyExprV maybe_replace_eq_null_with_isNull_call(V v) { bool has_null = v->get_lhs()->type == ast_null_keyword || v->get_rhs()->type == ast_null_keyword; @@ -377,14 +367,8 @@ static AnyExprV parse_expr100(Lexer& lex) { } return createV(loc, v_ident, v_instantiationTs); } - default: { - // show a proper error for `int i` (FunC-style declarations) - TokenType t = lex.tok(); - if (t == tok_int || t == tok_cell || t == tok_slice || t == tok_builder || t == tok_tuple) { - fire_error_FunC_style_var_declaration(lex); - } + default: lex.unexpected(""); - } } } @@ -405,12 +389,15 @@ static AnyExprV parse_expr80(Lexer& lex) { lex.next(); V v_ident = nullptr; V v_instantiationTs = nullptr; - if (lex.tok() == tok_identifier) { + if (lex.tok() == tok_identifier) { // obj.field / obj.method v_ident = createV(lex.cur_location(), lex.cur_str()); lex.next(); if (lex.tok() == tok_lt) { v_instantiationTs = parse_maybe_instantiationTs_after_identifier(lex); } + } else if (lex.tok() == tok_int_const) { // obj.0 (indexed access) + v_ident = createV(lex.cur_location(), lex.cur_str()); + lex.next(); } else { lex.unexpected("method name"); } diff --git a/tolk/ast.h b/tolk/ast.h index b90507e7e..d2db49f81 100644 --- a/tolk/ast.h +++ b/tolk/ast.h @@ -529,8 +529,14 @@ struct Vertex final : ASTExprUnary { public: - typedef const FunctionData* DotTarget; // for `t.tupleAt` target is `tupleAt` global function - DotTarget target = nullptr; // filled at type inferring + typedef std::variant< + const FunctionData*, // for `t.tupleAt` target is `tupleAt` global function + int // for `t.0` target is "indexed access" 0 + > DotTarget; + DotTarget target = static_cast(nullptr); // filled at type inferring + + bool is_target_fun_ref() const { return std::holds_alternative(target); } + bool is_target_indexed_access() const { return std::holds_alternative(target); } AnyExprV get_obj() const { return child; } auto get_identifier() const { return identifier; } diff --git a/tolk/builtins.cpp b/tolk/builtins.cpp index d704ec4d3..2b207c253 100644 --- a/tolk/builtins.cpp +++ b/tolk/builtins.cpp @@ -1060,6 +1060,17 @@ AsmOp compile_tuple_at(std::vector& res, std::vector& args, return exec_op("INDEXVAR", 2, 1); } +// fun tupleSetAt(mutate self: tuple, value: X, index: int): void asm "SETINDEXVAR"; +AsmOp compile_tuple_set_at(std::vector& res, std::vector& args, SrcLocation) { + tolk_assert(args.size() == 3 && res.size() == 1); + auto& y = args[2]; + if (y.is_int_const() && y.int_const >= 0 && y.int_const < 16) { + y.unused(); + return exec_arg_op("SETINDEX", y.int_const, 1, 1); + } + return exec_op("SETINDEXVAR", 2, 1); +} + // fun __isNull(X arg): bool AsmOp compile_is_null(std::vector& res, std::vector& args, SrcLocation) { tolk_assert(args.size() == 1 && res.size() == 1); @@ -1246,6 +1257,9 @@ void define_builtins() { define_builtin_func("tupleAt", {Tuple, Int}, typeT, declGenericT, compile_tuple_at, FunctionData::flagMarkedAsPure | FunctionData::flagAcceptsSelf); + define_builtin_func("tupleSetAt", {Tuple, typeT, Int}, Unit, declGenericT, + compile_tuple_set_at, + FunctionData::flagMarkedAsPure | FunctionData::flagHasMutateParams | FunctionData::flagAcceptsSelf); define_builtin_func("debugPrint", {typeT}, Unit, declGenericT, AsmOp::Custom("s0 DUMP DROP", 1, 1), 0); @@ -1255,6 +1269,13 @@ void define_builtins() { define_builtin_func("debugDumpStack", {}, Unit, nullptr, AsmOp::Custom("DUMPSTK", 0, 0), 0); + + // functions not presented in stdlib at all + // used in tolk-tester to check/expose internal compiler state + // each of them is handled in a special way, search by its name + define_builtin_func("__expect_type", {TypeDataUnknown::create(), Slice}, Unit, nullptr, + AsmOp::Nop(), + FunctionData::flagMarkedAsPure); } } // namespace tolk diff --git a/tolk/codegen.cpp b/tolk/codegen.cpp index 3830f7ae5..ad61b8a53 100644 --- a/tolk/codegen.cpp +++ b/tolk/codegen.cpp @@ -132,7 +132,7 @@ int Stack::drop_vars_except(const VarDescrList& var_info, int excl_var) { return dropped; } -void Stack::show(int flags) { +void Stack::show() { std::ostringstream os; for (auto i : s) { os << ' '; diff --git a/tolk/generics-helpers.cpp b/tolk/generics-helpers.cpp index 3d353cc4e..7a2dd83ff 100644 --- a/tolk/generics-helpers.cpp +++ b/tolk/generics-helpers.cpp @@ -202,8 +202,8 @@ td::Result> deduce_substitutionTs_on_generic_func_call(cons try { GenericSubstitutionsDeduceForFunctionCall deducing(called_fun); for (const LocalVarData& param : called_fun->parameters) { - if (param.declared_type->has_genericT_inside() && param.idx < static_cast(arg_types.size())) { - deducing.consider_next_condition(param.declared_type, arg_types[param.idx]); + if (param.declared_type->has_genericT_inside() && param.param_idx < static_cast(arg_types.size())) { + deducing.consider_next_condition(param.declared_type, arg_types[param.param_idx]); } } int idx = deducing.get_first_not_deduced_idx(); @@ -233,7 +233,7 @@ const FunctionData* instantiate_generic_function(SrcLocation loc, const Function std::vector parameters; parameters.reserve(fun_ref->get_num_params()); for (const LocalVarData& orig_p : fun_ref->parameters) { - parameters.emplace_back(orig_p.name, orig_p.loc, replace_genericT_with_deduced(orig_p.declared_type, fun_ref->genericTs, substitutionTs), orig_p.flags, orig_p.idx); + parameters.emplace_back(orig_p.name, orig_p.loc, replace_genericT_with_deduced(orig_p.declared_type, fun_ref->genericTs, substitutionTs), orig_p.flags, orig_p.param_idx); } TypePtr declared_return_type = replace_genericT_with_deduced(fun_ref->declared_return_type, fun_ref->genericTs, substitutionTs); const GenericsInstantiation* instantiationTs = new GenericsInstantiation(loc, std::move(substitutionTs)); diff --git a/tolk/lexer.cpp b/tolk/lexer.cpp index 7e8c8fb2d..06913a5fc 100644 --- a/tolk/lexer.cpp +++ b/tolk/lexer.cpp @@ -331,7 +331,6 @@ struct ChunkIdentifierOrKeyword final : ChunkLexerBase { if (str == "as") return tok_as; break; case 3: - if (str == "int") return tok_int; if (str == "var") return tok_var; if (str == "fun") return tok_fun; if (str == "asm") return tok_asm; @@ -342,18 +341,13 @@ struct ChunkIdentifierOrKeyword final : ChunkLexerBase { case 4: if (str == "else") return tok_else; if (str == "true") return tok_true; - if (str == "cell") return tok_cell; if (str == "null") return tok_null; - if (str == "void") return tok_void; - if (str == "bool") return tok_bool; if (str == "self") return tok_self; if (str == "tolk") return tok_tolk; if (str == "type") return tok_type; if (str == "enum") return tok_enum; break; case 5: - if (str == "slice") return tok_slice; - if (str == "tuple") return tok_tuple; if (str == "const") return tok_const; if (str == "false") return tok_false; if (str == "redef") return tok_redef; @@ -374,16 +368,12 @@ struct ChunkIdentifierOrKeyword final : ChunkLexerBase { if (str == "export") return tok_export; break; case 7: - if (str == "builder") return tok_builder; if (str == "builtin") return tok_builtin; break; case 8: if (str == "continue") return tok_continue; if (str == "operator") return tok_operator; break; - case 12: - if (str == "continuation") return tok_continuation; - break; default: break; } @@ -555,6 +545,15 @@ Lexer::Lexer(const SrcFile* file) next(); } +Lexer::Lexer(std::string_view text) + : file(nullptr) + , p_start(text.data()) + , p_end(p_start + text.size()) + , p_next(p_start) + , location() { + next(); +} + void Lexer::next() { while (cur_token_idx == last_token_idx && !is_eof()) { update_location(); @@ -563,7 +562,7 @@ void Lexer::next() { } } if (is_eof()) { - add_token(tok_eof, file->text); + add_token(tok_eof, ""); } cur_token = tokens_circularbuf[++cur_token_idx & 7]; } diff --git a/tolk/lexer.h b/tolk/lexer.h index 81d579db0..58bc3640a 100644 --- a/tolk/lexer.h +++ b/tolk/lexer.h @@ -118,14 +118,6 @@ enum TokenType { tok_if, tok_else, - tok_int, - tok_cell, - tok_bool, - tok_slice, - tok_builder, - tok_continuation, - tok_tuple, - tok_void, tok_arrow, tok_as, @@ -173,6 +165,7 @@ class Lexer { }; explicit Lexer(const SrcFile* file); + explicit Lexer(std::string_view text); Lexer(const Lexer&) = delete; Lexer &operator=(const Lexer&) = delete; diff --git a/tolk/pipe-ast-to-legacy.cpp b/tolk/pipe-ast-to-legacy.cpp index d60bb8b34..f5eca22cc 100644 --- a/tolk/pipe-ast-to-legacy.cpp +++ b/tolk/pipe-ast-to-legacy.cpp @@ -21,6 +21,7 @@ #include "type-system.h" #include "common/refint.h" #include "constant-evaluator.h" +#include /* * This pipe is the last one operating AST: it transforms AST to IR. @@ -28,38 +29,218 @@ * kernel (initially forked from FunC) comes into play. * Up to this point, all types have been inferred, all validity checks have been passed, etc. * All properties in AST nodes are assigned and can be safely used (fun_ref, etc.). - * So, if execution reaches this pass, the input is correct, and code generation should succeed. + * So, if execution reaches this pass, the input is (almost) correct, and code generation should succeed. + * The only thing additionally checked during this pass is tricky lvalue, like one and the same variable + * assigned/mutated multiple times in same expression, e.g. `(t.0, t.0) = rhs` / `f(mutate x.1.2, mutate x)`. */ namespace tolk { -struct LValGlobs { - std::vector> globs; +// fire error on cases like `(a, a) = rhs` / `f(mutate t.1.0, mutate t.1.0)` +GNU_ATTRIBUTE_NORETURN GNU_ATTRIBUTE_COLD +static void fire_error_variable_modified_twice_inside_same_expression(SrcLocation loc) { + throw ParseError(loc, "one variable modified twice inside the same expression"); +} - void add_modified_glob(const GlobalVarData* g_sym, var_idx_t local_ir_idx) { - globs.emplace_back(g_sym, local_ir_idx); - } +// fire error on cases like `(m.1.0, m.1) = rhs` (m.1 inside m.1.0 is "rval inside lval") +GNU_ATTRIBUTE_NORETURN GNU_ATTRIBUTE_COLD +static void fire_error_variable_modified_and_read_inside_same_expression(SrcLocation loc) { + throw ParseError(loc, "one variable both modified and read inside the same expression"); +} - void gen_ops_set_globs(CodeBlob& code, SrcLocation loc) const { - for (const auto& [g_sym, ir_idx] : globs) { - Op& op = code.emplace_back(loc, Op::_SetGlob, std::vector{}, std::vector{ ir_idx }, g_sym); +// Main goal of LValContext is to handle non-primitive lvalues. At IR level, a usual local variable +// exists, but on its change, something non-trivial should happen. +// Example: `globalVar = 9` actually does `Const $5 = 9` + `Let $6 = $5` + `SetGlob "globVar" = $6` +// Example: `tupleVar.0 = 9` actually does `Const $5 = 9` + `Let $6 = $5` + `Const $7 = 0` + `Call tupleSetAt($4, $6, $7)` +// Of course, mixing globals with tuples should also be supported. +// To achieve this, treat tupleObj inside "tupleObj.i" like "rvalue inside lvalue". +// For instance, `globalTuple.0 = 9` reads global (like rvalue), assigns 9 to tmp var, modifies tuple, writes global. +// A challenging thing is handling "unique" parts, to be read/updated only once. +// Example: `f(mutate globalTensor.0, mutate globalTensor.1)`, then globalTensor should be read/written once. +// Example: `(t.0.0, t.0.1) = rhs` (m is [[int, int]]), then t.0 should be read/updated once. +// Solving this by calculating hashes of every lvalue or rvalue inside lvalue automatically gives an ability +// to detect and fire "multiple writes inside expression", like `(a, a) = rhs` / `[t.0, (t.0.1, c)] = rhs`. +// Note, that tensors (not tuples) `tensorVar.0 = 9` do not emit anything special (unless global). +class LValContext { + // every global variable used as lvalue is registered here + // example: `globalInt = 9`, implicit var is created `$tmp = 9`, and `SetGlob "globalInt" $tmp` is done after + // global tensors are stored as tuples (unpacked on reading, packed on writing), then multiple tmp vars are created + struct ModifiedGlob { + const GlobalVarData* glob_ref; + std::vector local_ir_idx; // typically 1, generally calc_width_on_stack() of global var (tensors) + + void apply(CodeBlob& code, SrcLocation loc) const { + Op& op = code.emplace_back(loc, Op::_SetGlob, std::vector{}, local_ir_idx, glob_ref); op.set_impure_flag(); } + }; + + // every tuple index used as lvalue is registered here + // example: `t.0 = 9`, implicit var is created `$tmp = 9`, as well as `$tmp_idx = 0` and `tupleSetAt()` is done after + // for `t.0.0` if t is `[[int, ...]]`, `tupleAt()` for it is done since it's rvalue, and `tupleSetAt()` is done 2 times + struct ModifiedTupleIndex { + uint64_t hash; + var_idx_t tuple_ir_idx; + var_idx_t index_ir_idx; + var_idx_t field_ir_idx; + + void apply(CodeBlob& code, SrcLocation loc) const { + const FunctionData* builtin_sym = lookup_global_symbol("tupleSetAt")->as(); + code.emplace_back(loc, Op::_Call, std::vector{tuple_ir_idx}, std::vector{tuple_ir_idx, field_ir_idx, index_ir_idx}, builtin_sym); + } + }; + + int level_rval_inside_lval = 0; + std::vector> modifications; + std::unordered_set all_modified_hashes; + + void fire_if_one_variable_modified_twice(SrcLocation loc, uint64_t modified_hash) { + if (!is_rval_inside_lval()) { + if (!all_modified_hashes.insert(modified_hash).second) { + fire_error_variable_modified_twice_inside_same_expression(loc); + } + if (all_modified_hashes.contains(~modified_hash)) { + fire_error_variable_modified_and_read_inside_same_expression(loc); + } + } else { + all_modified_hashes.insert(~modified_hash); + if (all_modified_hashes.contains(modified_hash)) { + fire_error_variable_modified_and_read_inside_same_expression(loc); + } + } + } + +public: + void enter_rval_inside_lval() { level_rval_inside_lval++; } + void exit_rval_inside_lval() { level_rval_inside_lval--; } + bool is_rval_inside_lval() const { return level_rval_inside_lval > 0; } + + uint64_t register_lval(SrcLocation loc, const LocalVarData* var_ref) { + uint64_t hash = reinterpret_cast(var_ref); + fire_if_one_variable_modified_twice(loc, hash); + return hash; + } + + uint64_t register_lval(SrcLocation loc, const GlobalVarData* glob_ref) { + uint64_t hash = reinterpret_cast(glob_ref); + fire_if_one_variable_modified_twice(loc, hash); + return hash; + } + + uint64_t register_lval(SrcLocation loc, V v) { + uint64_t hash = 7; + AnyExprV leftmost_obj = v; + while (auto v_dot = leftmost_obj->try_as()) { + if (!v_dot->is_target_indexed_access()) { + break; + } + hash = hash * 1915239017 + std::get(v_dot->target); + leftmost_obj = v_dot->get_obj(); + } + if (auto v_ref = leftmost_obj->try_as()) { + hash *= reinterpret_cast(v_ref->sym); // `v.0` and `v.0` in 2 places is the same + } else { + hash *= reinterpret_cast(leftmost_obj); // unlike `f().0` and `f().0` (pointers to AST nodes differ) + } + fire_if_one_variable_modified_twice(loc, hash); + return hash; + } + + const std::vector* exists_already_known_global(const GlobalVarData* glob_ref) const { + for (const auto& m : modifications) { + if (const auto* m_glob = std::get_if(&m); m_glob && m_glob->glob_ref == glob_ref) { + return &m_glob->local_ir_idx; + } + } + return nullptr; + } + + const var_idx_t* exists_already_known_tuple_index(uint64_t hash) const { + for (const auto& m : modifications) { + if (const auto* m_tup = std::get_if(&m); m_tup && m_tup->hash == hash) { + return &m_tup->field_ir_idx; + } + } + return nullptr; + } + + void register_modified_global(const GlobalVarData* glob_ref, std::vector local_ir_idx) { + modifications.emplace_back(ModifiedGlob{glob_ref, std::move(local_ir_idx)}); + } + + void register_modified_tuple_index(uint64_t hash, var_idx_t tuple_ir_idx, var_idx_t index_ir_idx, var_idx_t field_ir_idx) { + modifications.emplace_back(ModifiedTupleIndex{hash, tuple_ir_idx, index_ir_idx, field_ir_idx}); + } + + void gen_ops_if_nonempty(CodeBlob& code, SrcLocation loc) const { + for (auto it = modifications.rbegin(); it != modifications.rend(); ++it) { // reverse, it's important + if (const auto* m_glob = std::get_if(&*it)) { + m_glob->apply(code, loc); + } else if (const auto* m_tup = std::get_if(&*it)) { + m_tup->apply(code, loc); + } + } } }; -std::vector pre_compile_expr(AnyExprV v, CodeBlob& code, LValGlobs* lval_globs = nullptr); +// The goal of VarsModificationWatcher is to detect such cases: `return (x, x += y, x)`. +// Without any changes, ops will be { _Call $2 = +($0_x, $1_y); _Return $0_x, $2, $0_x } - incorrect +// Correct will be to introduce tmp var: { _Let $3 = $0_x; _Call $2 = ...; _Return $3, $2, $0_x } +// This "introducing" is done when compiling tensors, whereas this class allows to watch vars for modification. +class VarsModificationWatcher { + struct WatchedVar { + var_idx_t ir_idx; + std::function on_modification_callback; + + WatchedVar(var_idx_t ir_idx, std::function on_modification_callback) + : ir_idx(ir_idx), on_modification_callback(std::move(on_modification_callback)) {} + }; + + std::vector all_callbacks; + +public: + + bool empty() const { return all_callbacks.empty(); } + + void push_callback(var_idx_t ir_idx, std::function callback) { + all_callbacks.emplace_back(ir_idx, std::move(callback)); + } + + void pop_callback(var_idx_t ir_idx) { + for (auto it = all_callbacks.rbegin(); it != all_callbacks.rend(); ++it) { + if (it->ir_idx == ir_idx) { + all_callbacks.erase((it + 1).base()); + return; + } + } + tolk_assert(false); + } + + void trigger_callbacks(const std::vector& left_lval_indices, SrcLocation loc) const { + for (const WatchedVar& w : all_callbacks) { + for (var_idx_t changed_var : left_lval_indices) { + if (w.ir_idx == changed_var) { + w.on_modification_callback(loc, w.ir_idx); + } + } + } + } +}; + +static VarsModificationWatcher vars_modification_watcher; + +std::vector pre_compile_expr(AnyExprV v, CodeBlob& code, LValContext* lval_ctx = nullptr); void process_any_statement(AnyV v, CodeBlob& code); static std::vector> pre_compile_tensor_inner(CodeBlob& code, const std::vector& args, - LValGlobs* lval_globs) { + LValContext* lval_ctx) { const int n = static_cast(args.size()); if (n == 0) { // just `()` return {}; } if (n == 1) { // just `(x)`: even if x is modified (e.g. `f(x=x+2)`), there are no next arguments - return {pre_compile_expr(args[0], code, lval_globs)}; + return {pre_compile_expr(args[0], code, lval_ctx)}; } // the purpose is to handle such cases: `return (x, x += y, x)` @@ -81,9 +262,9 @@ static std::vector> pre_compile_tensor_inner(CodeBlob& co void add_and_watch_modifications(std::vector&& vars_of_ith_arg, CodeBlob& code) { for (var_idx_t ir_idx : vars_of_ith_arg) { - if (code.vars[ir_idx].v_sym && !is_watched(ir_idx)) { + if (!code.vars[ir_idx].name.empty() && !is_watched(ir_idx)) { watched_vars.emplace_back(ir_idx); - code.vars[ir_idx].on_modification.emplace_back([this, &code, ir_idx](SrcLocation loc) { + vars_modification_watcher.push_callback(ir_idx, [this, &code](SrcLocation loc, var_idx_t ir_idx) { on_var_modified(ir_idx, loc, code); }); } @@ -93,16 +274,18 @@ static std::vector> pre_compile_tensor_inner(CodeBlob& co void on_var_modified(var_idx_t ir_idx, SrcLocation loc, CodeBlob& code) { tolk_assert(is_watched(ir_idx)); - var_idx_t tmp_idx = code.create_tmp_var(code.vars[ir_idx].v_type, loc); + std::vector tmp_idx_arr = code.create_tmp_var(code.vars[ir_idx].v_type, loc, "(pre-modified)"); + tolk_assert(tmp_idx_arr.size() == 1); + var_idx_t tmp_idx = tmp_idx_arr[0]; code.emplace_back(loc, Op::_Let, std::vector{tmp_idx}, std::vector{ir_idx}); for (std::vector& prev_vars : res_lists) { std::replace(prev_vars.begin(), prev_vars.end(), ir_idx, tmp_idx); } } - std::vector> clear_and_stop_watching(CodeBlob& code) { + std::vector> clear_and_stop_watching() { for (var_idx_t ir_idx : watched_vars) { - code.vars[ir_idx].on_modification.pop_back(); + vars_modification_watcher.pop_callback(ir_idx); } watched_vars.clear(); return std::move(res_lists); @@ -111,15 +294,15 @@ static std::vector> pre_compile_tensor_inner(CodeBlob& co WatchingVarList watched_vars(n); for (int arg_idx = 0; arg_idx < n; ++arg_idx) { - std::vector vars_of_ith_arg = pre_compile_expr(args[arg_idx], code, lval_globs); + std::vector vars_of_ith_arg = pre_compile_expr(args[arg_idx], code, lval_ctx); watched_vars.add_and_watch_modifications(std::move(vars_of_ith_arg), code); } - return watched_vars.clear_and_stop_watching(code); + return watched_vars.clear_and_stop_watching(); } static std::vector pre_compile_tensor(CodeBlob& code, const std::vector& args, - LValGlobs* lval_globs = nullptr) { - std::vector> res_lists = pre_compile_tensor_inner(code, args, lval_globs); + LValContext* lval_ctx = nullptr) { + std::vector> res_lists = pre_compile_tensor_inner(code, args, lval_ctx); std::vector res; for (const std::vector& list : res_lists) { res.insert(res.end(), list.cbegin(), list.cend()); @@ -131,11 +314,11 @@ static std::vector pre_compile_let(CodeBlob& code, AnyExprV lhs, AnyE // [lhs] = [rhs]; since type checking is ok, it's the same as "lhs = rhs" if (lhs->type == ast_typed_tuple && rhs->type == ast_typed_tuple) { std::vector right = pre_compile_tensor(code, rhs->as()->get_items()); - LValGlobs globs; - std::vector left = pre_compile_tensor(code, lhs->as()->get_items(), &globs); - code.on_var_modification(left, loc); + LValContext local_lval; + std::vector left = pre_compile_tensor(code, lhs->as()->get_items(), &local_lval); + vars_modification_watcher.trigger_callbacks(left, loc); code.emplace_back(loc, Op::_Let, std::move(left), right); - globs.gen_ops_set_globs(code, loc); + local_lval.gen_ops_if_nonempty(code, loc); return right; } // [lhs] = rhs; it's un-tuple to N left vars @@ -143,29 +326,37 @@ static std::vector pre_compile_let(CodeBlob& code, AnyExprV lhs, AnyE std::vector right = pre_compile_expr(rhs, code); const TypeDataTypedTuple* inferred_tuple = rhs->inferred_type->try_as(); std::vector types_list = inferred_tuple->items; - std::vector rvect = {code.create_tmp_var(TypeDataTensor::create(std::move(types_list)), rhs->loc)}; + std::vector rvect = code.create_tmp_var(TypeDataTensor::create(std::move(types_list)), rhs->loc, "(unpack-tuple)"); code.emplace_back(lhs->loc, Op::_UnTuple, rvect, std::move(right)); - LValGlobs globs; - std::vector left = pre_compile_tensor(code, lhs->as()->get_items(), &globs); - code.on_var_modification(left, loc); + LValContext local_lval; + std::vector left = pre_compile_tensor(code, lhs->as()->get_items(), &local_lval); + vars_modification_watcher.trigger_callbacks(left, loc); code.emplace_back(loc, Op::_Let, std::move(left), rvect); - globs.gen_ops_set_globs(code, loc); + local_lval.gen_ops_if_nonempty(code, loc); return rvect; } + // small optimization: `var x = rhs` or `local_var = rhs` (90% cases), LValContext not needed actually + if (lhs->type == ast_local_var_lhs || (lhs->type == ast_reference && lhs->as()->sym->try_as())) { + std::vector right = pre_compile_expr(rhs, code); + std::vector left = pre_compile_expr(lhs, code); // effectively, local_var->ir_idx + vars_modification_watcher.trigger_callbacks(left, loc); + code.emplace_back(loc, Op::_Let, std::move(left), right); + return right; + } // lhs = rhs std::vector right = pre_compile_expr(rhs, code); - LValGlobs globs; - std::vector left = pre_compile_expr(lhs, code, &globs); - code.on_var_modification(left, loc); + LValContext local_lval; + std::vector left = pre_compile_expr(lhs, code, &local_lval); + vars_modification_watcher.trigger_callbacks(left, loc); code.emplace_back(loc, Op::_Let, std::move(left), right); - globs.gen_ops_set_globs(code, loc); + local_lval.gen_ops_if_nonempty(code, loc); return right; } -static std::vector gen_op_call(CodeBlob& code, TypePtr ret_type, SrcLocation here, - std::vector&& args_vars, const FunctionData* fun_ref) { - std::vector rvect = {code.create_tmp_var(ret_type, here)}; - Op& op = code.emplace_back(here, Op::_Call, rvect, std::move(args_vars), fun_ref); +static std::vector gen_op_call(CodeBlob& code, TypePtr ret_type, SrcLocation loc, + std::vector&& args_vars, const FunctionData* fun_ref, const char* debug_desc) { + std::vector rvect = code.create_tmp_var(ret_type, loc, debug_desc); + Op& op = code.emplace_back(loc, Op::_Call, rvect, std::move(args_vars), fun_ref); if (!fun_ref->is_marked_as_pure()) { op.set_impure_flag(); } @@ -173,37 +364,55 @@ static std::vector gen_op_call(CodeBlob& code, TypePtr ret_type, SrcL } -static std::vector process_symbol(SrcLocation loc, const Symbol* sym, CodeBlob& code, LValGlobs* lval_globs) { +static std::vector pre_compile_symbol(SrcLocation loc, const Symbol* sym, CodeBlob& code, LValContext* lval_ctx) { if (const auto* glob_ref = sym->try_as()) { - std::vector rvect = {code.create_tmp_var(glob_ref->declared_type, loc)}; - if (lval_globs) { - lval_globs->add_modified_glob(glob_ref, rvect[0]); - return rvect; + if (!lval_ctx) { + // `globalVar` is used for reading, just create local IR var to represent its value, Op GlobVar will fill it + // note, that global tensors are stored as a tuple an unpacked to N vars on read, N determined by declared_type + std::vector local_ir_idx = code.create_tmp_var(glob_ref->declared_type, loc, "(glob-var)"); + code.emplace_back(loc, Op::_GlobVar, local_ir_idx, std::vector{}, glob_ref); + return local_ir_idx; } else { - code.emplace_back(loc, Op::_GlobVar, rvect, std::vector{}, glob_ref); - return rvect; + // `globalVar = rhs` / `mutate globalVar` / `globalTuple.0 = rhs` + lval_ctx->register_lval(loc, glob_ref); + if (const std::vector* local_ir_idx = lval_ctx->exists_already_known_global(glob_ref)) { + return *local_ir_idx; // `f(mutate g.0, mutate g.1)`, then g will be read only once + } + std::vector local_ir_idx = code.create_tmp_var(glob_ref->declared_type, loc, "(glob-var)"); + if (lval_ctx->is_rval_inside_lval()) { // for `globalVar.0` "globalVar" is rvalue inside lvalue + // for `globalVar = rhs` don't read a global actually, but for `globalVar.0 = rhs` do + code.emplace_back(loc, Op::_GlobVar, local_ir_idx, std::vector{}, glob_ref); + } + lval_ctx->register_modified_global(glob_ref, local_ir_idx); + return local_ir_idx; } } if (const auto* const_ref = sym->try_as()) { if (const_ref->is_int_const()) { - std::vector rvect = {code.create_tmp_var(TypeDataInt::create(), loc)}; + std::vector rvect = code.create_tmp_var(TypeDataInt::create(), loc, "(glob-const)"); code.emplace_back(loc, Op::_IntConst, rvect, const_ref->as_int_const()); return rvect; } else { - std::vector rvect = {code.create_tmp_var(TypeDataSlice::create(), loc)}; + std::vector rvect = code.create_tmp_var(TypeDataSlice::create(), loc, "(glob-const)"); code.emplace_back(loc, Op::_SliceConst, rvect, const_ref->as_slice_const()); return rvect; } } if (const auto* fun_ref = sym->try_as()) { - std::vector rvect = {code.create_tmp_var(fun_ref->inferred_full_type, loc)}; + std::vector rvect = code.create_tmp_var(fun_ref->inferred_full_type, loc, "(glob-var-fun)"); code.emplace_back(loc, Op::_GlobVar, rvect, std::vector{}, fun_ref); return rvect; } if (const auto* var_ref = sym->try_as()) { - return {var_ref->idx}; +#ifdef TOLK_DEBUG + tolk_assert(static_cast(var_ref->ir_idx.size()) == var_ref->declared_type->calc_width_on_stack()); +#endif + if (lval_ctx) { + lval_ctx->register_lval(loc, var_ref); + } + return var_ref->ir_idx; } - throw Fatal("process_symbol"); + throw Fatal("pre_compile_symbol"); } static std::vector process_assign(V v, CodeBlob& code) { @@ -229,7 +438,7 @@ static std::vector process_binary_operator(V v, if (v->fun_ref) { // almost all operators, fun_ref was assigned at type inferring std::vector args_vars = pre_compile_tensor(code, {v->get_lhs(), v->get_rhs()}); - return gen_op_call(code, v->inferred_type, v->loc, std::move(args_vars), v->fun_ref); + return gen_op_call(code, v->inferred_type, v->loc, std::move(args_vars), v->fun_ref, "(binary-op)"); } if (t == tok_logical_and || t == tok_logical_or) { // do the following transformations: @@ -244,7 +453,7 @@ static std::vector process_binary_operator(V v, v_b_ne_0->mutate()->assign_fun_ref(lookup_global_symbol("_!=_")->as()); std::vector cond = pre_compile_expr(v->get_lhs(), code); tolk_assert(cond.size() == 1); - std::vector rvect = {code.create_tmp_var(v->inferred_type, v->loc)}; + std::vector rvect = code.create_tmp_var(v->inferred_type, v->loc, "(cond)"); Op& if_op = code.emplace_back(v->loc, Op::_If, cond); code.push_set_cur(if_op.block0); code.emplace_back(v->loc, Op::_Let, rvect, pre_compile_expr(t == tok_logical_and ? v_b_ne_0 : v_1, code)); @@ -260,13 +469,13 @@ static std::vector process_binary_operator(V v, static std::vector process_unary_operator(V v, CodeBlob& code) { std::vector args_vars = pre_compile_tensor(code, {v->get_rhs()}); - return gen_op_call(code, v->inferred_type, v->loc, std::move(args_vars), v->fun_ref); + return gen_op_call(code, v->inferred_type, v->loc, std::move(args_vars), v->fun_ref, "(unary-op)"); } static std::vector process_ternary_operator(V v, CodeBlob& code) { std::vector cond = pre_compile_expr(v->get_cond(), code); tolk_assert(cond.size() == 1); - std::vector rvect = {code.create_tmp_var(v->inferred_type, v->loc)}; + std::vector rvect = code.create_tmp_var(v->inferred_type, v->loc, "(cond)"); Op& if_op = code.emplace_back(v->loc, Op::_If, cond); code.push_set_cur(if_op.block0); code.emplace_back(v->get_when_true()->loc, Op::_Let, rvect, pre_compile_expr(v->get_when_true(), code)); @@ -277,13 +486,67 @@ static std::vector process_ternary_operator(V v return rvect; } -static std::vector process_dot_access(V v, CodeBlob& code, LValGlobs* lval_globs) { +static std::vector process_dot_access(V v, CodeBlob& code, LValContext* lval_ctx) { // it's NOT a method call `t.tupleSize()` (since such cases are handled by process_function_call) // it's `t.0`, `getUser().id`, and `t.tupleSize` (as a reference, not as a call) - // currently, nothing except a global function can be a target of dot access - const FunctionData* fun_ref = v->target; + if (!v->is_target_fun_ref()) { + TypePtr obj_type = v->get_obj()->inferred_type; + int index_at = std::get(v->target); + // `tensorVar.0`; since a tensor of N elems are N vars on a stack actually, calculate offset + if (const auto* t_tensor = obj_type->try_as()) { + if (lval_ctx) lval_ctx->register_lval(v->loc, v); + if (lval_ctx) lval_ctx->enter_rval_inside_lval(); + std::vector lhs_vars = pre_compile_expr(v->get_obj(), code, lval_ctx); + if (lval_ctx) lval_ctx->exit_rval_inside_lval(); + int stack_width = t_tensor->items[index_at]->calc_width_on_stack(); + int stack_offset = 0; + for (int i = 0; i < index_at; ++i) { + stack_offset += t_tensor->items[i]->calc_width_on_stack(); + } + return {lhs_vars.begin() + stack_offset, lhs_vars.begin() + stack_offset + stack_width}; + } + // `tupleVar.0`; not to mess up, separate rvalue and lvalue cases + if (obj_type->try_as() || obj_type->try_as()) { + if (!lval_ctx) { + // `tupleVar.0` as rvalue: the same as "tupleAt(tupleVar, 0)" written in terms of IR vars + std::vector tuple_ir_idx = pre_compile_expr(v->get_obj(), code); + std::vector index_ir_idx = code.create_tmp_var(TypeDataInt::create(), v->get_identifier()->loc, "(tuple-idx)"); + code.emplace_back(v->loc, Op::_IntConst, index_ir_idx, td::make_refint(index_at)); + std::vector field_ir_idx = code.create_tmp_var(v->inferred_type, v->loc, "(tuple-field)"); + tolk_assert(tuple_ir_idx.size() == 1 && field_ir_idx.size() == 1); // tuples contain only 1-slot values + const FunctionData* builtin_sym = lookup_global_symbol("tupleAt")->as(); + code.emplace_back(v->loc, Op::_Call, field_ir_idx, std::vector{tuple_ir_idx[0], index_ir_idx[0]}, builtin_sym); + return field_ir_idx; + } else { + // `tupleVar.0 = rhs`: finally "tupleSetAt(tupleVar, rhs, 0)" will be done + uint64_t hash = lval_ctx->register_lval(v->loc, v); + if (const var_idx_t* field_ir_idx = lval_ctx->exists_already_known_tuple_index(hash)) { + return {*field_ir_idx}; // `(t.0.0, t.0.1) = rhs`, then "t.0" will be read (tupleAt) once + } + lval_ctx->enter_rval_inside_lval(); + std::vector tuple_ir_idx = pre_compile_expr(v->get_obj(), code, lval_ctx); + lval_ctx->exit_rval_inside_lval(); + std::vector index_ir_idx = code.create_tmp_var(TypeDataInt::create(), v->get_identifier()->loc, "(tuple-idx)"); + code.emplace_back(v->loc, Op::_IntConst, index_ir_idx, td::make_refint(index_at)); + std::vector field_ir_idx = code.create_tmp_var(v->inferred_type, v->loc, "(tuple-field)"); + if (lval_ctx->is_rval_inside_lval()) { // for `t.0.1 = rhs` "t.0" is rvalue inside lvalue + // for `t.0 = rhs` don't call tupleAt, but for `t.0.1 = rhs` do for t.0 (still don't for t.0.1) + const FunctionData* builtin_sym = lookup_global_symbol("tupleAt")->as(); + code.emplace_back(v->loc, Op::_Call, field_ir_idx, std::vector{tuple_ir_idx[0], index_ir_idx[0]}, builtin_sym); + } + lval_ctx->register_modified_tuple_index(hash, tuple_ir_idx[0], index_ir_idx[0], field_ir_idx[0]); + vars_modification_watcher.trigger_callbacks(tuple_ir_idx, v->loc); + return field_ir_idx; + } + } + tolk_assert(false); + } + + // okay, v->target refs a function, like `obj.method`, filled at type inferring + // (currently, nothing except a global function can be referenced, no object-scope methods exist) + const FunctionData* fun_ref = std::get(v->target); tolk_assert(fun_ref); - return process_symbol(v->loc, fun_ref, code, lval_globs); + return pre_compile_symbol(v->loc, fun_ref, code, lval_ctx); } static std::vector process_function_call(V v, CodeBlob& code) { @@ -299,7 +562,7 @@ static std::vector process_function_call(V v, Code std::vector tfunc = pre_compile_expr(v->get_callee(), code); tolk_assert(tfunc.size() == 1); args_vars.push_back(tfunc[0]); - std::vector rvect = {code.create_tmp_var(v->inferred_type, v->loc)}; + std::vector rvect = code.create_tmp_var(v->inferred_type, v->loc, "(call-ind)"); Op& op = code.emplace_back(v->loc, Op::_CallInd, rvect, std::move(args_vars)); op.set_impure_flag(); return rvect; @@ -344,28 +607,28 @@ static std::vector process_function_call(V v, Code for (const std::vector& list : vars_per_arg) { args_vars.insert(args_vars.end(), list.cbegin(), list.cend()); } - std::vector rvect_apply = gen_op_call(code, op_call_type, v->loc, std::move(args_vars), fun_ref); + std::vector rvect_apply = gen_op_call(code, op_call_type, v->loc, std::move(args_vars), fun_ref, "(fun-call)"); if (fun_ref->has_mutate_params()) { - LValGlobs local_globs; + LValContext local_lval; std::vector left; for (int i = 0; i < delta_self + v->get_num_args(); ++i) { if (fun_ref->parameters[i].is_mutate_parameter()) { AnyExprV arg_i = obj_leftmost && i == 0 ? obj_leftmost : args[i]; tolk_assert(arg_i->is_lvalue || i == 0); if (arg_i->is_lvalue) { - std::vector ith_var_idx = pre_compile_expr(arg_i, code, &local_globs); + std::vector ith_var_idx = pre_compile_expr(arg_i, code, &local_lval); left.insert(left.end(), ith_var_idx.begin(), ith_var_idx.end()); } else { left.insert(left.end(), vars_per_arg[0].begin(), vars_per_arg[0].end()); } } } - std::vector rvect = {code.create_tmp_var(real_ret_type, v->loc)}; - left.push_back(rvect[0]); - code.on_var_modification(left, v->loc); + std::vector rvect = code.create_tmp_var(real_ret_type, v->loc, "(fun-call)"); + left.insert(left.end(), rvect.begin(), rvect.end()); + vars_modification_watcher.trigger_callbacks(left, v->loc); code.emplace_back(v->loc, Op::_Let, std::move(left), rvect_apply); - local_globs.gen_ops_set_globs(code, v->loc); + local_lval.gen_ops_if_nonempty(code, v->loc); rvect_apply = rvect; } @@ -380,29 +643,29 @@ static std::vector process_function_call(V v, Code return rvect_apply; } -static std::vector process_tensor(V v, CodeBlob& code, LValGlobs* lval_globs) { - return pre_compile_tensor(code, v->get_items(), lval_globs); +static std::vector process_tensor(V v, CodeBlob& code, LValContext* lval_ctx) { + return pre_compile_tensor(code, v->get_items(), lval_ctx); } -static std::vector process_typed_tuple(V v, CodeBlob& code, LValGlobs* lval_globs) { - if (lval_globs) { // todo some time, make "var (a, [b,c]) = (1, [2,3])" work +static std::vector process_typed_tuple(V v, CodeBlob& code, LValContext* lval_ctx) { + if (lval_ctx) { // todo some time, make "var (a, [b,c]) = (1, [2,3])" work v->error("[...] can not be used as lvalue here"); } - std::vector left = std::vector{code.create_tmp_var(v->inferred_type, v->loc)}; - std::vector right = pre_compile_tensor(code, v->get_items()); + std::vector left = code.create_tmp_var(v->inferred_type, v->loc, "(pack-tuple)"); + std::vector right = pre_compile_tensor(code, v->get_items(), lval_ctx); code.emplace_back(v->loc, Op::_Tuple, left, std::move(right)); return left; } static std::vector process_int_const(V v, CodeBlob& code) { - std::vector rvect = {code.create_tmp_var(v->inferred_type, v->loc)}; + std::vector rvect = code.create_tmp_var(v->inferred_type, v->loc, "(int-const)"); code.emplace_back(v->loc, Op::_IntConst, rvect, v->intval); return rvect; } static std::vector process_string_const(V v, CodeBlob& code) { ConstantValue value = eval_const_init_value(v); - std::vector rvect = {code.create_tmp_var(v->inferred_type, v->loc)}; + std::vector rvect = code.create_tmp_var(v->inferred_type, v->loc, "(str-const)"); if (value.is_int()) { code.emplace_back(v->loc, Op::_IntConst, rvect, value.as_int()); } else { @@ -413,22 +676,22 @@ static std::vector process_string_const(V v, CodeBl static std::vector process_bool_const(V v, CodeBlob& code) { const FunctionData* builtin_sym = lookup_global_symbol(v->bool_val ? "__true" : "__false")->as(); - return gen_op_call(code, v->inferred_type, v->loc, {}, builtin_sym); + return gen_op_call(code, v->inferred_type, v->loc, {}, builtin_sym, "(bool-const)"); } static std::vector process_null_keyword(V v, CodeBlob& code) { const FunctionData* builtin_sym = lookup_global_symbol("__null")->as(); - return gen_op_call(code, v->inferred_type, v->loc, {}, builtin_sym); + return gen_op_call(code, v->inferred_type, v->loc, {}, builtin_sym, "(null-literal)"); } static std::vector process_local_var(V v, CodeBlob& code) { if (v->marked_as_redef) { - return process_symbol(v->loc, v->var_ref, code, nullptr); + return pre_compile_symbol(v->loc, v->var_ref, code, nullptr); } - tolk_assert(v->var_ref->idx == -1); - v->var_ref->mutate()->assign_idx(code.create_var(v->inferred_type, v->var_ref, v->loc)); - return {v->var_ref->idx}; + tolk_assert(v->var_ref->ir_idx.empty()); + v->var_ref->mutate()->assign_ir_idx(code.create_var(v->inferred_type, v->loc, v->var_ref->name)); + return v->var_ref->ir_idx; } static std::vector process_local_vars_declaration(V, CodeBlob&) { @@ -439,13 +702,13 @@ static std::vector process_local_vars_declaration(V process_underscore(V v, CodeBlob& code) { // when _ is used as left side of assignment, like `(cs, _) = cs.loadAndReturn()` - return {code.create_tmp_var(v->inferred_type, v->loc)}; + return code.create_tmp_var(v->inferred_type, v->loc, "(underscore)"); } -std::vector pre_compile_expr(AnyExprV v, CodeBlob& code, LValGlobs* lval_globs) { +std::vector pre_compile_expr(AnyExprV v, CodeBlob& code, LValContext* lval_ctx) { switch (v->type) { case ast_reference: - return process_symbol(v->loc, v->as()->sym, code, lval_globs); + return pre_compile_symbol(v->loc, v->as()->sym, code, lval_ctx); case ast_assign: return process_assign(v->as(), code); case ast_set_assign: @@ -457,17 +720,17 @@ std::vector pre_compile_expr(AnyExprV v, CodeBlob& code, LValGlobs* l case ast_ternary_operator: return process_ternary_operator(v->as(), code); case ast_cast_as_operator: - return pre_compile_expr(v->as()->get_expr(), code, lval_globs); + return pre_compile_expr(v->as()->get_expr(), code, lval_ctx); case ast_dot_access: - return process_dot_access(v->as(), code, lval_globs); + return process_dot_access(v->as(), code, lval_ctx); case ast_function_call: return process_function_call(v->as(), code); case ast_parenthesized_expression: - return pre_compile_expr(v->as()->get_expr(), code, lval_globs); + return pre_compile_expr(v->as()->get_expr(), code, lval_ctx); case ast_tensor: - return process_tensor(v->as(), code, lval_globs); + return process_tensor(v->as(), code, lval_ctx); case ast_typed_tuple: - return process_typed_tuple(v->as(), code, lval_globs); + return process_typed_tuple(v->as(), code, lval_ctx); case ast_int_const: return process_int_const(v->as(), code); case ast_string_const: @@ -510,14 +773,14 @@ static void process_assert_statement(V v, CodeBlob& code) const FunctionData* builtin_sym = lookup_global_symbol("__throw_if_unless")->as(); std::vector args_vars = pre_compile_tensor(code, args); - gen_op_call(code, TypeDataVoid::create(), v->loc, std::move(args_vars), builtin_sym); + gen_op_call(code, TypeDataVoid::create(), v->loc, std::move(args_vars), builtin_sym, "(throw-call)"); } static void process_catch_variable(AnyExprV v_catch_var, CodeBlob& code) { if (auto v_ref = v_catch_var->try_as(); v_ref && v_ref->sym) { // not underscore const LocalVarData* var_ref = v_ref->sym->as(); - tolk_assert(var_ref->idx == -1); - var_ref->mutate()->assign_idx(code.create_var(v_catch_var->inferred_type, var_ref, v_catch_var->loc)); + tolk_assert(var_ref->ir_idx.empty()); + var_ref->mutate()->assign_ir_idx(code.create_var(v_catch_var->inferred_type, v_catch_var->loc, var_ref->name)); } } @@ -616,25 +879,24 @@ static void process_throw_statement(V v, CodeBlob& code) { if (v->has_thrown_arg()) { const FunctionData* builtin_sym = lookup_global_symbol("__throw_arg")->as(); std::vector args_vars = pre_compile_tensor(code, {v->get_thrown_arg(), v->get_thrown_code()}); - gen_op_call(code, TypeDataVoid::create(), v->loc, std::move(args_vars), builtin_sym); + gen_op_call(code, TypeDataVoid::create(), v->loc, std::move(args_vars), builtin_sym, "(throw-call)"); } else { const FunctionData* builtin_sym = lookup_global_symbol("__throw")->as(); std::vector args_vars = pre_compile_tensor(code, {v->get_thrown_code()}); - gen_op_call(code, TypeDataVoid::create(), v->loc, std::move(args_vars), builtin_sym); + gen_op_call(code, TypeDataVoid::create(), v->loc, std::move(args_vars), builtin_sym, "(throw-call)"); } } static void process_return_statement(V v, CodeBlob& code) { std::vector return_vars = v->has_return_value() ? pre_compile_expr(v->get_return_value(), code) : std::vector{}; if (code.fun_ref->does_return_self()) { - tolk_assert(return_vars.size() == 1); return_vars = {}; } if (code.fun_ref->has_mutate_params()) { std::vector mutated_vars; for (const LocalVarData& p_sym: code.fun_ref->parameters) { if (p_sym.is_mutate_parameter()) { - mutated_vars.push_back(p_sym.idx); + mutated_vars.insert(mutated_vars.end(), p_sym.ir_idx.begin(), p_sym.ir_idx.end()); } } return_vars.insert(return_vars.begin(), mutated_vars.begin(), mutated_vars.end()); @@ -647,7 +909,7 @@ static void append_implicit_return_statement(SrcLocation loc_end, CodeBlob& code if (code.fun_ref->has_mutate_params()) { for (const LocalVarData& p_sym: code.fun_ref->parameters) { if (p_sym.is_mutate_parameter()) { - mutated_vars.push_back(p_sym.idx); + mutated_vars.insert(mutated_vars.end(), p_sym.ir_idx.begin(), p_sym.ir_idx.end()); } } } @@ -685,11 +947,23 @@ void process_any_statement(AnyV v, CodeBlob& code) { static void convert_function_body_to_CodeBlob(const FunctionData* fun_ref, FunctionBodyCode* code_body) { auto v_body = fun_ref->ast_root->as()->get_body()->as(); CodeBlob* blob = new CodeBlob{fun_ref->name, fun_ref->loc, fun_ref}; - FormalArgList legacy_arg_list; - for (const LocalVarData& param : fun_ref->parameters) { - legacy_arg_list.emplace_back(param.declared_type, ¶m, param.loc); + + std::vector rvect_import; + int total_arg_width = 0; + for (int i = 0; i < fun_ref->get_num_params(); ++i) { + total_arg_width += fun_ref->parameters[i].declared_type->calc_width_on_stack(); + } + rvect_import.reserve(total_arg_width); + + for (int i = 0; i < fun_ref->get_num_params(); ++i) { + const LocalVarData& param_i = fun_ref->parameters[i]; + std::vector ir_idx = blob->create_var(param_i.declared_type, param_i.loc, param_i.name); + rvect_import.insert(rvect_import.end(), ir_idx.begin(), ir_idx.end()); + param_i.mutate()->assign_ir_idx(std::move(ir_idx)); } - blob->import_params(std::move(legacy_arg_list)); + blob->emplace_back(fun_ref->loc, Op::_Import, rvect_import); + blob->in_var_cnt = blob->var_cnt; + tolk_assert(blob->var_cnt == total_arg_width); for (AnyV item : v_body->get_items()) { process_any_statement(item, *blob); @@ -700,6 +974,7 @@ static void convert_function_body_to_CodeBlob(const FunctionData* fun_ref, Funct blob->close_blk(v_body->loc_end); code_body->set_code(blob); + tolk_assert(vars_modification_watcher.empty()); } static void convert_asm_body_to_AsmOp(const FunctionData* fun_ref, FunctionBodyAsm* asm_body) { diff --git a/tolk/pipe-check-rvalue-lvalue.cpp b/tolk/pipe-check-rvalue-lvalue.cpp index 038b09991..a824cc5d0 100644 --- a/tolk/pipe-check-rvalue-lvalue.cpp +++ b/tolk/pipe-check-rvalue-lvalue.cpp @@ -38,7 +38,7 @@ static void fire_error_cannot_be_used_as_lvalue(AnyV v, const std::string& detai GNU_ATTRIBUTE_NORETURN GNU_ATTRIBUTE_COLD static void fire_error_modifying_immutable_variable(AnyExprV v, const LocalVarData* var_ref) { - if (var_ref->idx == 0 && var_ref->name == "self") { + if (var_ref->param_idx == 0 && var_ref->name == "self") { v->error("modifying `self`, which is immutable by default; probably, you want to declare `mutate self`"); } else { v->error("modifying immutable variable `" + var_ref->name + "`"); @@ -123,8 +123,8 @@ class CheckRValueLvalueVisitor final : public ASTVisitorFunctionBody { void visit(V v) override { // a reference to a method used as rvalue, like `var v = t.tupleAt` - if (const FunctionData* fun_ref = v->target; v->is_rvalue) { - validate_function_used_as_noncall(v, fun_ref); + if (v->is_rvalue && v->is_target_fun_ref()) { + validate_function_used_as_noncall(v, std::get(v->target)); } } diff --git a/tolk/pipe-generate-fif-output.cpp b/tolk/pipe-generate-fif-output.cpp index 9092e5647..7ef6ba7bc 100644 --- a/tolk/pipe-generate-fif-output.cpp +++ b/tolk/pipe-generate-fif-output.cpp @@ -54,11 +54,6 @@ static void generate_output_func(const FunctionData* fun_ref) { std::cerr << "after prune_unreachable: \n"; code->print(std::cerr, 0); } - code->split_vars(true); - if (G.is_verbosity(5)) { - std::cerr << "after split_vars: \n"; - code->print(std::cerr, 0); - } for (int i = 0; i < 8; i++) { code->compute_used_code_vars(); if (G.is_verbosity(4)) { diff --git a/tolk/pipe-infer-types-and-calls.cpp b/tolk/pipe-infer-types-and-calls.cpp index d8a7d41be..ba5f77a78 100644 --- a/tolk/pipe-infer-types-and-calls.cpp +++ b/tolk/pipe-infer-types-and-calls.cpp @@ -124,6 +124,19 @@ static void fire_error_cannot_apply_operator(SrcLocation loc, std::string_view o throw ParseError(loc, "can not apply operator `" + op + "` to " + to_string(lhs->inferred_type) + " and " + to_string(rhs->inferred_type)); } +// fire an error on `untypedTupleVar.0` when used without a hint +GNU_ATTRIBUTE_NORETURN GNU_ATTRIBUTE_COLD +static void fire_error_cannot_deduce_untyped_tuple_access(SrcLocation loc, int index) { + std::string idx_access = "." + std::to_string(index); + throw ParseError(loc, "can not deduce type of `" + idx_access + "`; either assign it to variable like `var c: int = " + idx_access + "` or cast the result like `" + idx_access + " as int`"); +} + +// fire an error on `untypedTupleVar.0` when inferred as (int,int), or `[int, (int,int)]`, or other non-1 width in a tuple +GNU_ATTRIBUTE_NORETURN GNU_ATTRIBUTE_COLD +static void fire_error_cannot_put_non1_stack_width_arg_to_tuple(SrcLocation loc, TypePtr inferred_type) { + throw ParseError(loc, "can not put " + to_string(inferred_type) + " into a tuple, because it occupies " + std::to_string(inferred_type->calc_width_on_stack()) + " stack slots in TVM, not 1"); +} + // check correctness of called arguments counts and their type matching static void check_function_arguments(const FunctionData* fun_ref, V v, AnyExprV lhs_of_dot_call) { int delta_self = lhs_of_dot_call ? 1 : 0; @@ -240,6 +253,24 @@ class TypeInferringUnifyStrategy { TypePtr get_result() const { return unified_result; } }; +// handle __expect_type(expr, "type") call +// this is used in compiler tests +GNU_ATTRIBUTE_NOINLINE GNU_ATTRIBUTE_COLD +static void handle_possible_compiler_internal_call(const FunctionData* current_function, V v) { + const FunctionData* fun_ref = v->fun_maybe; + tolk_assert(fun_ref && fun_ref->is_builtin_function()); + static_cast(current_function); + + if (fun_ref->name == "__expect_type") { + tolk_assert(v->get_num_args() == 2); + TypePtr expected_type = parse_type_from_string(v->get_arg(1)->get_expr()->as()->str_val); + TypePtr expr_type = v->get_arg(0)->inferred_type; + if (expected_type != expr_type) { + v->error("__expect_type failed: expected " + to_string(expected_type) + ", got " + to_string(expr_type)); + } + } +} + /* * This class handles all types of AST vertices and traverses them, filling all AnyExprV::inferred_type. * Note, that it isn't derived from ASTVisitor, it has manual `switch` over all existing vertex types. @@ -448,6 +479,22 @@ class InferCheckTypesAndCallsAndFieldsVisitor final { return TypeDataTypedTuple::create(std::move(sub_hints)); } + // `a.0 = rhs` / `b.1.0 = rhs` (remember, its target is not assigned yet) + if (auto lhs_dot = lhs->try_as()) { + TypePtr obj_hint = calc_hint_from_assignment_lhs(lhs_dot->get_obj()); + std::string_view field_name = lhs_dot->get_field_name(); + if (field_name[0] >= '0' && field_name[0] <= '9') { + int index_at = std::stoi(std::string(field_name)); + if (const auto* t_tensor = obj_hint->try_as(); t_tensor && index_at < t_tensor->size()) { + return t_tensor->items[index_at]; + } + if (const auto* t_tuple = obj_hint->try_as(); t_tuple && index_at < t_tuple->size()) { + return t_tuple->items[index_at]; + } + } + return TypeDataUnknown::create(); + } + return TypeDataUnknown::create(); } @@ -544,8 +591,8 @@ class InferCheckTypesAndCallsAndFieldsVisitor final { return; } - // here is something strange and unhandled, like `f() = rhs` - // it will fail on later compilation steps (like rvalue/lvalue checks), but type inferring should pass + // here is something unhandled like `a.0 = rhs`, run regular inferring on rhs + // for something strange like `f() = rhs` type inferring will pass, but will fail later infer_any_expr(lhs, rhs_type); if (!lhs->inferred_type->can_rhs_be_assigned(rhs_type)) { err_loc->error("can not assign " + to_string(rhs_type) + " to " + to_string(lhs)); @@ -821,25 +868,56 @@ class InferCheckTypesAndCallsAndFieldsVisitor final { // it's NOT a method call `t.tupleSize()` (since such cases are handled by infer_function_call) // it's `t.0`, `getUser().id`, and `t.tupleSize` (as a reference, not as a call) infer_any_expr(v->get_obj()); + TypePtr obj_type = v->get_obj()->inferred_type; // our goal is to fill v->target knowing type of obj V v_ident = v->get_identifier(); // field/method name vertex V v_instantiationTs = v->get_instantiationTs(); std::string_view field_name = v_ident->name; - // for now, Tolk doesn't have structures, properties, and object-scoped methods - // so, only `t.tupleSize` is allowed, look up a global function - const Symbol* sym = lookup_global_symbol(field_name); - if (!sym) { - v_ident->error("undefined symbol `" + static_cast(field_name) + "`"); + // it can be indexed access (`tensorVar.0`, `tupleVar.1`) or a method (`t.tupleSize`) + // at first, check for indexed access + if (field_name[0] >= '0' && field_name[0] <= '9') { + int index_at = std::stoi(std::string(field_name)); + if (const auto* t_tensor = obj_type->try_as()) { + if (index_at >= t_tensor->size()) { + v_ident->error("invalid tensor index, expected 0.." + std::to_string(t_tensor->items.size() - 1)); + } + v->mutate()->assign_target(index_at); + assign_inferred_type(v, t_tensor->items[index_at]); + return; + } + if (const auto* t_tuple = obj_type->try_as()) { + if (index_at >= t_tuple->size()) { + v_ident->error("invalid tuple index, expected 0.." + std::to_string(t_tuple->items.size() - 1)); + } + v->mutate()->assign_target(index_at); + assign_inferred_type(v, t_tuple->items[index_at]); + return; + } + if (obj_type->try_as()) { + if (hint == nullptr) { + fire_error_cannot_deduce_untyped_tuple_access(v->loc, index_at); + } + if (hint->calc_width_on_stack() != 1) { + fire_error_cannot_put_non1_stack_width_arg_to_tuple(v->loc, hint); + } + v->mutate()->assign_target(index_at); + assign_inferred_type(v, hint); + return; + } + v_ident->error("type " + to_string(obj_type) + " is not indexable"); } - const FunctionData* fun_ref = sym->try_as(); + + // for now, Tolk doesn't have fields and object-scoped methods; `t.tupleSize` is a global function `tupleSize` + const Symbol* sym = lookup_global_symbol(field_name); + const FunctionData* fun_ref = sym ? sym->try_as() : nullptr; if (!fun_ref) { - v_ident->error("referencing a non-function"); + v_ident->error("non-existing field `" + static_cast(field_name) + "` of type " + to_string(obj_type)); } // `t.tupleSize` is ok, `cs.tupleSize` not - if (!fun_ref->parameters[0].declared_type->can_rhs_be_assigned(v->get_obj()->inferred_type)) { - v_ident->error("referencing a method for " + to_string(fun_ref->parameters[0]) + " with an object of type " + to_string(v->get_obj())); + if (!fun_ref->parameters[0].declared_type->can_rhs_be_assigned(obj_type)) { + v_ident->error("referencing a method for " + to_string(fun_ref->parameters[0]) + " with object of type " + to_string(obj_type)); } if (fun_ref->is_generic_function() && !v_instantiationTs) { @@ -878,21 +956,24 @@ class InferCheckTypesAndCallsAndFieldsVisitor final { } else if (auto v_dot = callee->try_as()) { // `obj.someMethod()` / `obj.someMethod()` / `getF().someMethod()` / `obj.SOME_CONST()` + // note, that dot_obj->target is not filled yet, since callee was not inferred yet delta_self = 1; dot_obj = v_dot->get_obj(); v_instantiationTs = v_dot->get_instantiationTs(); // present for `obj.someMethod()` infer_any_expr(dot_obj); - // for now, Tolk doesn't have object-scoped methods, so method resolving doesn't depend on obj type - // (in other words, `globalFunction(a)` = `a.globalFunction()`) - std::string_view method_name = v_dot->get_field_name(); - const Symbol* sym = lookup_global_symbol(method_name); - if (!sym) { - v_dot->get_identifier()->error("undefined symbol `" + static_cast(method_name) + "`"); - } - fun_ref = sym->try_as(); - if (!fun_ref) { - v_dot->get_identifier()->error("calling a non-function"); + // it can be indexed access (`tensorVar.0()`, `tupleVar.1()`) or a method (`t.tupleSize()`) + std::string_view field_name = v_dot->get_field_name(); + if (field_name[0] >= '0' && field_name[0] <= '9') { + // indexed access `ab.2()`, then treat `ab.2` just like an expression, fun_ref remains nullptr + // infer_dot_access() will be called for a callee, it will check type, index correctness, etc. + } else { + // for now, Tolk doesn't have fields and object-scoped methods; `t.tupleSize` is a global function `tupleSize` + const Symbol* sym = lookup_global_symbol(field_name); + fun_ref = sym ? sym->try_as() : nullptr; + if (!fun_ref) { + v_dot->get_identifier()->error("non-existing method `" + static_cast(field_name) + "` of type " + to_string(dot_obj)); + } } } else { @@ -908,7 +989,7 @@ class InferCheckTypesAndCallsAndFieldsVisitor final { assign_inferred_type(arg_i, arg_i->get_expr()); } - // handle `local_var()` / `getF()()` / `5()` / `SOME_CONST()` / `obj.method()()()` + // handle `local_var()` / `getF()()` / `5()` / `SOME_CONST()` / `obj.method()()()` / `tensorVar.0()` if (!fun_ref) { // treat callee like a usual expression, which must have "callable" inferred type infer_any_expr(callee); @@ -974,6 +1055,9 @@ class InferCheckTypesAndCallsAndFieldsVisitor final { TypePtr inferred_type = dot_obj && fun_ref->does_return_self() ? dot_obj->inferred_type : fun_ref->inferred_return_type; assign_inferred_type(v, inferred_type); assign_inferred_type(callee, fun_ref->inferred_full_type); + if (fun_ref->is_builtin_function() && fun_ref->name[0] == '_') { + handle_possible_compiler_internal_call(current_function, v); + } // note, that mutate params don't affect typing, they are handled when converting to IR } @@ -996,6 +1080,9 @@ class InferCheckTypesAndCallsAndFieldsVisitor final { for (int i = 0; i < v->size(); ++i) { AnyExprV item = v->get_item(i); infer_any_expr(item, tuple_hint && i < tuple_hint->size() ? tuple_hint->items[i] : nullptr); + if (item->inferred_type->calc_width_on_stack() != 1) { + fire_error_cannot_put_non1_stack_width_arg_to_tuple(v->get_item(i)->loc, item->inferred_type); + } types_list.emplace_back(item->inferred_type); } assign_inferred_type(v, TypeDataTypedTuple::create(std::move(types_list))); diff --git a/tolk/pipe-refine-lvalue-for-mutate.cpp b/tolk/pipe-refine-lvalue-for-mutate.cpp index 45dd3a94f..540d74137 100644 --- a/tolk/pipe-refine-lvalue-for-mutate.cpp +++ b/tolk/pipe-refine-lvalue-for-mutate.cpp @@ -38,11 +38,11 @@ static void fire_error_invalid_mutate_arg_passed(AnyExprV v, const FunctionData* std::string arg_str(arg_expr->type == ast_reference ? arg_expr->as()->get_name() : "obj"); // case: `loadInt(cs, 32)`; suggest: `cs.loadInt(32)` - if (p_sym.is_mutate_parameter() && !arg_passed_as_mutate && !called_as_method && p_sym.idx == 0 && fun_ref->does_accept_self()) { + if (p_sym.is_mutate_parameter() && !arg_passed_as_mutate && !called_as_method && p_sym.param_idx == 0 && fun_ref->does_accept_self()) { v->error("`" + fun_ref->name + "` is a mutating method; consider calling `" + arg_str + "." + fun_ref->name + "()`, not `" + fun_ref->name + "(" + arg_str + ")`"); } // case: `cs.mutating_function()`; suggest: `mutating_function(mutate cs)` or make it a method - if (p_sym.is_mutate_parameter() && called_as_method && p_sym.idx == 0 && !fun_ref->does_accept_self()) { + if (p_sym.is_mutate_parameter() && called_as_method && p_sym.param_idx == 0 && !fun_ref->does_accept_self()) { v->error("function `" + fun_ref->name + "` mutates parameter `" + p_sym.name + "`; consider calling `" + fun_ref->name + "(mutate " + arg_str + ")`, not `" + arg_str + "." + fun_ref->name + "`(); alternatively, rename parameter to `self` to make it a method"); } // case: `mutating_function(arg)`; suggest: `mutate arg` diff --git a/tolk/symtable.cpp b/tolk/symtable.cpp index 918fdab33..c56dc6ed8 100644 --- a/tolk/symtable.cpp +++ b/tolk/symtable.cpp @@ -93,8 +93,8 @@ void GlobalConstData::assign_resolved_type(TypePtr declared_type) { this->declared_type = declared_type; } -void LocalVarData::assign_idx(int idx) { - this->idx = idx; +void LocalVarData::assign_ir_idx(std::vector&& ir_idx) { + this->ir_idx = std::move(ir_idx); } void LocalVarData::assign_resolved_type(TypePtr declared_type) { diff --git a/tolk/symtable.h b/tolk/symtable.h index 3cda24edf..27753cebd 100644 --- a/tolk/symtable.h +++ b/tolk/symtable.h @@ -59,20 +59,23 @@ struct LocalVarData final : Symbol { TypePtr declared_type; // either at declaration `var x:int`, or if omitted, from assigned value `var x=2` int flags; - int idx; + int param_idx; // 0...N for function parameters, -1 for local vars + std::vector ir_idx; - LocalVarData(std::string name, SrcLocation loc, TypePtr declared_type, int flags, int idx) + LocalVarData(std::string name, SrcLocation loc, TypePtr declared_type, int flags, int param_idx) : Symbol(std::move(name), loc) , declared_type(declared_type) , flags(flags) - , idx(idx) { + , param_idx(param_idx) { } + bool is_parameter() const { return param_idx >= 0; } + bool is_immutable() const { return flags & flagImmutable; } bool is_mutate_parameter() const { return flags & flagMutateParameter; } LocalVarData* mutate() const { return const_cast(this); } - void assign_idx(int idx); + void assign_ir_idx(std::vector&& ir_idx); void assign_resolved_type(TypePtr declared_type); void assign_inferred_type(TypePtr inferred_type); }; diff --git a/tolk/tolk-version.h b/tolk/tolk-version.h index 7eaf55a7a..843260129 100644 --- a/tolk/tolk-version.h +++ b/tolk/tolk-version.h @@ -18,6 +18,6 @@ namespace tolk { -constexpr const char* TOLK_VERSION = "0.7.0"; +constexpr const char* TOLK_VERSION = "0.8.0"; } // namespace tolk diff --git a/tolk/tolk.h b/tolk/tolk.h index 5ec4d3e08..4086d7f78 100644 --- a/tolk/tolk.h +++ b/tolk/tolk.h @@ -44,23 +44,23 @@ typedef int var_idx_t; typedef int const_idx_t; struct TmpVar { - TypePtr v_type; - var_idx_t idx; - const LocalVarData* v_sym; // points to var defined in code; nullptr for implicitly created tmp vars - int coord; - SrcLocation where; - std::vector> on_modification; + var_idx_t ir_idx; // every var in IR represents 1 stack slot + TypePtr v_type; // calc_width_on_stack() is 1 + std::string name; // "x" for vars originated from user sources; "x.0" for tensor components; empty for implicitly created tmp vars + SrcLocation loc; // location of var declaration in sources or where a tmp var was originated +#ifdef TOLK_DEBUG + const char* desc = nullptr; // "origin" of tmp var, for debug output like `'15 (binary-op) '16 (glob-var)` +#endif - TmpVar(var_idx_t _idx, TypePtr type, const LocalVarData* v_sym, SrcLocation loc) - : v_type(type) - , idx(_idx) - , v_sym(v_sym) - , coord(0) - , where(loc) { + TmpVar(var_idx_t ir_idx, TypePtr v_type, std::string name, SrcLocation loc) + : ir_idx(ir_idx) + , v_type(v_type) + , name(std::move(name)) + , loc(loc) { } - void show(std::ostream& os, int omit_idx = 0) const; - void dump(std::ostream& os) const; + void show_as_stack_comment(std::ostream& os) const; + void show(std::ostream& os) const; }; struct VarDescr { @@ -345,8 +345,6 @@ struct Op { void show_var_list(std::ostream& os, const std::vector& list, const std::vector& vars) const; static void show_block(std::ostream& os, const Op* block, const std::vector& vars, std::string pfx = "", int mode = 0); - void split_vars(const std::vector& vars); - static void split_var_list(std::vector& var_list, const std::vector& vars); bool compute_used_vars(const CodeBlob& code, bool edit); bool std_compute_used_vars(bool disabled = false); bool set_var_info(const VarDescrList& new_var_info); @@ -385,9 +383,6 @@ inline ListIterator end(const Op* op_list) { return ListIterator{}; } -typedef std::tuple FormalArg; -typedef std::vector FormalArgList; - struct AsmOpList; struct FunctionBodyCode { @@ -609,7 +604,6 @@ struct AsmOpList { } const_idx_t register_const(Const new_const); Const get_const(const_idx_t idx); - void show_var(std::ostream& os, var_idx_t idx) const; void show_var_ext(std::ostream& os, std::pair idx_pair) const; void adjust_last() { if (list_.back().is_nop()) { @@ -1025,13 +1019,10 @@ struct Stack { void rearrange_top(var_idx_t top, bool last); void merge_const(const Stack& req_stack); void merge_state(const Stack& req_stack); - void show(int _mode); - void show() { - show(mode); - } + void show(); void opt_show() { if ((mode & (_StkCmt | _Shown)) == _StkCmt) { - show(mode); + show(); } } bool operator==(const Stack& y) const & { @@ -1115,12 +1106,16 @@ struct CodeBlob { #endif return res; } - bool import_params(FormalArgList&& arg_list); - var_idx_t create_var(TypePtr var_type, const LocalVarData* v_sym, SrcLocation loc); - var_idx_t create_tmp_var(TypePtr var_type, SrcLocation loc) { - return create_var(var_type, nullptr, loc); + std::vector create_var(TypePtr var_type, SrcLocation loc, std::string name); + std::vector create_tmp_var(TypePtr var_type, SrcLocation loc, const char* desc) { + std::vector ir_idx = create_var(var_type, loc, {}); +#ifdef TOLK_DEBUG + for (var_idx_t v : ir_idx) { + vars[v].desc = desc; + } +#endif + return ir_idx; } - int split_vars(bool strict = false); bool compute_used_code_vars(); bool compute_used_code_vars(std::unique_ptr& ops, const VarDescrList& var_info, bool edit) const; void print(std::ostream& os, int flags = 0) const; @@ -1144,14 +1139,6 @@ struct CodeBlob { void mark_noreturn(); void generate_code(AsmOpList& out_list, int mode = 0); void generate_code(std::ostream& os, int mode = 0, int indent = 0); - - void on_var_modification(const std::vector& left_lval_indices, SrcLocation here) const { - for (var_idx_t ir_idx : left_lval_indices) { - for (auto& f : vars.at(ir_idx).on_modification) { - f(here); - } - } - } }; // defined in builtins.cpp diff --git a/tolk/type-system.cpp b/tolk/type-system.cpp index b21bd0eeb..c7122e100 100644 --- a/tolk/type-system.cpp +++ b/tolk/type-system.cpp @@ -537,31 +537,6 @@ bool TypeDataVoid::can_be_casted_with_as_operator(TypePtr cast_to) const { } -// -------------------------------------------- -// extract_components() -// -// used in code generation (transforming Ops to other Ops) -// to be removed in the future -// - -void TypeDataGenericT::extract_components(std::vector& comp_types) const { - assert(false); -} - -void TypeDataTensor::extract_components(std::vector& comp_types) const { - for (TypePtr item : items) { - item->extract_components(comp_types); - } -} - -void TypeDataUnresolved::extract_components(std::vector& comp_types) const { - assert(false); -} - -void TypeDataVoid::extract_components(std::vector& comp_types) const { -} - - // -------------------------------------------- // parsing type from tokens // @@ -606,40 +581,38 @@ std::vector parse_nested_type_list_in_parenthesis(Lexer& lex) { static TypePtr parse_simple_type(Lexer& lex) { switch (lex.tok()) { - case tok_int: - lex.next(); - return TypeDataInt::create(); - case tok_bool: - lex.next(); - return TypeDataBool::create(); - case tok_cell: - lex.next(); - return TypeDataCell::create(); - case tok_builder: - lex.next(); - return TypeDataBuilder::create(); - case tok_slice: - lex.next(); - return TypeDataSlice::create(); - case tok_tuple: - lex.next(); - return TypeDataTuple::create(); - case tok_continuation: - lex.next(); - return TypeDataContinuation::create(); - case tok_null: - lex.next(); - return TypeDataNullLiteral::create(); - case tok_void: - lex.next(); - return TypeDataVoid::create(); case tok_self: case tok_identifier: { SrcLocation loc = lex.cur_location(); - std::string text = static_cast(lex.cur_str()); + std::string_view str = lex.cur_str(); lex.next(); - return TypeDataUnresolved::create(std::move(text), loc); + switch (str.size()) { + case 3: + if (str == "int") return TypeDataInt::create(); + break; + case 4: + if (str == "cell") return TypeDataCell::create(); + if (str == "void") return TypeDataVoid::create(); + if (str == "bool") return TypeDataBool::create(); + break; + case 5: + if (str == "slice") return TypeDataSlice::create(); + if (str == "tuple") return TypeDataTuple::create(); + break; + case 7: + if (str == "builder") return TypeDataBuilder::create(); + break; + case 12: + if (str == "continuation") return TypeDataContinuation::create(); + break; + default: + break; + } + return TypeDataUnresolved::create(std::string(str), loc); } + case tok_null: + lex.next(); + return TypeDataNullLiteral::create(); case tok_oppar: { std::vector items = parse_nested_type_list_in_parenthesis(lex); if (items.size() == 1) { @@ -651,11 +624,6 @@ static TypePtr parse_simple_type(Lexer& lex) { std::vector items = parse_nested_type_list(lex, tok_opbracket, "`[`", tok_clbracket, "`]` or `,`"); return TypeDataTypedTuple::create(std::move(items)); } - case tok_fun: { - lex.next(); - std::vector params_types = parse_nested_type_list_in_parenthesis(lex); - lex.expect(tok_arrow, "`->`"); - } default: lex.unexpected(""); } @@ -695,6 +663,12 @@ TypePtr parse_type_from_tokens(Lexer& lex) { return parse_type_expression(lex); } +// for internal usage only +TypePtr parse_type_from_string(std::string_view text) { + Lexer lex(text); + return parse_type_expression(lex); +} + std::ostream& operator<<(std::ostream& os, TypePtr type_data) { return os << (type_data ? type_data->as_human_readable() : "(nullptr-type)"); } diff --git a/tolk/type-system.h b/tolk/type-system.h index 13c0e4b09..482039e61 100644 --- a/tolk/type-system.h +++ b/tolk/type-system.h @@ -97,10 +97,6 @@ class TypeData { virtual int calc_width_on_stack() const { return 1; } - - virtual void extract_components(std::vector& comp_types) const { - comp_types.push_back(this); - } }; /* @@ -291,7 +287,6 @@ class TypeDataGenericT final : public TypeData { bool can_rhs_be_assigned(TypePtr rhs) const override; bool can_be_casted_with_as_operator(TypePtr cast_to) const override; int calc_width_on_stack() const override; - void extract_components(std::vector& comp_types) const override; }; /* @@ -318,7 +313,6 @@ class TypeDataTensor final : public TypeData { void traverse(const TraverserCallbackT& callback) const override; TypePtr replace_children_custom(const ReplacerCallbackT& callback) const override; int calc_width_on_stack() const override; - void extract_components(std::vector& comp_types) const override; }; /* @@ -387,7 +381,6 @@ class TypeDataUnresolved final : public TypeData { bool can_rhs_be_assigned(TypePtr rhs) const override; bool can_be_casted_with_as_operator(TypePtr cast_to) const override; int calc_width_on_stack() const override; - void extract_components(std::vector& comp_types) const override; }; /* @@ -408,7 +401,6 @@ class TypeDataVoid final : public TypeData { bool can_rhs_be_assigned(TypePtr rhs) const override; bool can_be_casted_with_as_operator(TypePtr cast_to) const override; int calc_width_on_stack() const override; - void extract_components(std::vector& comp_types) const override; }; @@ -417,6 +409,7 @@ class TypeDataVoid final : public TypeData { class Lexer; TypePtr parse_type_from_tokens(Lexer& lex); +TypePtr parse_type_from_string(std::string_view text); void type_system_init();