diff --git a/crypto/smartcont/mathlib.tolk b/crypto/smartcont/mathlib.tolk deleted file mode 100644 index 1f7510a6b..000000000 --- a/crypto/smartcont/mathlib.tolk +++ /dev/null @@ -1,1002 +0,0 @@ -/* - - - - Tolk fixed-point mathematical library - - (initially copied from mathlib.fc) - - - */ -tolk 0.6 - -/* - This file is part of TON Tolk Standard Library. - - Tolk Standard Library is free software: you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation, either version 2 of the License, or - (at your option) any later version. - - Tolk Standard Library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Lesser General Public License for more details. - -*/ - -/*--------------- MISSING OPERATIONS AND BUILT-INS ----------------*/ - -@pure -fun sgn(x: int): int - asm "SGN"; - -/// compute floor(log2(x))+1 -@pure -fun log2_floor_p1(x: int): int - asm "UBITSIZE"; - -@pure -fun mulrshiftr(x: int, y: int, s: int): int - asm "MULRSHIFTR"; - -@pure -fun mulrshiftr256(x: int, y: int): int - asm "256 MULRSHIFTR#"; - -@pure -fun mulrshift256mod(x: int, y: int): (int, int) - asm "256 MULRSHIFT#MOD"; - -@pure -fun mulrshiftr256mod(x: int, y: int): (int, int) - asm "256 MULRSHIFTR#MOD"; - -@pure -fun mulrshiftr255mod(x: int, y: int): (int, int) - asm "255 MULRSHIFTR#MOD"; - -@pure -fun mulrshiftr248mod(x: int, y: int): (int, int) - asm "248 MULRSHIFTR#MOD"; - -@pure -fun mulrshiftr5mod(x: int, y: int): (int, int) - asm "5 MULRSHIFTR#MOD"; - -@pure -fun mulrshiftr6mod(x: int, y: int): (int, int) - asm "6 MULRSHIFTR#MOD"; - -@pure -fun mulrshiftr7mod(x: int, y: int): (int, int) - asm "7 MULRSHIFTR#MOD"; - -@pure -fun lshift256divr(x: int, y: int): int - asm "256 LSHIFT#DIVR"; - -@pure -fun lshift256divmodr(x: int, y: int): (int, int) - asm "256 LSHIFT#DIVMODR"; - -@pure -fun lshift255divmodr(x: int, y: int): (int, int) - asm "255 LSHIFT#DIVMODR"; - -@pure -fun lshift2divmodr(x: int, y: int): (int, int) - asm "2 LSHIFT#DIVMODR"; - -@pure -fun lshift7divmodr(x: int, y: int): (int, int) - asm "7 LSHIFT#DIVMODR"; - -@pure -fun lshiftdivmodr(x: int, y: int, s: int): (int, int) - asm "LSHIFTDIVMODR"; - -@pure -fun rshiftr256mod(x: int): (int, int) - asm "256 RSHIFTR#MOD"; - -@pure -fun rshiftr248mod(x: int): (int, int) - asm "248 RSHIFTR#MOD"; - -@pure -fun rshiftr4mod(x: int): (int, int) - asm "4 RSHIFTR#MOD"; - -@pure -fun rshift3mod(x: int): (int, int) - asm "3 RSHIFT#MOD"; - -/// computes y - x (Tolk compiler does not try to use this by itself) -@pure -fun sub_rev(x: int, y: int): int - asm "SUBR"; - -@pure -fun nan(): int - asm "PUSHNAN"; - -@pure -fun is_nan(x: int): int - asm "ISNAN"; - -/*----------------------- SQUARE ROOTS ---------------------------*/ - -/// computes sqrt(a*b) exactly rounded to the nearest integer -/// for all 0 <= a, b <= 2^256-1 -/// may be used with b=1 or b=scale of fixed-point numbers -@pure -@inline_ref -fun geom_mean(a: int, b: int): int { - if (!min(a, b)) { - return 0; - } - var s: int = log2_floor_p1(a); // throws out of range error if a < 0 or b < 0 - var t: int = log2_floor_p1(b); - // NB: (a-b)/2+b == (a+b)/2, but without overflow for large a and b - var x: int = (s == t ? (a - b) / 2 + b : 1 << ((s + t) / 2)); - do { - // if always used with b=2^const, may be optimized to "const LSHIFTDIVC#" - // it is important to use `muldivc` here, not `muldiv` or `muldivr` - var q: int = (muldivc(a, b, x) - x) / 2; - x += q; - } while (q); - return x; -} - -/// integer square root, computes round(sqrt(a)) for all a>=0. -/// note: `inline` is better than `inline_ref` for such simple functions -@pure -@inline -fun sqrt(a: int): int { - return geom_mean(a, 1); -} - -/// version for fixed248 = fixed-point numbers with scale 2^248 -/// fixed248 sqrt(fixed248 x) -@pure -@inline -fun fixed248_sqrt(x: int): int { - return geom_mean(x, 1 << 248); -} - -/// fixed255 sqrt(fixed255 x) -@pure -@inline -fun fixed255_sqrt(x: int): int { - return geom_mean(x, 1 << 255); -} - -/// fixed248 sqr(fixed248 x); -@pure -@inline -fun fixed248_sqr(x: int): int { - return muldivr(x, x, 1 << 248); -} - -/// fixed255 sqr(fixed255 x); -@pure -@inline -fun fixed255_sqr(x: int): int { - return muldivr(x, x, 1 << 255); -} - -const fixed248_One: int = (1 << 248); -const fixed255_One: int = (1 << 255); - -/*------------------- USEFUL CONSTANTS -------------------*/ - -/// store huge constants in inline_ref functions for reuse -/// (y,z) where y=round(log(2)*2^256), z=round((log(2)*2^256-y)*2^128) -/// then log(2) = y/2^256 + z/2^384 -@pure -@inline_ref -fun log2_xconst_f256(): (int, int) { - return (80260960185991308862233904206310070533990667611589946606122867505419956976172, -32272921378999278490133606779486332143); -} - -/// (y,z) where Pi = y/2^254 + z/2^382 -@pure -@inline_ref -fun Pi_xconst_f254(): (int, int) { - return (90942894222941581070058735694432465663348344332098107489693037779484723616546, 108051869516004014909778934258921521947); -} - -/// atan(1/16) as fixed260 -@pure -@inline_ref -fun Atan1_16_f260(): int { - return 115641670674223639132965820642403718536242645001775371762318060545014644837101; // true value is ...101.0089... -} - -/// atan(1/8) as fixed259 -@pure -@inline_ref -fun Atan1_8_f259(): int { - return 115194597005316551477397594802136977648153890007566736408151129975021336532841; // correction -0.1687... -} - -/// atan(1/32) as fixed261 -@pure -@inline_ref -fun Atan1_32_f261(): int { - return 115754418570128574501879331591757054405465733718902755858991306434399246026247; // correction 0.395... -} - -/// inline is better than inline_ref for such very small functions -@pure -@inline -fun log2_const_f256(): int { - var (c: int, _) = log2_xconst_f256(); - return c; -} - -@pure -@inline -fun fixed248_log2_const(): int { - return log2_const_f256() ~>> 8; -} - -@pure -@inline -fun Pi_const_f254(): int { - var (c: auto, _) = Pi_xconst_f254(); - return c; -} - -@pure -@inline -fun fixed248_Pi_const(): int { - return Pi_const_f254() ~>> 6; -} - -/*-------------- HYPERBOLIC TANGENT AND EXPONENT ------------------*/ - -/// hyperbolic tangent of small x via n+2 terms of Lambert's continued fraction -/// n=17: good for |x| < log(2)/4 = 0.173 -/// fixed258 tanh_f258(fixed258 x, int n) -@pure -@inline_ref -fun tanh_f258(x: int, n: int): int { - var x2: int = muldivr(x, x, 1 << 255); // x^2 as fixed261 - var a: int = (2 * n + 5) << 250; // a=2n+5 as fixed250 - var c = a; - var Two: int = (1 << 251); // 2. as fixed250 - repeat (n) { - a = (c -= Two) + muldivr(x2, 1 << 239, a); // a := 2k+1+x^2/a as fixed250, k=n+1,n,...,2 - } - a = (touch(3) << 254) + muldivr(x2, 1 << 243, a); // a := 3+x^2/a as fixed254 - // y = x/(1+a') = x - x*a'/(1+a') = x - x*x^2/(a+x^2) where a' = x^2/a - return x - (muldivr(x, x2, a + (x2 ~>> 7)) ~>> 7); -} - -/// fixed257 expm1_f257(fixed257 x) -/// computes exp(x)-1 for small x via 19 terms of Lambert's continued fraction for tanh(x/2) -/// good for |x| < log(2)/2 = 0.347 (n=17); consumes ~3500 gas -@pure -@inline_ref -fun expm1_f257(x: int): int { - // (almost) compute tanh(x/2) first; x/2 as fixed258 = x as fixed257 - var x2: int = muldivr(x, x, 1 << 255); // x^2 as fixed261 - var Two: int = (1 << 251); // 2. as fixed250 - var a: int = touch(39) << 250; // a=2n+5 as fixed250 - var c = a; - repeat (17) { - a = (c -= Two) + muldivr(x2, 1 << 239, a); // a := 2k+1+x^2/a as fixed250, k=n+1,n,...,2 - } - a = (touch(3) << 254) + muldivr(x2, 1 << 243, a); // a := 3+x^2/a as fixed254 - // now tanh(x/2) = x/(1+a') where a'=x^2/a ; apply exp(x)-1=2*tanh(x/2)/(1-tanh(x/2)) - var t: int = (x ~>> 4) - a; // t:=x-a as fixed254 - return x - muldivr(x2, t / 2, a + mulrshiftr256(x, t) ~/ 4) ~/ 4; // x - x^2 * (x-a) / (a + x*(x-a)) -} - -/// expm1_f257() may be used to implement specific fixed-point exponentials -/// example: -/// fixed248 exp(fixed248 x) -@pure -@inline_ref -fun fixed248_exp(x: int): int { - var (l2c, l2d) = log2_xconst_f256(); - // divide x by log(2) and convert to fixed257 - // (int q, x) = muldivmodr(x, 256, l2c); // unfortunately, no such built-in - var (q: int, x redef) = lshiftdivmodr(x, l2c, 8); - x = 2 * x - muldivr(q, l2d, 1 << 127); - var y: int = expm1_f257(x); - // result is (1 + y) * (2^q) --> ((1 << 257) + y) >> (9 - q) - return (y ~>> (9 - q)) - (-1 << (248 + q)); - // note that (y ~>> (9 - q)) + (1 << (248 + q)) leads to overflow when q=8 -} - -/// compute 2^x in fixed248 -/// fixed248 exp2(fixed248 x) -@pure -@inline_ref -fun fixed248_exp2(x: int): int { - // (int q, x) = divmodr(x, 1 << 248); // no such built-in - var (q: int, x redef) = rshiftr248mod(x); - x = muldivr(x, log2_const_f256(), 1 << 247); - var y: int = expm1_f257(x); - return (y ~>> (9 - q)) - (-1 << (248 + q)); -} - -/*-------------------- TRIGONOMETRIC FUNCTIONS ----------------------*/ - -/// fixed260 tan(fixed260 x); -/// computes tan(x) for small |x|> 10)) ~>> 9); -} - -/// fixed260 tan(fixed260 x); -@pure -@inline_ref -fun tan_f260(x: int): int { - return tan_f260_inlined(x); -} - -/// fixed258 tan(fixed258 x); -/// computes tan(x) for small |x|> 6)) ~>> 5); -} - -/// fixed258 tan(fixed258 x); -@pure -@inline_ref -fun tan_f258(x: int): int { - return tan_f258_inlined(x); -} - -/// (fixed259, fixed263) sincosm1(fixed259 x) -/// computes (sin(x), 1-cos(x)) for small |x|<2*atan(1/16) -@pure -@inline -fun sincosm1_f259_inlined(x: int): (int, int) { - var t: int = tan_f260_inlined(x); // t=tan(x/2) as fixed260 - var tt: int = mulrshiftr256(t, t); // t^2 as fixed264 - var y: int = tt ~/ 512 + (1 << 255); // 1+t^2 as fixed255 - // 2*t/(1+t^2) as fixed259 and 2*t^2/(1+t^2) as fixed263 - // return (muldivr(t, 1 << 255, y), muldivr(tt, 1 << 255, y)); - return (t - muldivr(t / 2, tt, y) ~/ 256, tt - muldivr(tt / 2, tt, y) ~/ 256); -} - -@pure -@inline_ref -fun sincosm1_f259(x: int): (int, int) { - return sincosm1_f259_inlined(x); -} - -/// computes (sin(x+xe),-cos(x+xe)) for |x| <= Pi/4, xe very small -/// this function is very accurate, error less than 0.7 ulp (consumes ~ 5500 gas) -/// (fixed256, fixed256) sincosn(fixed256 x, fixed259 xe) -@pure -@inline_ref -fun sincosn_f256(x: int, xe: int): (int, int) { - // var (q, x1) = muldivmodr(x, 8, Atan1_8_f259()); // no muldivmodr() builtin - var (q, x1) = lshift2divmodr(abs(x), Atan1_8_f259()); // reduce mod theta where theta=2*atan(1/8) - var (si, co) = sincosm1_f259(x1 * 2 + xe); - var (a, b, c) = (-1, 0, 1); - repeat (q) { - // (a+b*I) *= (8+I)^2 = 63+16*I - (a, b, c) = (63 * a - 16 * b, 16 * a + 63 * b, 65 * c); - } - // now a/c = cos(q*theta), b/c = sin(q*theta) exactly(!) - // compute (a+b*I)*(1-co+si*I)/c - // (b, a) = (lshift256divr(b, c), lshift256divr(a, c)); - var (b redef, br: int) = lshift256divmodr(b, c); br = muldivr(br, 128, c); - var (a redef, ar: int) = lshift256divmodr(a, c); ar = muldivr(ar, 128, c); - return (sgn(x) * (((mulrshiftr256(b, co) - br) ~/ 16 - mulrshiftr256(a, si)) ~/ 8 - b), - a - ((mulrshiftr256(a, co) - ar) ~/ 16 + mulrshiftr256(b, si)) ~/ 8); -} - -/// compute (sin(x),1-cos(x)) in fixed256 for |x| < 16*atan(1/16) = 0.9987 -/// (fixed256, fixed257) sincosm1_f256(fixed256 x); -/// slightly less accurate than sincosn_f256() (error up to 3/2^256), but faster (~ 4k gas) and shorter -@pure -@inline_ref -fun sincosm1_f256(x: int): (int, int) { - var (si, co) = sincosm1_f259_inlined(x); // compute (sin,1-cos)(x/8) in (fixed259,fixed263) - var r: int = 7; - repeat (r / 2) { - // 1-cos(2*x) = 2*sin(x)^2, sin(2*x) = 2*sin(x)*cos(x) - (co, si) = (mulrshiftr256(si, si), si - (mulrshiftr256(si, co) ~>> r)); - r -= 2; - } - return (si, co); -} - -/// compute (p, q) such that p/q = tan(x) for |x|<2*atan(1/2)=1899/2048=0.927 -/// (int, int) tan_aux(fixed256 x); -@pure -@inline_ref -fun tan_aux_f256(x: int): (int, int) { - var t: int = tan_f258_inlined(x); // t=tan(x/4) as fixed258 - // t:=2*t/(1-t^2)=2*(t-t^3/(t^2-1)) - var tt: int = mulrshiftr256(t, t); // t^2 as fixed260 - t = muldivr(t, tt, tt ~/ 16 + (-1 << 256)) ~/ 16 - t; // now t=-tan(x/2) as fixed259 - return (t, mulrshiftr256(t, t) ~/ 4 + (-1 << 256)); // return (2*t, t^2-1) as fixed256 -} - -/// sincosm1_f256() and sincosn_f256() may be used to implement trigonometric functions for different fixed-point types -/// example: -/// (fixed248, fixed248) sincos(fixed248 x); -@pure -@inline_ref -fun fixed248_sincos(x: int): (int, int) { - var (Pic, Pid) = Pi_xconst_f254(); - // (int q, x) = muldivmodr(x, 128, Pic); // no muldivmodr() builtin - var (q: int, x redef) = lshift7divmodr(x, Pic); // reduce mod Pi/2 - x = 2 * x - muldivr(q, Pid, 1 << 127); - var (si: int, co: int) = sincosm1_f256(x); // doesn't make sense to use more accurate sincosn_f256() - co = (1 << 248) - (co ~>> 9); - si = si ~>> 8; - repeat (q & 3) { - (si, co) = (co, -si); - } - return (si, co); -} - -/// fixed248 sin(fixed248 x); -/// inline is better than inline_ref for such simple functions -@pure -@inline -fun fixed248_sin(x: int): int { - var (si: int, _) = fixed248_sincos(x); - return si; -} - -/// fixed248 cos(fixed248 x); -@pure -@inline -fun fixed248_cos(x: int): int { - var (_, co: int) = fixed248_sincos(x); - return co; -} - -/// similarly, tan_aux_f256() may be used to implement tan() and cot() for specific fixed-point formats -/// fixed248 tan(fixed248 x); -/// not very accurate when |tan(x)| is very large (difficult to do better without floating-point numbers) -/// however, the relative accuracy is approximately 2^-247 in all cases, which is good enough for arguments given up to 2^-249 -@pure -@inline_ref -fun fixed248_tan(x: int): int { - var (Pic, Pid) = Pi_xconst_f254(); - // (int q, x) = muldivmodr(x, 128, Pic); // no muldivmodr() builtin - var (q: int, x redef) = lshift7divmodr(x, Pic); // reduce mod Pi/2 - x = 2 * x - muldivr(q, Pid, 1 << 127); - var (a, b) = tan_aux_f256(x); // now a/b = tan(x') - if (q & 1) { - (a, b) = (b, -a); - } - return muldivr(a, 1 << 248, b); // either -b/a or a/b as fixed248 -} - -/// fixed248 cot(fixed248 x); -@pure -@inline_ref -fun fixed248_cot(x: int): int { - var (Pic, Pid) = Pi_xconst_f254(); - var (q: int, x redef) = lshift7divmodr(x, Pic); // reduce mod Pi/2 - x = 2 * x - muldivr(q, Pid, 1 << 127); - var (b, a) = tan_aux_f256(x); // now b/a = tan(x') - if (q & 1) { - (a, b) = (b, -a); - } - return muldivr(a, 1 << 248, b); // either -b/a or a/b as fixed248 -} - -/*---------------- INVERSE HYPERBOLIC TANGENT AND LOGARITHMS ----------------*/ - -/// inverse hyperbolic tangent of small x, evaluated by means of n terms of the continued fraction -/// valid for |x| < 2^-2.5 ~ 0.18 if n=37 (slightly less accurate with n=36) -/// |x| < 1/8 if n=32; |x| < 2^-3.5 if n=28; |x| < 1/16 if n=25 -/// |x| < 2^-4.5 if n=23; |x| < 1/32 if n=21; |x| < 1/64 if n=18 -/// fixed258 atanh(fixed258 x); -@pure -@inline_ref -fun atanh_f258(x: int, n: int): int { - var x2: int = mulrshiftr256(x, x); // x^2 as fixed260 - var One: int = (1 << 254); - var a: int = One ~/ n + (1 << 255); // a := 2 + 1/n as fixed254 - repeat (n - 1) { - // a := 1 + (1 - x^2 / a)(1 + 1/n) as fixed254 - var t: int = One - muldivr(x2, 1 << 248, a); // t := 1 - x^2 / a - var n1: int = n - 1; - a = muldivr(t, n, n1) + One; - n = n1; - } - // x / (1 - x^2 / a) = x / (1 - d) = x + x * d / (1 - d) for d = x^2 / a - // int d = muldivr(x2, 1 << 255, a - (x2 ~>> 6)); // d/(1-d) = x^2/(a-x^2) as fixed261 - // return x + (mulrshiftr256(x, d) ~>> 5); - return x + muldivr(x, x2 / 2, a - x2 ~/ 64) ~/ 32; -} - -/// number of terms n should be chosen as for atanh_f258() -/// fixed261 atanh(fixed261 x); -@pure -@inline -fun atanh_f261_inlined(x: int, n: int): int { - var x2: int = mulrshiftr256(x, x); // x^2 as fixed266 - var One: int = (1 << 254); - var a: int = One ~/ n + (1 << 255); // a := 2 + 1/n as fixed254 - repeat (n - 1) { - // a := 1 + (1 - x^2 / a)(1 + 1/n) as fixed254 - var t: int = One - muldivr(x2, 1 << 242, a); // t := 1 - x^2 / a - var n1: int = n - 1; - a = muldivr(t, n, n1) + One; - n = n1; - } - // x / (1 - x^2 / a) = x / (1 - d) = x + x * d / (1 - d) for d = x^2 / a - // int d = muldivr(x2, 1 << 255, a - (x2 ~>> 12)); // d/(1-d) = x^2/(a-x^2) as fixed267 - // return x + (mulrshiftr256(x, d) ~>> 11); - return x + muldivr(x, x2, a - x2 ~/ 4096) ~/ 4096; -} - -/// fixed261 atanh(fixed261 x); -@pure -@inline_ref -fun atanh_f261(x: int, n: int): int { - return atanh_f261_inlined(x, n); -} - -/// returns (y, s) such that log(x) = y/2^257 + s*log(2) for positive integer x -/// (fixed257, int) log_aux(int x) -@pure -@inline_ref -fun log_aux_f257(x: int): (int, int) { - var s: int = log2_floor_p1(x); - x <<= 256 - s; - var t: int = touch(-1 << 256); - if ((x >> 249) <= 90) { - // t~touch(); - t >>= 1; - s -= 1; - } - x += t; - var `2x`: int = 2 * x; - var y: int = lshift256divr(`2x`, (x >> 1) - t); - // y = `2x` - (mulrshiftr256(2x, y) ~>> 2); // this line could improve precision on very rare occasions - return (atanh_f258(y, 36), s); -} - -/// computes 33^m for small m -@pure -@inline -fun pow33(m: int): int { - var t: int = 1; - repeat (m) { - t *= 33; - } - return t; -} - -/// computes 33^m for small 0<=m<=22 -/// slightly faster than pow33() -@pure -@inline -fun pow33b(m: int): int { - var (mh: int, ml: int) = divmod(m, 5); - var t: int = 1; - repeat (ml) { - t *= 33; - } - repeat (mh) { - t *= 33 * 33 * 33 * 33 * 33; - } - return t; -} - -/// returns (s, q, y) such that log(x) = s*log(2) + q*log(33/32) + y/2^260 for positive integer x -/// (int, int, fixed260) log_auxx_f260(int x); -@pure -@inline_ref -fun log_auxx_f260(x: int): (int, int, int) { - var s: int = log2_floor_p1(x) - 1; - x <<= 255 - s; // rescale to 1 <= x < 2 as fixed255 - var t: int = touch(2873) << 244; // ~ (33/32)^11 ~ sqrt(2) as fixed255 - var x1: int = (x - t) >> 1; - var q: int = muldivr(x1, 65, x1 + t) + 11; // crude approximation to round(log(x)/log(33/32)) - // t = 1; repeat (q) { t *= 33; } // t:=33^q, 0<=q<=22 - t = pow33b(q); - t <<= (51 - q) * 5; // t:=(33/32)^q as fixed255, nearest power of 33/32 to x - x -= t; - var y: int = lshift256divr(x << 4, (x >> 1) + t); // y = (x-t)/(x+t) as fixed261 - y = atanh_f261(y, 18); // atanh((x-t)/(x+t)) as fixed261, or log(x/t) as fixed260 - return (s, q, y); -} - -/// returns (y, s) such that log(x) = y/2^256 + s*log(2) for positive integer x -/// this function is very precise (error less than 0.6 ulp) and consumes < 7k gas -/// may be used to implement specific fixed-point instances of log() and log2() -/// (fixed256, int) log_aux_f256(int x); -@pure -@inline_ref -fun log_aux_f256(x: int): (int, int) { - var (s, q, y) = log_auxx_f260(x); - var (yh, yl) = rshiftr4mod(y); // y ~/% 16 , but Tolk does not optimize this to RSHIFTR#MOD - // int Log33_32 = 3563114646320977386603103333812068872452913448227778071188132859183498739150; // log(33/32) as fixed256 - // int Log33_32_l = -3769; // log(33/32) = Log33_32 / 2^256 + Log33_32_l / 2^269 - yh += (yl * 512 + q * -3769) ~>> 13; // compensation, may be removed if slightly worse accuracy is acceptable - var Log33_32: int = 3563114646320977386603103333812068872452913448227778071188132859183498739150; // log(33/32) as fixed256 - return (yh + q * Log33_32, s); -} - -/// returns (y, s) such that log2(x) = y/2^256 + s for positive integer x -/// this function is very precise (error less than 0.6 ulp) and consumes < 7k gas -/// may be used to implement specific fixed-point instances of log() and log2() -/// (fixed256, int) log2_aux_f256(int x); -@pure -@inline_ref -fun log2_aux_f256(x: int): (int, int) { - var (s, q, y) = log_auxx_f260(x); - y = lshift256divr(y, log2_const_f256()) ~>> 4; // y/log(2) as fixed256 - var Log33_32: int = 5140487830366106860412008603913034462883915832139695448455767612111363481357; // log_2(33/32) as fixed256 - // Log33_32/2^256 happens to be a very precise approximation to log_2(33/32), no compensation required - return (y + q * Log33_32, s); -} - - -/// fixed248 log(fixed248 x) -@pure -@inline_ref -fun fixed248_log(x: int): int { - var (y, s) = log_aux_f256(x); - return muldivr(s - 248, log2_const_f256(), 1 << 8) + (y ~>> 8); - // return muldivr(s - 248, 80260960185991308862233904206310070533990667611589946606122867505419956976172, 1 << 8) + (y ~>> 8); -} - -/// fixed248 log2(fixed248 x) -@pure -@inline -fun fixed248_log2(x: int): int { - var (y, s) = log2_aux_f256(x); - return ((s - 248) << 248) + (y ~>> 8); -} - -/// computes x^y as exp(y*log(x)), x >= 0 -/// fixed248 pow(fixed248 x, fixed248 y); -@pure -@inline_ref -fun fixed248_pow(x: int, y: int): int { - if (!y) { - return 1 << 248; // x^0 = 1 - } - if (x <= 0) { - var bad: int = (x | y) < 0; - return 0 >> bad; // 0^y = 0 if x=0 and y>=0; "out of range" exception otherwise - } - var (l, s) = log2_aux_f256(x); - s -= 248; // log_2(x) = s+l, l is fixed256, 0<=l<1 - // compute (s+l)*y = q+ll - var (q1, r1) = mulrshiftr248mod(s, y); // muldivmodr(s, y, 1 << 248) - var (q2, r2) = mulrshift256mod(l, y); - r2 >>= 247; - var (q3, r3) = rshiftr248mod(q2); // divmodr(q2, 1 << 248); - var (q, ll) = rshiftr248mod(r1 + r3); - ll = 512 * ll + r2; - q += q1 + q3; - // now log_2(x^y) = y*log_2(x) = q + ll, ss integer, ll fixed257, -1/2<=ll<1/2 - var sq: int = q + 248; - if (sq <= 0) { - return -(sq == 0); // underflow - } - y = expm1_f257(mulrshiftr256(ll, log2_const_f256())); - return (y ~>> (9 - q)) - (-1 << sq); -} - -/*-------------------- INVERSE TRIGONOMETRIC FUNCTIONS ------------------*/ - -/// number of terms n should be chosen as for atanh_f258() -/// fixed259 atan(fixed259 x); -@pure -@inline_ref -fun atan_f259(x: int, n: int): int { - var x2: int = mulrshiftr256(x, x); // x^2 as fixed262 - var One: int = (1 << 254); - var a: int = One ~/ n + (1 << 255); // a := 2 + 1/n as fixed254 - repeat (n - 1) { - // a := 1 + (1 + x^2 / a)(1 + 1/n) as fixed254 - var t: int = One + muldivr(x2, 1 << 246, a); // t := 1 + x^2 / a - var n1: int = n - 1; - a = muldivr(t, n, n1) + One; - n = n1; - } - // x / (1 + x^2 / a) = x / (1 + d) = x - x * d / (1 + d) = x - x * x^2/(a+x^2) for d = x^2 / a - return x - muldivr(x, x2, a + x2 ~/ 256) ~/ 256; -} - -/// number of terms n should be chosen as for atanh_f261() -/// fixed261 atan(fixed261 x); -@pure -@inline -fun atan_f261_inlined(x: int, n: int): int { - var x2: int = mulrshiftr256(x, x); // x^2 as fixed266 - var One: int = (1 << 254); - var a: int = One ~/ n + (1 << 255); // a := 2 + 1/n as fixed254 - repeat (n - 1) { - // a := 1 + (1 + x^2 / a)(1 + 1/n) as fixed254 - var t: int = One + muldivr(x2, 1 << 242, a); // t := 1 + x^2 / a - var n1: int = n - 1; - a = muldivr(t, n, n1) + One; - n = n1; - } - // x / (1 + x^2 / a) = x / (1 + d) = x - x * d / (1 + d) = x - x * x^2/(a+x^2) for d = x^2 / a - return x - muldivr(x, x2, a + x2 ~/ 4096) ~/ 4096; -} - -/// fixed261 atan(fixed261 x); -@pure -@inline_ref -fun atan_f261(x: int, n: int): int { - return atan_f261_inlined(x, n); -} - -/// computes (q,a,b) such that q is approximately atan(x)/atan(1/32) and a+b*I=(1+I/32)^q as fixed255 -/// then b/a=atan(q*atan(1/32)) exactly, and (a,b) is almost a unit vector pointing in the direction of (1,x) -/// must have |x|<1.1, x is fixed24 -/// (int, fixed255, fixed255) atan_aux_prereduce(fixed24 x); -@pure -@inline_ref -fun atan_aux_prereduce(x: int): (int, int, int) { - var xu: int = abs(x); - var tc: int = 7214596; // tan(13*theta) as fixed24 where theta=atan(1/32) - var t1: int = muldivr(xu - tc, 1 << 88, xu * tc + (1 << 48)); // tan(x') as fixed64 where x'=atan(x)-13*theta - // t1/(3+t1^2) * 3073/32 = x'/3 * 3072/32 = x' / (96/3072) = x' / theta - var q: int = muldivr(t1 * 3073, 1 << 59, t1 * t1 + (touch(3) << 128)) + 13; // approximately round(atan(x)/theta), 0<=q<=25 - var (pa, pb) = (33226912, 5232641); // (32+I)^5 - var (qh, ql) = divmod(q, 5); - var (a, b) = (1 << (5 * (51 - q)), 0); // (1/32^q, 0) as fixed255 - repeat (ql) { - // a+b*I *= 32+I - (a, b) = (sub_rev(touch(b), 32 * a), a + 32 * b); // same as (32 * a - b, 32 * b + a), but more efficient - } - repeat (qh) { - // a+b*I *= (32+I)^5 = pa + pb*I - (a, b) = (a * pa - b * pb, a * pb + b * pa); - } - var xs: int = sgn(x); - return (xs * q, a, xs * b); -} - -/// compute (q, z) such that atan(x)=q*atan(1/32)+z for -1 <= x < 1 -/// this function is reasonably accurate (error < 7 ulp with ulp = 2^-261), but it consumes >7k gas -/// this is sufficient for most purposes -/// (int, fixed261) atan_aux(fixed256 x) -@pure -@inline_ref -fun atan_aux_f256(x: int): (int, int) { - var (q, a, b) = atan_aux_prereduce(x ~>> 232); // convert x to fixed24 - // now b/a = tan(q*atan(1/32)) exactly, where q is near atan(x)/atan(1/32); so b/a is near x - // compute y = u/v = (a*x-b)/(a+b*x) as fixed261 ; then |y|<0.0167 = 1.07/64 and atan(x)=atan(y)+q*atan(1/32) - var (u, ul) = mulrshiftr256mod(a, x); - u = (ul ~>> 250) + ((u - b) << 6); // |u| < 1/32, convert fixed255 -> fixed261 - var v: int = a + mulrshiftr256(b, x); // v is scalar product of (a,b) and (1,x), it is approximately in [1..sqrt(2)] as fixed255 - var y: int = muldivr(u, 1 << 255, v); // y = u/v as fixed261 - var z: int = atan_f261_inlined(y, 18); // z = atan(x)-q*atan(1/32) - return (q, z); -} - -/// compute (q, z) such that atan(x)=q*atan(1/32)+z for -1 <= x < 1 -/// this function is very accurate (error < 2 ulp), but it consumes >7k gas -/// in most cases, faster function atan_aux_f256() should be used -/// (int, fixed261) atan_auxx(fixed256 x) -@pure -@inline_ref -fun atan_auxx_f256(x: int): (int, int) { - var (q, a, b) = atan_aux_prereduce(x ~>> 232); // convert x to fixed24 - // now b/a = tan(q*atan(1/32)) exactly, where q is near atan(x)/atan(1/32); so b/a is near x - // compute y = (a*x-b)/(a+b*x) as fixed261 ; then |y|<0.0167 = 1.07/64 and atan(x)=atan(y)+q*atan(1/32) - // use sort of double precision arithmetic for this - var (u, ul) = mulrshiftr256mod(a, x); - ul /= 2; - u -= b; // |u| < 1/32 as fixed255 - var (v, vl) = mulrshiftr256mod(b, x); - vl /= 2; - v += a; // v is scalar product of (a,b) and (1,x), it is approximately in [1..sqrt(2)] as fixed255 - // y = (u + ul*eps) / (v + vl*eps) = u/v + (ul - vl * u/v)/v * eps where eps=1/2^255 - var (y, r) = lshift255divmodr(u, v); // y = u/v as fixed255 - var yl: int = muldivr(ul + r, 1 << 255, v) - muldivr(vl, y, v); // y/2^255 + yl/2^510 represent u/v - y = (yl ~>> 249) + (y << 6); // convert y to fixed261 - var z: int = atan_f261_inlined(y, 18); // z = atan(x)-q*atan(1/32) - return (q, z); -} - -/// consumes ~ 8k gas -/// fixed255 atan(fixed255 x); -@pure -@inline_ref -fun atan_f255(x: int): int { - var s: int = (x ~>> 256); - touch(x); - if (s) { - x = lshift256divr(-1 << 255, x); // x:=-1/x as fixed256 - } else { - x *= 2; // convert to fixed256 - } - var (q, z) = atan_aux_f256(x); - // now atan(x) = z + q*atan(1/32) + s*(Pi/2), z is fixed261 - var (Pi_h, Pi_l) = Pi_xconst_f254(); // Pi/2 as fixed255 + fixed383 - var (qh, ql) = mulrshiftr6mod(q, Atan1_32_f261()); - return qh + s * Pi_h + (z + ql + muldivr(s, Pi_l, 1 << 122)) ~/ 64; -} - -/// computes atan(x) for -1 <= x < 1 only -/// fixed256 atan_small(fixed256 x); -@pure -@inline_ref -fun atan_f256_small(x: int): int { - var (q, z) = atan_aux_f256(x); - // now atan(x) = z + q*atan(1/32), z is fixed261 - var (qh, ql) = mulrshiftr5mod(q, Atan1_32_f261()); - return qh + (z + ql) ~/ 32; -} - -/// fixed255 asin(fixed255 x); -@pure -@inline_ref -fun asin_f255(x: int): int { - var a: int = fixed255_One - fixed255_sqr(x); // a:=1-x^2 - if (!a) { - return sgn(x) * Pi_const_f254(); // Pi/2 or -Pi/2 - } - var y: int = fixed255_sqrt(a); // sqrt(1-x^2) - var t: int = -lshift256divr(x, (-1 << 255) - y); // t = x/(1+sqrt(1-x^2)) avoiding overflow - return atan_f256_small(t); // asin(x)=2*atan(t) -} - -/// fixed254 acos(fixed255 x); -@pure -@inline_ref -fun acos_f255(x: int): int { - var Pi: int = Pi_const_f254(); - if (x == (-1 << 255)) { - return Pi; // acos(-1) = Pi - } - Pi /= 2; - var y: int = fixed255_sqrt(fixed255_One - fixed255_sqr(x)); // sqrt(1-x^2) - var t: int = lshift256divr(x, (-1 << 255) - y); // t = -x/(1+sqrt(1-x^2)) avoiding overflow - return Pi + atan_f256_small(t) ~/ 2; // acos(x)=Pi/2 + 2*atan(t) -} - -/// consumes ~ 10k gas -/// fixed248 asin(fixed248 x) -@pure -@inline -fun fixed248_asin(x: int): int { - return asin_f255(x << 7) ~>> 7; -} - -/// consumes ~ 10k gas -/// fixed248 acos(fixed248 x) -@pure -@inline -fun fixed248_acos(x: int): int { - return acos_f255(x << 7) ~>> 6; -} - -/// consumes ~ 7500 gas -/// fixed248 atan(fixed248 x); -@pure -@inline_ref -fun fixed248_atan(x: int): int { - var s: int = (x ~>> 249); - touch(x); - if (s) { - s = sgn(s); - x = lshift256divr(-1 << 248, x); // x:=-1/x as fixed256 - } else { - x <<= 8; // convert to fixed256 - } - var (q, z) = atan_aux_f256(x); - // now atan(x) = z + q*atan(1/32) + s*(Pi/2), z is fixed261 - return (z ~/ 64 + s * Pi_const_f254() + muldivr(q, Atan1_32_f261(), 64)) ~/ 128; // compute in fixed255, then convert -} - -/// fixed248 acot(fixed248 x); -@pure -@inline_ref -fun fixed248_acot(x: int): int { - var s: int = (x ~>> 249); - touch(x); - if (s) { - x = lshift256divr(-1 << 248, x); // x:=-1/x as fixed256 - s = 0; - } else { - x <<= 8; // convert to fixed256 - s = sgn(x); - } - var (q, z) = atan_aux_f256(x); - // now acot(x) = - z - q*atan(1/32) + s*(Pi/2), z is fixed261 - return (s * Pi_const_f254() - z ~/ 64 - muldivr(q, Atan1_32_f261(), 64)) ~/ 128; // compute in fixed255, then convert -} - -/*-------------------- PSEUDO-RANDOM NUMBERS ------------------*/ - -/// random number with standard normal distribution N(0,1) -/// generated by Kinderman--Monahan ratio method modified by J.Leva -/// spends ~ 2k..3k gas on average -/// fixed252 nrand(); -@inline_ref -fun nrand_f252(): int { - var (x, s, t, A, B, r0) = (nan(), touch(29483) << 236, touch(-3167) << 239, 12845, 16693, 9043); - // 4/sqrt(e*Pi) = 1.369 loop iterations on average - do { - var (u, v) = (random() / 16 + 1, muldivr(random() - (1 << 255), 7027, 1 << 16)); // fixed252; 7027=ceil(sqrt(8/e)*2^12) - var va: int = abs(v); - var (u1, v1) = (u - s, va - t); // (u - 29483/2^16, abs(v) + 3167/2^13) as fixed252 - // Q := u1^2 + v1 * (A*v1 - B*u1) as fixed252 where A=12845/2^16, B=16693/2^16 - var Q: int = muldivr(u1, u1, 1 << 252) + muldivr(v1, muldivr(v1, A, 1 << 16) - muldivr(u1, B, 1 << 16), 1 << 252); - // must have 9043 / 2^15 < Q < 9125 / 2^15, otherwise accept if smaller, reject if larger - var Qd: int = (Q >> 237) - r0; - if ((Qd < 9125 - 9043) & (va / u < 16)) { - x = muldivr(v, 1 << 252, u); // x:=v/u as fixed252; reject immediately if |v/u| >= 16 - if (Qd >= 0) { - // immediately accept if Qd < 0 - // rarely taken branch - 0.012 times per call on average - // check condition v^2 < -4*u^2*log(u), or equivalent condition u < exp(-x^2/4) for x=v/u - var xx: int = mulrshiftr256(x, x) ~/ 4; // x^2/4 as fixed248 - var ex: int = fixed248_exp(-xx) * 16; // exp(-x^2/4) as fixed252 - if (u > ex) { - x = nan(); // condition false, reject - } - } - } - } while (!(~ is_nan(x))); - return x; -} - -/// generates a random number approximately distributed according to the standard normal distribution -/// much faster than nrand_f252(), should be suitable for most purposes when only several random numbers are needed -/// fixed252 nrand_fast(); -@inline_ref -fun nrand_fast_f252(): int { - var t: int = touch(-3) << 253; // -6. as fixed252 - repeat (12) { - t += random() / 16; // add together 12 uniformly random numbers - } - return t; -} - -/// random number uniformly distributed in [0..1) -/// fixed248 random(); -@inline -fun fixed248_random(): int { - return random() >> 8; -} - -/// random number with standard normal distribution -/// fixed248 nrand(); -@inline -fun fixed248_nrand(): int { - return nrand_f252() ~>> 4; -} - -/// generates a random number approximately distributed according to the standard normal distribution -/// fixed248 nrand_fast(); -@inline -fun fixed248_nrand_fast(): int { - return nrand_fast_f252() ~>> 4; -} diff --git a/crypto/smartcont/stdlib.tolk b/crypto/smartcont/stdlib.tolk deleted file mode 100644 index 10c3b36ad..000000000 --- a/crypto/smartcont/stdlib.tolk +++ /dev/null @@ -1,1108 +0,0 @@ -// Standard library for Tolk -// (initially copied from stdlib.fc) -// -tolk 0.6 - -/* - This file is part of TON Tolk Standard Library. - - Tolk Standard Library is free software: you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation, either version 2 of the License, or - (at your option) any later version. - - Tolk Standard Library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Lesser General Public License for more details. -*/ - -/* - # Tuple manipulation primitives - The names and the types are mostly self-explaining. - - Note that currently values of atomic type `tuple` can't be cast to composite tuple type (e.g. `[int, cell]`) - and vise versa. -*/ - -/*** - # Lisp-style lists - - Lists can be represented as nested 2-elements tuples. - Empty list is conventionally represented as TVM `null` value. - For example, tuple `(1, (2, (3, null)))` represents list `[1, 2, 3]`. Elements of a list can be of different types. -*/ - -/// Adds an element to the beginning of lisp-style list. -@pure -fun cons(head: X, tail: tuple): tuple - asm "CONS"; - -/// Extracts the head and the tail of lisp-style list. -@pure -fun uncons(list: tuple): (X, tuple) - asm "UNCONS"; - -/// Extracts the tail and the head of lisp-style list. -@pure -fun list_next(list: tuple): (tuple, X) - asm( -> 1 0) "UNCONS"; - -/// Returns the head of lisp-style list. -@pure -fun car(list: tuple): X - asm "CAR"; - -/// Returns the tail of lisp-style list. -@pure -fun cdr(list: tuple): tuple - asm "CDR"; - -/// Creates tuple with zero elements. -@pure -fun empty_tuple(): tuple - asm "NIL"; - -/// Appends a value `x` to a `Tuple t = (x1, ..., xn)`, but only if the resulting `Tuple t' = (x1, ..., xn, x)` -/// is of length at most 255. Otherwise throws a type check exception. -@pure -fun tpush(t: tuple, value: X): tuple - asm "TPUSH"; - -@pure -fun ~tpush(t: tuple, value: X): (tuple, ()) - asm "TPUSH"; - -/// Creates a tuple of length one with given argument as element. -@pure -fun single(x: X): [X] - asm "SINGLE"; - -/// Unpacks a tuple of length one -@pure -fun unsingle(t: [X]): X - asm "UNSINGLE"; - -/// Creates a tuple of length two with given arguments as elements. -@pure -fun pair(x: X, y: Y): [X, Y] - asm "PAIR"; - -/// Unpacks a tuple of length two -@pure -fun unpair(t: [X, Y]): (X, Y) - asm "UNPAIR"; - -/// Creates a tuple of length three with given arguments as elements. -@pure -fun triple(x: X, y: Y, z: Z): [X, Y, Z] - asm "TRIPLE"; - -/// Unpacks a tuple of length three -@pure -fun untriple(t: [X, Y, Z]): (X, Y, Z) - asm "UNTRIPLE"; - -/// Creates a tuple of length four with given arguments as elements. -@pure -fun tuple4(x: X, y: Y, z: Z, w: W): [X, Y, Z, W] - asm "4 TUPLE"; - -/// Unpacks a tuple of length four -@pure -fun untuple4(t: [X, Y, Z, W]): (X, Y, Z, W) - asm "4 UNTUPLE"; - -/// Returns the first element of a tuple (with unknown element types). -@pure -fun first(t: tuple): X - asm "FIRST"; - -/// Returns the second element of a tuple (with unknown element types). -@pure -fun second(t: tuple): X - asm "SECOND"; - -/// Returns the third element of a tuple (with unknown element types). -@pure -fun third(t: tuple): X - asm "THIRD"; - -/// Returns the fourth element of a tuple (with unknown element types). -@pure -fun fourth(t: tuple): X - asm "3 INDEX"; - -/// Returns the [`index`]-th element of tuple [`t`]. -@pure -fun at(t: tuple, index: int): X - builtin; - -/// Returns the first element of a pair tuple. -@pure -fun pair_first(p: [X, Y]): X - asm "FIRST"; - -/// Returns the second element of a pair tuple. -@pure -fun pair_second(p: [X, Y]): Y - asm "SECOND"; - -/// Returns the first element of a triple tuple. -@pure -fun triple_first(p: [X, Y, Z]): X - asm "FIRST"; - -/// Returns the second element of a triple tuple. -@pure -fun triple_second(p: [X, Y, Z]): Y - asm "SECOND"; - -/// Returns the third element of a triple tuple. -@pure -fun triple_third(p: [X, Y, Z]): Z - asm "THIRD"; - - -/// Moves a variable [x] to the top of the stack. -@pure -fun touch(x: X): X - builtin; - -/// Moves a variable [x] to the top of the stack. -@pure -fun ~touch(x: X): (X, ()) - builtin; - -/// Mark a variable as used, such that the code which produced it won't be deleted even if it is not impure. -fun ~impure_touch(x: X): (X, ()) - asm "NOP"; - - - -/// Returns the current Unix time as an Integer -@pure -fun now(): int - asm "NOW"; - -/// Returns the internal address of the current smart contract as a Slice with a `MsgAddressInt`. -/// If necessary, it can be parsed further using primitives such as [parse_std_addr]. -@pure -fun my_address(): slice - asm "MYADDR"; - -/// Returns the balance of the smart contract as a tuple consisting of an int -/// (balance in nanotoncoins) and a `cell` -/// (a dictionary with 32-bit keys representing the balance of "extra currencies") -/// at the start of Computation Phase. -/// Note that RAW primitives such as [send_raw_message] do not update this field. -@pure -fun get_balance(): [int, cell] - asm "BALANCE"; - -/// Returns the logical time of the current transaction. -@pure -fun cur_lt(): int - asm "LTIME"; - -/// Returns the starting logical time of the current block. -@pure -fun block_lt(): int - asm "BLOCKLT"; - -/// Computes the representation hash of a `cell` [c] and returns it as a 256-bit unsigned integer `x`. -/// Useful for signing and checking signatures of arbitrary entities represented by a tree of cells. -@pure -fun cell_hash(c: cell): int - asm "HASHCU"; - -/// Computes the hash of a `slice s` and returns it as a 256-bit unsigned integer `x`. -/// The result is the same as if an ordinary cell containing only data and references from `s` had been created -/// and its hash computed by [cell_hash]. -@pure -fun slice_hash(s: slice): int - asm "HASHSU"; - -/// Computes sha256 of the data bits of `slice` [s]. If the bit length of `s` is not divisible by eight, -/// throws a cell underflow exception. The hash value is returned as a 256-bit unsigned integer `x`. -@pure -fun string_hash(s: slice): int - asm "SHA256U"; - -/*** - # Signature checks -*/ - -/// Checks the Ed25519-`signature` of a `hash` (a 256-bit unsigned integer, usually computed as the hash of some data) -/// using [public_key] (also represented by a 256-bit unsigned integer). -/// The signature must contain at least 512 data bits; only the first 512 bits are used. -/// The result is `−1` if the signature is valid, `0` otherwise. -/// Note that `CHKSIGNU` creates a 256-bit slice with the hash and calls `CHKSIGNS`. -/// That is, if [hash] is computed as the hash of some data, these data are hashed twice, -/// the second hashing occurring inside `CHKSIGNS`. -@pure -fun check_signature(hash: int, signature: slice, public_key: int): int - asm "CHKSIGNU"; - -/// Checks whether [signature] is a valid Ed25519-signature of the data portion of `slice data` using `public_key`, -/// similarly to [check_signature]. -/// If the bit length of [data] is not divisible by eight, throws a cell underflow exception. -/// The verification of Ed25519 signatures is the standard one, -/// with sha256 used to reduce [data] to the 256-bit number that is actually signed. -@pure -fun check_data_signature(data: slice, signature: slice, public_key: int): int - asm "CHKSIGNS"; - -/*** - # Computation of boc size - The primitives below may be useful for computing storage fees of user-provided data. -*/ - -/// A non-quiet version of [compute_data_size?] that throws a cell overflow exception (`8`) on failure. -fun compute_data_size(c: cell, max_cells: int): (int, int, int) - asm "CDATASIZE"; - -/// A non-quiet version of [slice_compute_data_size?] that throws a cell overflow exception (`8`) on failure. -fun slice_compute_data_size(s: slice, max_cells: int): (int, int, int) - asm "SDATASIZE"; - -/// Returns `(x, y, z, -1)` or `(null, null, null, 0)`. -/// Recursively computes the count of distinct cells `x`, data bits `y`, and cell references `z` -/// in the DAG rooted at `cell` [c], effectively returning the total storage used by this DAG taking into account -/// the identification of equal cells. -/// The values of `x`, `y`, and `z` are computed by a depth-first traversal of this DAG, -/// with a hash table of visited cell hashes used to prevent visits of already-visited cells. -/// The total count of visited cells `x` cannot exceed non-negative [max_cells]; -/// otherwise the computation is aborted before visiting the `(max_cells + 1)`-st cell and -/// a zero flag is returned to indicate failure. If [c] is `null`, returns `x = y = z = 0`. -@pure -fun compute_data_size?(c: cell, max_cells: int): (int, int, int, int) - asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; - -/// Similar to [compute_data_size?], but accepting a `slice` [s] instead of a `cell`. -/// The returned value of `x` does not take into account the cell that contains the `slice` [s] itself; -/// however, the data bits and the cell references of [s] are accounted for in `y` and `z`. -@pure -fun slice_compute_data_size?(s: slice, max_cells: int): (int, int, int, int) - asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; - -/*** - # Debug primitives - Only works for local TVM execution with debug level verbosity -*/ - -/// Dump a variable [x] to the debug log. -fun ~dump(x: X): (X, ()) - builtin; - -/// Dump a string [x] to the debug log. -fun ~strdump(x: X): (X, ()) - builtin; - -/// Dumps the stack (at most the top 255 values) and shows the total stack depth. -fun dump_stack(): void - asm "DUMPSTK"; - -/*** - # Persistent storage save and load -*/ - -/// Returns the persistent contract storage cell. It can be parsed or modified with slice and builder primitives later. -@pure -fun get_data(): cell - asm "c4 PUSH"; - -/// Sets `cell` [c] as persistent contract data. You can update persistent contract storage with this primitive. -fun set_data(c: cell): void - asm "c4 POP"; - -/*** - # Continuation primitives -*/ -/// 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`. -@pure -fun get_c3(): continuation - asm "c3 PUSH"; - -/// Updates the current value of `c3`. Usually, it is used for updating smart contract code in run-time. -/// Note that after execution of this primitive the current code -/// (and the stack of recursive function calls) won't change, -/// but any other function call will use a function from the new code. -fun set_c3(c: continuation): void - asm "c3 POP"; - -/// Transforms a `slice` [s] into a simple ordinary continuation `c`, with `c.code = s` and an empty stack and savelist. -@pure -fun bless(s: slice): continuation - asm "BLESS"; - -/*** - # Gas related primitives -*/ - -/// Sets current gas limit `gl` to its maximal allowed value `gm`, and resets the gas credit `gc` to zero, -/// decreasing the value of `gr` by `gc` in the process. -/// In other words, the current smart contract agrees to buy some gas to finish the current transaction. -/// This action is required to process external messages, which bring no value (hence no gas) with themselves. -/// -/// For more details check [accept_message effects](https://ton.org/docs/#/smart-contracts/accept). -fun accept_message(): void - asm "ACCEPT"; - -/// Sets current gas limit `gl` to the minimum of limit and `gm`, and resets the gas credit `gc` to zero. -/// If the gas consumed so far (including the present instruction) exceeds the resulting value of `gl`, -/// an (unhandled) out of gas exception is thrown before setting new gas limits. -/// Notice that [set_gas_limit] with an argument `limit ≥ 2^63 − 1` is equivalent to [accept_message]. -fun set_gas_limit(limit: int): void - asm "SETGASLIMIT"; - -/// Commits the current state of registers `c4` (“persistent data”) and `c5` (“actions”) -/// so that the current execution is considered “successful” with the saved values even if an exception -/// in Computation Phase is thrown later. -fun commit(): void - asm "COMMIT"; - -/// Computes the amount of gas that can be bought for `amount` nanoTONs, -/// and sets `gl` accordingly in the same way as [set_gas_limit]. -fun buy_gas(amount: int): void - asm "BUYGAS"; - -/// Computes the minimum of two integers [x] and [y]. -@pure -fun min(x: int, y: int): int - asm "MIN"; - -/// Computes the maximum of two integers [x] and [y]. -@pure -fun max(x: int, y: int): int - asm "MAX"; - -/// Sorts two integers. -@pure -fun minmax(x: int, y: int): (int, int) - asm "MINMAX"; - -/// Computes the absolute value of an integer [x]. -@pure -fun abs(x: int): int - asm "ABS"; - -/// Computes the quotient and remainder of [x] / [y]. Example: divmod(112,3) = (37,1) -@pure -fun divmod(x: int, y: int): (int, int) - builtin; - -/// Computes the remainder and quotient of [x] / [y]. Example: moddiv(112,3) = (1,37) -@pure -fun moddiv(x: int, y: int): (int, int) - builtin; - -/// Computes multiple-then-divide: floor([x] * [y] / [z]). -/// The intermediate result is stored in a 513-bit integer to prevent precision loss. -@pure -fun muldiv(x: int, y: int, z: int): int - builtin; - -/// Similar to `muldiv`, but rounds the result: round([x] * [y] / [z]). -@pure -fun muldivr(x: int, y: int, z: int): int - builtin; - -/// Similar to `muldiv`, but ceils the result: ceil([x] * [y] / [z]). -@pure -fun muldivc(x: int, y: int, z: int): int - builtin; - -/// Computes the quotient and remainder of ([x] * [y] / [z]). Example: muldivmod(112,3,10) = (33,6) -@pure -fun muldivmod(x: int, y: int, z: int): (int, int) - builtin; - -/*** - # Slice primitives - - It is said that a primitive _loads_ some data, - if it returns the data and the remainder of the slice - (so it can also be used as modifying method). - - It is said that a primitive _preloads_ some data, if it returns only the data - (it can be used as non-modifying method). - - Unless otherwise stated, loading and preloading primitives read the data from a prefix of the slice. -*/ - - -/// Converts a `cell` [c] into a `slice`. Notice that [c] must be either an ordinary cell, -/// or an exotic cell (see [TVM.pdf](https://ton-blockchain.github.io/docs/tvm.pdf), 3.1.2) -/// which is automatically loaded to yield an ordinary cell `c'`, converted into a `slice` afterwards. -@pure -fun begin_parse(c: cell): slice - asm "CTOS"; - -/// Checks if [s] is empty. If not, throws an exception. -fun end_parse(s: slice): void - asm "ENDS"; - -/// Loads the first reference from the slice. -@pure -fun load_ref(s: slice): (slice, cell) - asm( -> 1 0) "LDREF"; - -/// Preloads the first reference from the slice. -@pure -fun preload_ref(s: slice): cell - asm "PLDREF"; - -/// Loads a signed [len]-bit integer from a slice [s]. -@pure -fun load_int(s: slice, len: int): (slice, int) - builtin; - -/// Loads an unsigned [len]-bit integer from a slice [s]. -@pure -fun load_uint(s: slice, len: int): (slice, int) - builtin; - -/// Preloads a signed [len]-bit integer from a slice [s]. -@pure -fun preload_int(s: slice, len: int): int - builtin; - -/// Preloads an unsigned [len]-bit integer from a slice [s]. -@pure -fun preload_uint(s: slice, len: int): int - builtin; - -/// Loads the first `0 ≤ len ≤ 1023` bits from slice [s] into a separate slice `s''`. -@pure -fun load_bits(s: slice, len: int): (slice, slice) - builtin; - -/// Preloads the first `0 ≤ len ≤ 1023` bits from slice [s] into a separate slice `s''`. -@pure -fun preload_bits(s: slice, len: int): slice - builtin; - -/// Loads serialized amount of TonCoins (any unsigned integer up to `2^120 - 1`). -@pure -fun load_grams(s: slice): (slice, int) - asm( -> 1 0) "LDGRAMS"; - -@pure -fun load_coins(s: slice): (slice, int) - asm( -> 1 0) "LDGRAMS"; - -/// Returns all but the first `0 ≤ len ≤ 1023` bits of `slice` [s]. -@pure -fun skip_bits(s: slice, len: int): slice - asm "SDSKIPFIRST"; - -@pure -fun ~skip_bits(s: slice, len: int): (slice, ()) - asm "SDSKIPFIRST"; - -/// Returns the first `0 ≤ len ≤ 1023` bits of `slice` [s]. -@pure -fun first_bits(s: slice, len: int): slice - asm "SDCUTFIRST"; - -/// Returns all but the last `0 ≤ len ≤ 1023` bits of `slice` [s]. -@pure -fun skip_last_bits(s: slice, len: int): slice - asm "SDSKIPLAST"; -@pure -fun ~skip_last_bits(s: slice, len: int): (slice, ()) - asm "SDSKIPLAST"; - -/// Returns the last `0 ≤ len ≤ 1023` bits of `slice` [s]. -@pure -fun slice_last(s: slice, len: int): slice - asm "SDCUTLAST"; - -/// Loads a dictionary `D` (HashMapE) from `slice` [s]. -/// (returns `null` if `nothing` constructor is used). -@pure -fun load_dict(s: slice): (slice, cell) - asm( -> 1 0) "LDDICT"; - -/// Preloads a dictionary `D` from `slice` [s]. -@pure -fun preload_dict(s: slice): cell - asm "PLDDICT"; - -/// Loads a dictionary as [load_dict], but returns only the remainder of the slice. -@pure -fun skip_dict(s: slice): slice - asm "SKIPDICT"; - -@pure -fun ~skip_dict(s: slice): (slice, ()) - asm "SKIPDICT"; - -/// Loads (Maybe ^Cell) from `slice` [s]. -/// In other words loads 1 bit and if it is true -/// loads first ref and return it with slice remainder -/// otherwise returns `null` and slice remainder -@pure -fun load_maybe_ref(s: slice): (slice, cell) - asm( -> 1 0) "LDOPTREF"; - -/// Preloads (Maybe ^Cell) from `slice` [s]. -@pure -fun preload_maybe_ref(s: slice): cell - asm "PLDOPTREF"; - - -/// Returns the depth of `cell` [c]. -/// If [c] has no references, then return `0`; -/// otherwise the returned value is one plus the maximum of depths of cells referred to from [c]. -/// If [c] is a `null` instead of a cell, returns zero. -@pure -fun cell_depth(c: cell): int - asm "CDEPTH"; - - -/*** - # Slice size primitives -*/ - -/// Returns the number of references in `slice` [s]. -@pure -fun slice_refs(s: slice): int - asm "SREFS"; - -/// Returns the number of data bits in `slice` [s]. -@pure -fun slice_bits(s: slice): int - asm "SBITS"; - -/// Returns both the number of data bits and the number of references in `slice` [s]. -@pure -fun slice_bits_refs(s: slice): (int, int) - asm "SBITREFS"; - -/// Checks whether a `slice` [s] is empty (i.e., contains no bits of data and no cell references). -@pure -fun slice_empty?(s: slice): int - asm "SEMPTY"; - -/// Checks whether `slice` [s] has no bits of data. -@pure -fun slice_data_empty?(s: slice): int - asm "SDEMPTY"; - -/// Checks whether `slice` [s] has no references. -@pure -fun slice_refs_empty?(s: slice): int - asm "SREMPTY"; - -/// Returns the depth of `slice` [s]. -/// If [s] has no references, then returns `0`; -/// otherwise the returned value is one plus the maximum of depths of cells referred to from [s]. -@pure -fun slice_depth(s: slice): int - asm "SDEPTH"; - -/*** - # Builder size primitives -*/ - -/// Returns the number of cell references already stored in `builder` [b] -@pure -fun builder_refs(b: builder): int - asm "BREFS"; - -/// Returns the number of data bits already stored in `builder` [b]. -@pure -fun builder_bits(b: builder): int - asm "BBITS"; - -/// Returns the depth of `builder` [b]. -/// If no cell references are stored in [b], then returns 0; -/// otherwise the returned value is one plus the maximum of depths of cells referred to from [b]. -@pure -fun builder_depth(b: builder): int - asm "BDEPTH"; - -/*** - # Builder primitives - It is said that a primitive _stores_ a value `x` into a builder `b` - if it returns a modified version of the builder `b'` with the value `x` stored at the end of it. - It can be used as non-modifying method. - - All the primitives below first check whether there is enough space in the `builder`, - and only then check the range of the value being serialized. -*/ - -/// Creates a new empty `builder`. -@pure -fun begin_cell(): builder - asm "NEWC"; - -/// Converts a `builder` into an ordinary `cell`. -@pure -fun end_cell(b: builder): cell - asm "ENDC"; - -/// Stores a reference to `cell` [c] into `builder` [b]. -@pure -fun store_ref(b: builder, c: cell): builder - asm(c b) "STREF"; - -/// Stores an unsigned [len]-bit integer `x` into `b` for `0 ≤ len ≤ 256`. -@pure -fun store_uint(b: builder, x: int, len: int): builder - builtin; - -/// Stores a signed [len]-bit integer `x` into `b` for `0 ≤ len ≤ 257`. -@pure -fun store_int(b: builder, x: int, len: int): builder - builtin; - -/// Stores `slice` [s] into `builder` [b]. -@pure -fun store_slice(b: builder, s: slice): builder - asm "STSLICER"; - -/// Stores (serializes) an integer [x] in the range `0..2^120 − 1` into `builder` [b]. -/// The serialization of [x] consists of a 4-bit unsigned big-endian integer `l`, -/// which is the smallest integer `l ≥ 0`, such that `x < 2^8l`, -/// followed by an `8l`-bit unsigned big-endian representation of [x]. -/// If [x] does not belong to the supported range, a range check exception is thrown. -/// -/// Store amounts of TonCoins to the builder as VarUInteger 16 -@pure -fun store_grams(b: builder, x: int): builder - asm "STGRAMS"; - -@pure -fun store_coins(b: builder, x: int): builder - asm "STGRAMS"; - -/// Stores dictionary `D` represented by `cell` [c] or `null` into `builder` [b]. -/// In other words, stores a `1`-bit and a reference to [c] if [c] is not `null` and `0`-bit otherwise. -@pure -fun store_dict(b: builder, c: cell): builder - asm(c b) "STDICT"; - -/// Stores (Maybe ^Cell) to builder: -/// if cell is null store 1 zero bit -/// otherwise store 1 true bit and ref to cell -@pure -fun store_maybe_ref(b: builder, c: cell): builder - asm(c b) "STOPTREF"; - - -/*** - # Address manipulation primitives - The address manipulation primitives listed below serialize and deserialize values according to the following TL-B scheme: - ```TL-B - addr_none$00 = MsgAddressExt; - addr_extern$01 len:(## 8) external_address:(bits len) - = MsgAddressExt; - anycast_info$_ depth:(#<= 30) { depth >= 1 } - rewrite_pfx:(bits depth) = Anycast; - addr_std$10 anycast:(Maybe Anycast) - workchain_id:int8 address:bits256 = MsgAddressInt; - addr_var$11 anycast:(Maybe Anycast) addr_len:(## 9) - workchain_id:int32 address:(bits addr_len) = MsgAddressInt; - _ _:MsgAddressInt = MsgAddress; - _ _:MsgAddressExt = MsgAddress; - - int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool - src:MsgAddress dest:MsgAddressInt - value:CurrencyCollection ihr_fee:Grams fwd_fee:Grams - created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed; - ext_out_msg_info$11 src:MsgAddress dest:MsgAddressExt - created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed; - ``` - A deserialized `MsgAddress` is represented by a tuple `t` as follows: - - - `addr_none` is represented by `t = (0)`, - i.e., a tuple containing exactly one integer equal to zero. - - `addr_extern` is represented by `t = (1, s)`, - where slice `s` contains the field `external_address`. In other words, ` - t` is a pair (a tuple consisting of two entries), containing an integer equal to one and slice `s`. - - `addr_std` is represented by `t = (2, u, x, s)`, - where `u` is either a `null` (if `anycast` is absent) or a slice `s'` containing `rewrite_pfx` (if anycast is present). - Next, integer `x` is the `workchain_id`, and slice `s` contains the address. - - `addr_var` is represented by `t = (3, u, x, s)`, - where `u`, `x`, and `s` have the same meaning as for `addr_std`. -*/ - -/// Loads from slice [s] the only prefix that is a valid `MsgAddress`, -/// and returns both this prefix `s'` and the remainder `s''` of [s] as slices. -@pure -fun load_msg_addr(s: slice): (slice, slice) - asm( -> 1 0) "LDMSGADDR"; - -/// Decomposes slice [s] containing a valid `MsgAddress` into a `tuple t` with separate fields of this `MsgAddress`. -/// If [s] is not a valid `MsgAddress`, a cell deserialization exception is thrown. -@pure -fun parse_addr(s: slice): tuple - asm "PARSEMSGADDR"; - -/// Parses slice [s] containing a valid `MsgAddressInt` (usually a `msg_addr_std`), -/// applies rewriting from the anycast (if present) to the same-length prefix of the address, -/// and returns both the workchain and the 256-bit address as integers. -/// If the address is not 256-bit, or if [s] is not a valid serialization of `MsgAddressInt`, -/// throws a cell deserialization exception. -@pure -fun parse_std_addr(s: slice): (int, int) - asm "REWRITESTDADDR"; - -/// A variant of [parse_std_addr] that returns the (rewritten) address as a slice [s], -/// even if it is not exactly 256 bit long (represented by a `msg_addr_var`). -@pure -fun parse_var_addr(s: slice): (int, slice) - asm "REWRITEVARADDR"; - -/*** - # Dictionary primitives -*/ - - -/// Sets the value associated with [key_len]-bit key signed index in dictionary [dict] to [value] (cell), -/// and returns the resulting dictionary. -@pure -fun idict_set_ref(dict: cell, key_len: int, index: int, value: cell): cell - asm(value index dict key_len) "DICTISETREF"; - -@pure -fun ~idict_set_ref(dict: cell, key_len: int, index: int, value: cell): (cell, ()) - asm(value index dict key_len) "DICTISETREF"; - -/// Sets the value associated with [key_len]-bit key unsigned index in dictionary [dict] to [value] (cell), -/// and returns the resulting dictionary. -@pure -fun udict_set_ref(dict: cell, key_len: int, index: int, value: cell): cell - asm(value index dict key_len) "DICTUSETREF"; - -@pure -fun ~udict_set_ref(dict: cell, key_len: int, index: int, value: cell): (cell, ()) - asm(value index dict key_len) "DICTUSETREF"; - -@pure -fun idict_get_ref(dict: cell, key_len: int, index: int): cell - asm(index dict key_len) "DICTIGETOPTREF"; - -@pure -fun idict_get_ref?(dict: cell, key_len: int, index: int): (cell, int) - asm(index dict key_len) "DICTIGETREF" "NULLSWAPIFNOT"; - -@pure -fun udict_get_ref?(dict: cell, key_len: int, index: int): (cell, int) - asm(index dict key_len) "DICTUGETREF" "NULLSWAPIFNOT"; - -@pure -fun idict_set_get_ref(dict: cell, key_len: int, index: int, value: cell): (cell, cell) - asm(value index dict key_len) "DICTISETGETOPTREF"; - -@pure -fun udict_set_get_ref(dict: cell, key_len: int, index: int, value: cell): (cell, cell) - asm(value index dict key_len) "DICTUSETGETOPTREF"; - -@pure -fun idict_delete?(dict: cell, key_len: int, index: int): (cell, int) - asm(index dict key_len) "DICTIDEL"; - -@pure -fun udict_delete?(dict: cell, key_len: int, index: int): (cell, int) - asm(index dict key_len) "DICTUDEL"; - -@pure -fun idict_get?(dict: cell, key_len: int, index: int): (slice, int) - asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; - -@pure -fun udict_get?(dict: cell, key_len: int, index: int): (slice, int) - asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; - -@pure -fun idict_delete_get?(dict: cell, key_len: int, index: int): (cell, slice, int) - asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; - -@pure -fun udict_delete_get?(dict: cell, key_len: int, index: int): (cell, slice, int) - asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; - -@pure -fun ~idict_delete_get?(dict: cell, key_len: int, index: int): (cell, (slice, int)) - asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; - -@pure -fun ~udict_delete_get?(dict: cell, key_len: int, index: int): (cell, (slice, int)) - asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; - -@pure -fun udict_set(dict: cell, key_len: int, index: int, value: slice): cell - asm(value index dict key_len) "DICTUSET"; - -@pure -fun ~udict_set(dict: cell, key_len: int, index: int, value: slice): (cell, ()) - asm(value index dict key_len) "DICTUSET"; - -@pure -fun idict_set(dict: cell, key_len: int, index: int, value: slice): cell - asm(value index dict key_len) "DICTISET"; - -@pure -fun ~idict_set(dict: cell, key_len: int, index: int, value: slice): (cell, ()) - asm(value index dict key_len) "DICTISET"; - -@pure -fun dict_set(dict: cell, key_len: int, index: slice, value: slice): cell - asm(value index dict key_len) "DICTSET"; - -@pure -fun ~dict_set(dict: cell, key_len: int, index: slice, value: slice): (cell, ()) - asm(value index dict key_len) "DICTSET"; - -@pure -fun udict_add?(dict: cell, key_len: int, index: int, value: slice): (cell, int) - asm(value index dict key_len) "DICTUADD"; - -@pure -fun udict_replace?(dict: cell, key_len: int, index: int, value: slice): (cell, int) - asm(value index dict key_len) "DICTUREPLACE"; - -@pure -fun idict_add?(dict: cell, key_len: int, index: int, value: slice): (cell, int) - asm(value index dict key_len) "DICTIADD"; - -@pure -fun idict_replace?(dict: cell, key_len: int, index: int, value: slice): (cell, int) - asm(value index dict key_len) "DICTIREPLACE"; - -@pure -fun udict_set_builder(dict: cell, key_len: int, index: int, value: builder): cell - asm(value index dict key_len) "DICTUSETB"; - -@pure -fun ~udict_set_builder(dict: cell, key_len: int, index: int, value: builder): (cell, ()) - asm(value index dict key_len) "DICTUSETB"; - -@pure -fun idict_set_builder(dict: cell, key_len: int, index: int, value: builder): cell - asm(value index dict key_len) "DICTISETB"; - -@pure -fun ~idict_set_builder(dict: cell, key_len: int, index: int, value: builder): (cell, ()) - asm(value index dict key_len) "DICTISETB"; - -@pure -fun dict_set_builder(dict: cell, key_len: int, index: slice, value: builder): cell - asm(value index dict key_len) "DICTSETB"; - -@pure -fun ~dict_set_builder(dict: cell, key_len: int, index: slice, value: builder): (cell, ()) - asm(value index dict key_len) "DICTSETB"; - -@pure -fun udict_add_builder?(dict: cell, key_len: int, index: int, value: builder): (cell, int) - asm(value index dict key_len) "DICTUADDB"; - -@pure -fun udict_replace_builder?(dict: cell, key_len: int, index: int, value: builder): (cell, int) - asm(value index dict key_len) "DICTUREPLACEB"; - -@pure -fun idict_add_builder?(dict: cell, key_len: int, index: int, value: builder): (cell, int) - asm(value index dict key_len) "DICTIADDB"; - -@pure -fun idict_replace_builder?(dict: cell, key_len: int, index: int, value: builder): (cell, int) - asm(value index dict key_len) "DICTIREPLACEB"; - -@pure -fun udict_delete_get_min(dict: cell, key_len: int): (cell, int, slice, int) - asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; - -@pure -fun ~udict_delete_get_min(dict: cell, key_len: int): (cell, (int, slice, int)) - asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; - -@pure -fun idict_delete_get_min(dict: cell, key_len: int): (cell, int, slice, int) - asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; - -@pure -fun ~idict_delete_get_min(dict: cell, key_len: int): (cell, (int, slice, int)) - asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; - -@pure -fun dict_delete_get_min(dict: cell, key_len: int): (cell, slice, slice, int) - asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; - -@pure -fun ~dict_delete_get_min(dict: cell, key_len: int): (cell, (slice, slice, int)) - asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; - -@pure -fun udict_delete_get_max(dict: cell, key_len: int): (cell, int, slice, int) - asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; - -@pure -fun ~udict_delete_get_max(dict: cell, key_len: int): (cell, (int, slice, int)) - asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; - -@pure -fun idict_delete_get_max(dict: cell, key_len: int): (cell, int, slice, int) - asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; - -@pure -fun ~idict_delete_get_max(dict: cell, key_len: int): (cell, (int, slice, int)) - asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; - -@pure -fun dict_delete_get_max(dict: cell, key_len: int): (cell, slice, slice, int) - asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; - -@pure -fun ~dict_delete_get_max(dict: cell, key_len: int): (cell, (slice, slice, int)) - asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; - -@pure -fun udict_get_min?(dict: cell, key_len: int): (int, slice, int) - asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; - -@pure -fun udict_get_max?(dict: cell, key_len: int): (int, slice, int) - asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; - -@pure -fun udict_get_min_ref?(dict: cell, key_len: int): (int, cell, int) - asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; - -@pure -fun udict_get_max_ref?(dict: cell, key_len: int): (int, cell, int) - asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; - -@pure -fun idict_get_min?(dict: cell, key_len: int): (int, slice, int) - asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; - -@pure -fun idict_get_max?(dict: cell, key_len: int): (int, slice, int) - asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; - -@pure -fun idict_get_min_ref?(dict: cell, key_len: int): (int, cell, int) - asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; - -@pure -fun idict_get_max_ref?(dict: cell, key_len: int): (int, cell, int) - asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; - -@pure -fun udict_get_next?(dict: cell, key_len: int, pivot: int): (int, slice, int) - asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; - -@pure -fun udict_get_nexteq?(dict: cell, key_len: int, pivot: int): (int, slice, int) - asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; - -@pure -fun udict_get_prev?(dict: cell, key_len: int, pivot: int): (int, slice, int) - asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; - -@pure -fun udict_get_preveq?(dict: cell, key_len: int, pivot: int): (int, slice, int) - asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; - -@pure -fun idict_get_next?(dict: cell, key_len: int, pivot: int): (int, slice, int) - asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; - -@pure -fun idict_get_nexteq?(dict: cell, key_len: int, pivot: int): (int, slice, int) - asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; - -@pure -fun idict_get_prev?(dict: cell, key_len: int, pivot: int): (int, slice, int) - asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; - -@pure -fun idict_get_preveq?(dict: cell, key_len: int, pivot: int): (int, slice, int) - asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; - -/// Creates an empty dictionary, which is actually a null value. Equivalent to PUSHNULL -@pure -fun new_dict(): cell - asm "NEWDICT"; - -/// Checks whether a dictionary is empty. -@pure -fun dict_empty?(c: cell): int - asm "DICTEMPTY"; - - -/* Prefix dictionary primitives */ -@pure -fun pfxdict_get?(dict: cell, key_len: int, key: slice): (slice, slice, slice, int) - asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; - -@pure -fun pfxdict_set?(dict: cell, key_len: int, key: slice, value: slice): (cell, int) - asm(value key dict key_len) "PFXDICTSET"; - -@pure -fun pfxdict_delete?(dict: cell, key_len: int, key: slice): (cell, int) - asm(key dict key_len) "PFXDICTDEL"; - -/// Returns the value of the global configuration parameter with integer index `i` as a `cell` or `null` value. -@pure -fun config_param(x: int): cell - asm "CONFIGOPTPARAM"; - -/// Creates an output action which would reserve exactly amount nanotoncoins (if mode = 0), at most amount nanotoncoins (if mode = 2), or all but amount nanotoncoins (if mode = 1 or mode = 3), from the remaining balance of the account. It is roughly equivalent to creating an outbound message carrying amount nanotoncoins (or b − amount nanotoncoins, where b is the remaining balance) to oneself, so that the subsequent output actions would not be able to spend more money than the remainder. Bit +2 in mode means that the external action does not fail if the specified amount cannot be reserved; instead, all remaining balance is reserved. Bit +8 in mode means `amount <- -amount` before performing any further actions. Bit +4 in mode means that amount is increased by the original balance of the current account (before the compute phase), including all extra currencies, before performing any other checks and actions. Currently, amount must be a non-negative integer, and mode must be in the range 0..15. -fun raw_reserve(amount: int, mode: int): void - asm "RAWRESERVE"; - -/// Similar to raw_reserve, but also accepts a dictionary extra_amount (represented by a cell or null) with extra currencies. In this way currencies other than TonCoin can be reserved. -fun raw_reserve_extra(amount: int, extra_amount: cell, mode: int): void - asm "RAWRESERVEX"; - -/// Sends a raw message contained in msg, which should contain a correctly serialized object Message X, with the only exception that the source address is allowed to have dummy value addr_none (to be automatically replaced with the current smart contract address), and ihr_fee, fwd_fee, created_lt and created_at fields can have arbitrary values (to be rewritten with correct values during the action phase of the current transaction). Integer parameter mode contains the flags. Currently mode = 0 is used for ordinary messages; mode = 128 is used for messages that are to carry all the remaining balance of the current smart contract (instead of the value originally indicated in the message); mode = 64 is used for messages that carry all the remaining value of the inbound message in addition to the value initially indicated in the new message (if bit 0 is not set, the gas fees are deducted from this amount); mode' = mode + 1 means that the sender wants to pay transfer fees separately; mode' = mode + 2 means that any errors arising while processing this message during the action phase should be ignored. Finally, mode' = mode + 32 means that the current account must be destroyed if its resulting balance is zero. This flag is usually employed together with +128. -fun send_raw_message(msg: cell, mode: int): void - asm "SENDRAWMSG"; - -/// Creates an output action that would change this smart contract code to that given by cell new_code. Notice that this change will take effect only after the successful termination of the current run of the smart contract -fun set_code(new_code: cell): void - asm "SETCODE"; - -/// Generates a new pseudo-random unsigned 256-bit integer x. The algorithm is as follows: if r is the old value of the random seed, considered as a 32-byte array (by constructing the big-endian representation of an unsigned 256-bit integer), then its sha512(r) is computed; the first 32 bytes of this hash are stored as the new value r' of the random seed, and the remaining 32 bytes are returned as the next random value x. -fun random(): int - asm "RANDU256"; - -/// Generates a new pseudo-random integer z in the range 0..range−1 (or range..−1, if range < 0). More precisely, an unsigned random value x is generated as in random; then z := x * range / 2^256 is computed. -fun rand(range: int): int - asm "RAND"; - -/// Returns the current random seed as an unsigned 256-bit Integer. -@pure -fun get_seed(): int - asm "RANDSEED"; - -/// Sets the random seed to unsigned 256-bit seed. -fun set_seed(seed: int): void - asm "SETRAND"; - -/// Mixes unsigned 256-bit integer x into the random seed r by setting the random seed to sha256 of the concatenation of two 32-byte strings: the first with the big-endian representation of the old seed r, and the second with the big-endian representation of x. -fun randomize(x: int): void - asm "ADDRAND"; - -/// Equivalent to randomize(cur_lt());. -fun randomize_lt(): void - asm "LTIME" "ADDRAND"; - -/// Checks whether the data parts of two slices coinside -@pure -fun equal_slice_bits(a: slice, b: slice): int - asm "SDEQ"; - -/// Concatenates two builders -@pure -fun store_builder(to: builder, from: builder): builder - asm "STBR"; diff --git a/crypto/smartcont/tolk-stdlib/common.tolk b/crypto/smartcont/tolk-stdlib/common.tolk new file mode 100644 index 000000000..de711f7df --- /dev/null +++ b/crypto/smartcont/tolk-stdlib/common.tolk @@ -0,0 +1,766 @@ +// 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.6 + +/** + Tuple manipulation primitives. + Elements of a tuple can be of arbitrary type. + Note that atomic type `tuple` can't be cast to composite tuple type (e.g. `[int, cell]`) and vise versa. + */ + +/// Creates a tuple with zero elements. +@pure +fun createEmptyTuple(): tuple + asm "NIL"; + +/// Appends a value to tuple, resulting in `Tuple t' = (x1, ..., xn, value)`. +/// If its size exceeds 255, throws a type check exception. +@pure +fun tuplePush(t: tuple, value: X): tuple + asm "TPUSH"; + +@pure +fun ~tuplePush(t: tuple, value: X): (tuple, ()) + asm "TPUSH"; + +/// Returns the first element of a non-empty tuple. +@pure +fun tupleFirst(t: tuple): X + asm "FIRST"; + +/// Returns the [`index`]-th element of a tuple. +@pure +fun tupleAt(t: tuple, index: int): X + builtin; + +/// Returns the size of a tuple (elements count in it). +@pure +fun tupleSize(t: tuple): int + asm "TLEN"; + +/// Returns the last element of a non-empty tuple. +@pure +fun tupleLast(t: tuple): int + asm "LAST"; + + +/** + Mathematical primitives. + */ + +/// Computes the minimum of two integers. +@pure +fun min(x: int, y: int): int + asm "MIN"; + +/// Computes the maximum of two integers. +@pure +fun max(x: int, y: int): int + asm "MAX"; + +/// Sorts two integers. +@pure +fun minMax(x: int, y: int): (int, int) + asm "MINMAX"; + +/// Computes the absolute value of an integer. +@pure +fun abs(x: int): int + asm "ABS"; + +/// Returns the sign of an integer: `-1` if x < 0, `0` if x == 0, `1` if x > 0. +@pure +fun sign(x: int): int + asm "SGN"; + +/// Computes the quotient and remainder of [x] / [y]. Example: divMod(112,3) = (37,1) +@pure +fun divMod(x: int, y: int): (int, int) + asm "DIVMOD"; + +/// Computes the remainder and quotient of [x] / [y]. Example: modDiv(112,3) = (1,37) +@pure +fun modDiv(x: int, y: int): (int, int) + asm(-> 1 0) "DIVMOD"; + +/// Computes multiple-then-divide: floor([x] * [y] / [z]). +/// The intermediate result is stored in a 513-bit integer to prevent precision loss. +@pure +fun mulDivFloor(x: int, y: int, z: int): int + builtin; + +/// Similar to `mulDivFloor`, but rounds the result: round([x] * [y] / [z]). +@pure +fun mulDivRound(x: int, y: int, z: int): int + builtin; + +/// Similar to `mulDivFloor`, but ceils the result: ceil([x] * [y] / [z]). +@pure +fun mulDivCeil(x: int, y: int, z: int): int + builtin; + +/// Computes the quotient and remainder of ([x] * [y] / [z]). Example: mulDivMod(112,3,10) = (33,6) +@pure +fun mulDivMod(x: int, y: int, z: int): (int, int) + builtin; + + +/** + Global getters of environment and contract state. + */ + +const MASTERCHAIN = -1; +const BASECHAIN = 0; + +/// Returns current Unix timestamp (in seconds). +@pure +fun now(): int + asm "NOW"; + +/// Returns the internal address of the current smart contract as a Slice with a `MsgAddressInt`. +/// If necessary, it can be parsed further using primitives such as [parseStandardAddress]. +@pure +fun getMyAddress(): slice + asm "MYADDR"; + +/// Returns the balance (in nanotoncoins) of the smart contract at the start of Computation Phase. +/// Note that RAW primitives such as [sendMessage] do not update this field. +@pure +fun getMyOriginalBalance(): int + asm "BALANCE" "FIRST"; + +/// Same as [getMyOriginalBalance], but returns a tuple: +/// `int` — balance in nanotoncoins; +/// `cell` — a dictionary with 32-bit keys representing the balance of "extra currencies". +@pure +fun getMyOriginalBalanceWithExtraCurrencies(): [int, cell] + asm "BALANCE"; + +/// Returns the logical time of the current transaction. +@pure +fun getLogicalTime(): int + asm "LTIME"; + +/// Returns the starting logical time of the current block. +@pure +fun getCurrentBlockLogicalTime(): int + asm "BLOCKLT"; + +/// Returns the value of the global configuration parameter with integer index `i` as a `cell` or `null` value. +@pure +fun getBlockchainConfigParam(x: int): cell + asm "CONFIGOPTPARAM"; + +/// Returns the persistent contract storage cell. It can be parsed or modified with slice and builder primitives later. +@pure +fun getContractData(): cell + asm "c4 PUSH"; + +/// Sets `cell` [c] as persistent contract data. You can update persistent contract storage with this primitive. +fun setContractData(c: cell): void + asm "c4 POP"; + +/// Retrieves code of smart-contract from c7 +@pure +fun getContractCode(): cell + asm "MYCODE"; + +/// Creates an output action that would change this smart contract code to that given by cell [newCode]. +/// Notice that this change will take effect only after the successful termination of the current run of the smart contract. +fun setContractCodePostponed(newCode: cell): void + asm "SETCODE"; + +/// Commits the current state of registers `c4` (“persistent data”) and `c5` (“actions”) +/// so that the current execution is considered “successful” with the saved values even if an exception +/// in Computation Phase is thrown later. +fun commitContractDataAndActions(): void + asm "COMMIT"; + + +/** + Signature checks, hashing, cryptography. + */ + +/// Computes the representation hash of a `cell` [c] and returns it as a 256-bit unsigned integer `x`. +/// Useful for signing and checking signatures of arbitrary entities represented by a tree of cells. +@pure +fun cellHash(c: cell): int + asm "HASHCU"; + +/// Computes the hash of a `slice s` and returns it as a 256-bit unsigned integer `x`. +/// The result is the same as if an ordinary cell containing only data and references from `s` had been created +/// and its hash computed by [cellHash]. +@pure +fun sliceHash(s: slice): int + asm "HASHSU"; + +/// Computes sha256 of the data bits of `slice` [s]. If the bit length of `s` is not divisible by eight, +/// throws a cell underflow exception. The hash value is returned as a 256-bit unsigned integer `x`. +@pure +fun stringHash(s: slice): int + asm "SHA256U"; + +/// Checks the Ed25519-`signature` of a `hash` (a 256-bit unsigned integer, usually computed as the hash of some data) +/// using [publicKey] (also represented by a 256-bit unsigned integer). +/// The signature must contain at least 512 data bits; only the first 512 bits are used. +/// The result is `−1` if the signature is valid, `0` otherwise. +/// Note that `CHKSIGNU` creates a 256-bit slice with the hash and calls `CHKSIGNS`. +/// That is, if [hash] is computed as the hash of some data, these data are hashed twice, +/// the second hashing occurring inside `CHKSIGNS`. +@pure +fun isSignatureValid(hash: int, signature: slice, publicKey: int): int + asm "CHKSIGNU"; + +/// Checks whether [signature] is a valid Ed25519-signature of the data portion of `slice data` using `publicKey`, +/// similarly to [isSignatureValid]. +/// If the bit length of [data] is not divisible by eight, throws a cell underflow exception. +/// The verification of Ed25519 signatures is the standard one, +/// with sha256 used to reduce [data] to the 256-bit number that is actually signed. +@pure +fun isSliceSignatureValid(data: slice, signature: slice, publicKey: int): int + asm "CHKSIGNS"; + +/// Generates a new pseudo-random unsigned 256-bit integer x. +fun random(): int + asm "RANDU256"; + +/// Generates a new pseudo-random integer z in the range 0..range−1 (or range..−1, if range < 0). +/// More precisely, an unsigned random value x is generated as in random; then z := x * range / 2^256 is computed. +fun randomRange(range: int): int + asm "RAND"; + +/// Returns the current random seed as an unsigned 256-bit integer. +@pure +fun randomGetSeed(): int + asm "RANDSEED"; + +/// Sets the random seed to unsigned 256-bit seed. +fun randomSetSeed(seed: int): void + asm "SETRAND"; + +/// Initializes (mixes) random seed with unsigned 256-bit integer x. +fun randomizeBy(x: int): void + asm "ADDRAND"; + +/// Initializes random seed using current time. Don't forget to call this before calling `random`! +fun randomizeByLogicalTime(): void + asm "LTIME" "ADDRAND"; + + +/** + Size computation primitives. + They may be useful for computing storage fees of user-provided data. + */ + +/// Returns `(x, y, z, -1)` or `(null, null, null, 0)`. +/// Recursively computes the count of distinct cells `x`, data bits `y`, and cell references `z` +/// in the DAG rooted at `cell` [c], effectively returning the total storage used by this DAG taking into account +/// the identification of equal cells. +/// The values of `x`, `y`, and `z` are computed by a depth-first traversal of this DAG, +/// with a hash table of visited cell hashes used to prevent visits of already-visited cells. +/// The total count of visited cells `x` cannot exceed non-negative [maxCells]; +/// otherwise the computation is aborted before visiting the `(maxCells + 1)`-st cell and +/// a zero flag is returned to indicate failure. If [c] is `null`, returns `x = y = z = 0`. +@pure +fun calculateCellSize(c: cell, maxCells: int): (int, int, int, int) + asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +/// Similar to [calculateCellSize], but accepting a `slice` [s] instead of a `cell`. +/// The returned value of `x` does not take into account the cell that contains the `slice` [s] itself; +/// however, the data bits and the cell references of [s] are accounted for in `y` and `z`. +@pure +fun calculateSliceSize(s: slice, maxCells: int): (int, int, int, int) + asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +/// A non-quiet version of [calculateCellSize] that throws a cell overflow exception (`8`) on failure. +fun calculateCellSizeStrict(c: cell, maxCells: int): (int, int, int) + asm "CDATASIZE"; + +/// A non-quiet version of [calculateSliceSize] that throws a cell overflow exception (`8`) on failure. +fun calculateSliceSizeStrict(s: slice, maxCells: int): (int, int, int) + asm "SDATASIZE"; + +/// Returns the depth of `cell` [c]. +/// If [c] has no references, then return `0`; +/// otherwise the returned value is one plus the maximum of depths of cells referred to from [c]. +/// If [c] is a `null` instead of a cell, returns zero. +@pure +fun getCellDepth(c: cell): int + asm "CDEPTH"; + +/// Returns the depth of `slice` [s]. +/// If [s] has no references, then returns `0`; +/// otherwise the returned value is one plus the maximum of depths of cells referred to from [s]. +@pure +fun getSliceDepth(s: slice): int + asm "SDEPTH"; + +/// Returns the depth of `builder` [b]. +/// If no cell references are stored in [b], then returns 0; +/// otherwise the returned value is one plus the maximum of depths of cells referred to from [b]. +@pure +fun getBuilderDepth(b: builder): int + asm "BDEPTH"; + + +/** + Debug primitives. + Only works for local TVM execution with debug level verbosity. + */ + +/// Dump a variable [x] to the debug log. +fun debugPrint(x: X): void + builtin; + +/// Dump a string [x] to the debug log. +fun debugPrintString(x: X): void + builtin; + +/// Dumps the stack (at most the top 255 values) and shows the total stack depth. +fun debugDumpStack(): void + builtin; + + +/** + Slice primitives: parsing cells. + When you _load_ some data, you mutate the slice (shifting an internal pointer on the stack). + When you _preload_ some data, you just get the result without mutating the slice. + */ + +/// Converts a `cell` [c] into a `slice`. Notice that [c] must be either an ordinary cell, +/// or an exotic cell (see [TVM.pdf](https://ton-blockchain.github.io/docs/tvm.pdf), 3.1.2) +/// which is automatically loaded to yield an ordinary cell `c'`, converted into a `slice` afterwards. +@pure +fun beginParse(c: cell): slice + asm "CTOS"; + +/// Checks if slice is empty. If not, throws an exception. +fun assertEndOfSlice(s: slice): void + asm "ENDS"; + +/// Loads the next reference from the slice. +@pure +fun loadRef(s: slice): (slice, cell) + asm( -> 1 0) "LDREF"; + +/// Preloads the next reference from the slice. +@pure +fun preloadRef(s: slice): cell + asm "PLDREF"; + +/// Loads a signed [len]-bit integer from a slice. +@pure +fun loadInt(s: slice, len: int): (slice, int) + builtin; + +/// Loads an unsigned [len]-bit integer from a slice. +@pure +fun loadUint(s: slice, len: int): (slice, int) + builtin; + +/// Loads the first `0 ≤ len ≤ 1023` bits from slice [s] into a separate slice `s''`. +@pure +fun loadBits(s: slice, len: int): (slice, slice) + builtin; + +/// Preloads a signed [len]-bit integer from a slice. +@pure +fun preloadInt(s: slice, len: int): int + builtin; + +/// Preloads an unsigned [len]-bit integer from a slice. +@pure +fun preloadUint(s: slice, len: int): int + builtin; + +/// Preloads the first `0 ≤ len ≤ 1023` bits from slice [s] into a separate slice. +@pure +fun preloadBits(s: slice, len: int): slice + builtin; + +/// Loads serialized amount of Toncoins (any unsigned integer up to `2^120 - 1`). +@pure +fun loadCoins(s: slice): (slice, int) + asm( -> 1 0) "LDGRAMS"; + +/// Loads bool (-1 or 0) from a slice +@pure +fun loadBool(s: slice): (slice, int) + asm( -> 1 0) "1 LDI"; + +/// Shifts a slice pointer to [len] bits forward, mutating the slice. +@pure +fun skipBits(s: slice, len: int): slice + asm "SDSKIPFIRST"; // todo make mutating +@pure +fun ~skipBits(s: slice, len: int): (slice, ()) + asm "SDSKIPFIRST"; + +/// Returns the first `0 ≤ len ≤ 1023` bits of a slice. +@pure +fun getFirstBits(s: slice, len: int): slice + asm "SDCUTFIRST"; + +/// Returns all but the last `0 ≤ len ≤ 1023` bits of a slice. +@pure +fun removeLastBits(s: slice, len: int): slice + asm "SDSKIPLAST"; // todo make mutating +@pure +fun ~removeLastBits(s: slice, len: int): (slice, ()) + asm "SDSKIPLAST"; + +/// Returns the last `0 ≤ len ≤ 1023` bits of a slice. +@pure +fun getLastBits(s: slice, len: int): slice + asm "SDCUTLAST"; + +/// Loads a dictionary (TL HashMapE structure, represented as TVM cell) from a slice. +/// Returns `null` if `nothing` constructor is used. +@pure +fun loadDict(s: slice): (slice, cell) + asm( -> 1 0) "LDDICT"; + +/// Preloads a dictionary (cell) from a slice. +@pure +fun preloadDict(s: slice): cell + asm "PLDDICT"; + +/// Loads a dictionary as [loadDict], but returns only the remainder of the slice. +@pure +fun skipDict(s: slice): slice + asm "SKIPDICT"; // todo make mutating +@pure +fun ~skipDict(s: slice): (slice, ()) + asm "SKIPDICT"; + +/// Loads (Maybe ^Cell) from a slice. +/// In other words, loads 1 bit: if it's true, loads the first ref, otherwise returns `null`. +@pure +fun loadMaybeRef(s: slice): (slice, cell) + asm( -> 1 0) "LDOPTREF"; + +/// Preloads (Maybe ^Cell) from a slice. +@pure +fun preloadMaybeRef(s: slice): cell + asm "PLDOPTREF"; + +/// Loads (Maybe ^Cell), but returns only the remainder of the slice. +@pure +fun ~skipMaybeRef(s: slice): (slice, ()) + asm "SKIPOPTREF"; + +/** + Builder primitives: constructing cells. + When you _store_ some data, you mutate the builder (shifting an internal pointer on the stack). + All the primitives below first check whether there is enough space in the `builder`, + and only then check the range of the value being serialized. + */ + +/// Creates a new empty builder. +@pure +fun beginCell(): builder + asm "NEWC"; + +/// Converts a builder into an ordinary `cell`. +@pure +fun endCell(b: builder): cell + asm "ENDC"; + +/// Stores a reference to a cell into a builder. +@pure +fun storeRef(b: builder, c: cell): builder + asm(c b) "STREF"; + +/// Stores a signed [len]-bit integer into a builder (`0 ≤ len ≤ 257`). +@pure +fun storeInt(b: builder, x: int, len: int): builder + builtin; + +/// Stores an unsigned [len]-bit integer into a builder (`0 ≤ len ≤ 256`). +@pure +fun storeUint(b: builder, x: int, len: int): builder + builtin; + +/// Stores a slice into a builder. +@pure +fun storeSlice(b: builder, s: slice): builder + asm "STSLICER"; + +/// Stores amount of Toncoins into a builder. +@pure +fun storeCoins(b: builder, x: int): builder + asm "STGRAMS"; + +/// Stores bool (-1 or 0) into a builder. +/// Attention: true value is `-1`, not 1! If you pass `1` here, TVM will throw an exception. +@pure +fun storeBool(b: builder, x: int): builder + asm(x b) "1 STI"; + +/// Stores dictionary (represented by TVM `cell` or `null`) into a builder. +/// In other words, stores a `1`-bit and a reference to [c] if [c] is not `null` and `0`-bit otherwise. +@pure +fun storeDict(b: builder, c: cell): builder + asm(c b) "STDICT"; + +/// Stores (Maybe ^Cell) into a builder. +/// In other words, if cell is `null`, store '0' bit; otherwise, store '1' and a ref to [c]. +@pure +fun storeMaybeRef(b: builder, c: cell): builder + asm(c b) "STOPTREF"; + +/// Concatenates two builders. +@pure +fun storeBuilder(to: builder, from: builder): builder + asm "STBR"; + +@pure +fun storeAddressNone(b: builder): builder + asm "0 PUSHINT" "SWAP" "2 STU"; +@pure +fun ~storeAddressNone(b: builder): (builder, ()) + asm "b{00} STSLICECONST"; + + +/** + Slice size primitives. + */ + +/// Returns the number of references in a slice. +@pure +fun getRemainingRefsCount(s: slice): int + asm "SREFS"; + +/// Returns the number of data bits in a slice. +@pure +fun getRemainingBitsCount(s: slice): int + asm "SBITS"; + +/// Returns both the number of data bits and the number of references in a slice. +@pure +fun getRemainingBitsAndRefsCount(s: slice): (int, int) + asm "SBITREFS"; + +/// Checks whether a slice is empty (i.e., contains no bits of data and no cell references). +@pure +fun isEndOfSlice(s: slice): int + asm "SEMPTY"; + +/// Checks whether a slice has no bits of data. +@pure +fun isEndOfSliceBits(s: slice): int + asm "SDEMPTY"; + +/// Checks whether a slice has no references. +@pure +fun isEndOfSliceRefs(s: slice): int + asm "SREMPTY"; + +/// Checks whether data parts of two slices coinside. +@pure +fun isSliceBitsEqual(a: slice, b: slice): int + asm "SDEQ"; + +/// Returns the number of cell references already stored in a builder. +@pure +fun getBuilderRefsCount(b: builder): int + asm "BREFS"; + +/// Returns the number of data bits already stored in a builder. +@pure +fun getBuilderBitsCount(b: builder): int + asm "BBITS"; + + +/** + Address manipulation primitives. + The address manipulation primitives listed below serialize and deserialize values according to the following TL-B scheme: + ```TL-B + addr_none$00 = MsgAddressExt; + addr_extern$01 len:(## 8) external_address:(bits len) + = MsgAddressExt; + anycast_info$_ depth:(#<= 30) { depth >= 1 } + rewrite_pfx:(bits depth) = Anycast; + addr_std$10 anycast:(Maybe Anycast) + workchain_id:int8 address:bits256 = MsgAddressInt; + addr_var$11 anycast:(Maybe Anycast) addr_len:(## 9) + workchain_id:int32 address:(bits addr_len) = MsgAddressInt; + _ _:MsgAddressInt = MsgAddress; + _ _:MsgAddressExt = MsgAddress; + + int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool + src:MsgAddress dest:MsgAddressInt + value:CurrencyCollection ihr_fee:Grams fwd_fee:Grams + created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed; + ext_out_msg_info$11 src:MsgAddress dest:MsgAddressExt + created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed; + ``` + A deserialized `MsgAddress` is represented by a tuple `t` as follows: + + - `addr_none` is represented by `t = (0)`, + i.e., a tuple containing exactly one integer equal to zero. + - `addr_extern` is represented by `t = (1, s)`, + where slice `s` contains the field `external_address`. In other words, ` + t` is a pair (a tuple consisting of two entries), containing an integer equal to one and slice `s`. + - `addr_std` is represented by `t = (2, u, x, s)`, + where `u` is either a `null` (if `anycast` is absent) or a slice `s'` containing `rewrite_pfx` (if anycast is present). + Next, integer `x` is the `workchain_id`, and slice `s` contains the address. + - `addr_var` is represented by `t = (3, u, x, s)`, + where `u`, `x`, and `s` have the same meaning as for `addr_std`. + */ + +/// Loads from slice [s] the only prefix that is a valid `MsgAddress`, +/// and returns both this prefix `s'` and the remainder `s''` of [s] as slices. +@pure +fun loadAddress(s: slice): (slice, slice) + asm( -> 1 0) "LDMSGADDR"; // todo make mutating + +/// Decomposes slice [s] containing a valid `MsgAddress` into a `tuple t` with separate fields of this `MsgAddress`. +/// If [s] is not a valid `MsgAddress`, a cell deserialization exception is thrown. +@pure +fun parseAddress(s: slice): tuple + asm "PARSEMSGADDR"; + +/// Parses slice [s] containing a valid `MsgAddressInt` (usually a `msg_addr_std`), +/// applies rewriting from the anycast (if present) to the same-length prefix of the address, +/// and returns both the workchain and the 256-bit address as integers. +/// If the address is not 256-bit, or if [s] is not a valid serialization of `MsgAddressInt`, +/// throws a cell deserialization exception. +@pure +fun parseStandardAddress(s: slice): (int, int) + asm "REWRITESTDADDR"; + +/// Creates a slice representing TL addr_none$00 (two `0` bits). +@pure +fun createAddressNone(): slice + asm "b{00} PUSHSLICE"; + +/// Returns if a slice pointer contains an empty address (`-1` for true, `0` for false, as always). +/// In other words, a slice starts with two `0` bits (TL addr_none$00). +@pure +fun addressIsNone(s: slice): int + asm "2 PLDU" "0 EQINT"; + + +/** + Reserving Toncoins on balance and its flags. + */ + +/// mode = 0: Reserve exact amount of nanotoncoins +const RESERVE_MODE_EXACT_AMOUNT = 0; +/// +1: Actually reserves all but amount, meaning `currentContractBalance - amount` +const RESERVE_MODE_ALL_BUT_AMOUNT = 1; +/// +2: Actually set `min(amount, currentContractBalance)` (without this mode, if amount is greater, the action will fail) +const RESERVE_MODE_AT_MOST = 2; +/// +4: [amount] is increased by the _original_ balance of the current account (before the compute phase). +const RESERVE_MODE_INCREASE_BY_ORIGINAL_BALANCE = 4; +/// +8: Actually sets `amount = -amount` before performing any further actions. +const RESERVE_MODE_NEGATE_AMOUNT = 8; +/// +16: If this action fails, the transaction will be bounced. +const RESERVE_MODE_BOUNCE_ON_ACTION_FAIL = 16; + +/// Creates an output action which would reserve Toncoins on balance. +/// For [reserveMode] consider constants above. +fun reserveToncoinsOnBalance(nanoTonCoins: int, reserveMode: int): void + asm "RAWRESERVE"; + +/// Similar to [reserveToncoinsOnBalance], but also accepts a dictionary extraAmount (represented by a cell or null) +/// with extra currencies. In this way currencies other than Toncoin can be reserved. +fun reserveExtraCurrenciesOnBalance(nanoTonCoins: int, extraAmount: cell, reserveMode: int): void + asm "RAWRESERVEX"; + + +/** + Messages sending and parsing primitives. + Working with messages is low-level right now, but still, every contract should do that. + + `Message` structure, its header and so on are specified in TL-B scheme, particularly: + int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool ... = CommonMsgInfo; + */ + +/// 0b011000 tag - 0, ihr_disabled - 1, bounce - 1, bounced - 0, src = adr_none$00 +const BOUNCEABLE = 0x18; +/// 0b010000 tag - 0, ihr_disabled - 1, bounce - 0, bounced - 0, src = adr_none$00 +const NON_BOUNCEABLE = 0x10; + +/// Load msgFlags from incoming message body (4 bits). +@pure +fun loadMessageFlags(s: slice): (slice, int) + asm( -> 1 0) "4 LDU"; + +/// Having msgFlags (4 bits), check that a message is bounced. +/// Effectively, it's `msgFlags & 1` (the lowest bit present). +@pure +fun isMessageBounced(msgFlags: int): int + asm "1 PUSHINT" "AND"; + +/// Skip 0xFFFFFFFF prefix (when a message is bounced). +@pure +fun ~skipBouncedPrefix(s: slice): (slice, ()) + asm "32 PUSHINT" "SDSKIPFIRST"; + +/// The guideline recommends to start the body of an internal message with uint32 `op` and uint64 `queryId`. +@pure +fun loadMessageOp(s: slice): (slice, int) + asm( -> 1 0) "32 LDU"; + +@pure +fun ~skipMessageOp(s: slice): (slice, ()) + asm "32 PUSHINT" "SDSKIPFIRST"; + +@pure +fun storeMessageOp(b: builder, op: int): builder + asm(op b) "32 STU"; +fun ~storeMessageOp(b: builder, op: int): (builder, ()) + asm(op b) "32 STU"; + +/// The guideline recommends that uint64 `queryId` should follow uint32 `op`. +@pure +fun loadMessageQueryId(s: slice): (slice, int) + asm( -> 1 0) "64 LDU"; + +@pure +fun ~skipMessageQueryId(s: slice): (slice, ()) + asm "64 PUSHINT" "SDSKIPFIRST"; + +@pure +fun storeMessageQueryId(b: builder, queryId: int): builder + asm(queryId b) "64 STU"; +fun ~storeMessageQueryId(b: builder, queryId: int): (builder, ()) + asm(queryId b) "64 STU"; + +/// SEND MODES - https://docs.ton.org/tvm.pdf page 137, SENDRAWMSG + +/// mode = 0 is used for ordinary messages; the gas fees are deducted from the senging amount; action phaes should NOT be ignored. +const SEND_MODE_REGULAR = 0; +/// +1 means that the sender wants to pay transfer fees separately. +const SEND_MODE_PAY_FEES_SEPARATELY = 1; +/// +2 means that any errors arising while processing this message during the action phase should be ignored. +const SEND_MODE_IGNORE_ERRORS = 2; +/// in the case of action fail - bounce transaction. No effect if SEND_MODE_IGNORE_ERRORS (+2) is used. TVM UPGRADE 2023-07. https://docs.ton.org/learn/tvm-instructions/tvm-upgrade-2023-07#sending-messages +const SEND_MODE_BOUNCE_ON_ACTION_FAIL = 16; +/// mode = 32 means that the current account must be destroyed if its resulting balance is zero. +const SEND_MODE_DESTROY = 32; +/// mode = 64 is used for messages that carry all the remaining value of the inbound message in addition to the value initially indicated in the new message. +const SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE = 64; +/// mode = 128 is used for messages that are to carry all the remaining balance of the current smart contract (instead of the value originally indicated in the message). +const SEND_MODE_CARRY_ALL_BALANCE = 128; +/// do not create an action, only estimate fee. TVM UPGRADE 2023-07. https://docs.ton.org/learn/tvm-instructions/tvm-upgrade-2023-07#sending-messages +const SEND_MODE_ESTIMATE_FEE_ONLY = 1024; +/// Other modes affect the fee calculation as follows: +/// +64 substitutes the entire balance of the incoming message as an outcoming value (slightly inaccurate, gas expenses that cannot be estimated before the computation is completed are not taken into account). +/// +128 substitutes the value of the entire balance of the contract before the start of the computation phase (slightly inaccurate, since gas expenses that cannot be estimated before the completion of the computation phase are not taken into account). + +/// Sends a raw message — a correctly serialized TL object `Message X`. +/// For `mode`, see constants above (except SEND_MODE_ESTIMATE_FEE_ONLY). +/// This function is still available, but deprecated: consider using [sendMessage]. +@deprecated +fun sendRawMessage(msg: cell, mode: int): void + asm "SENDRAWMSG"; + +/// Creates an output action and returns a fee for creating a message. +/// Mode has the same effect as in the case of SENDRAWMSG. +/// For mode including SEND_MODE_ESTIMATE_FEE_ONLY it just returns estimated fee without sending a message. +fun sendMessage(msg: cell, mode: int): int + asm "SENDMSG"; diff --git a/crypto/smartcont/tolk-stdlib/gas-payments.tolk b/crypto/smartcont/tolk-stdlib/gas-payments.tolk new file mode 100644 index 000000000..1dc6f3f89 --- /dev/null +++ b/crypto/smartcont/tolk-stdlib/gas-payments.tolk @@ -0,0 +1,63 @@ +// A part of standard library for Tolk +tolk 0.6 + +/** + Gas and payment related primitives. + */ + +/// Returns amount of gas (in gas units) consumed in current Computation Phase. +fun getGasConsumedAtTheMoment(): int + asm "GASCONSUMED"; + +/// This function is required to be called when you process an external message (from an outer world) +/// and "accept" it to blockchain. +/// Without calling this function, an external message would be discarded. +/// As an effect, the current smart contract agrees to buy some gas to finish the current transaction. +/// For more details, check [accept_message effects](https://ton.org/docs/#/smart-contracts/accept). +fun acceptExternalMessage(): void + asm "ACCEPT"; + +/// When processing an internal message, by default, the limit of gas consumption is determined by incoming message. +/// Functions [setGasLimit] and [setGasLimitToMaximum] allow you to change this behavior. +/// Sets current gas limit `gl` to its maximal allowed value `gm`, and resets the gas credit `gc` to zero, +/// decreasing the value of `gr` by `gc` in the process. +fun setGasLimitToMaximum(): void + asm "ACCEPT"; + +/// When processing an internal message, by default, the limit of gas consumption is determined by incoming message. +/// Functions [setGasLimit] and [setGasLimitToMaximum] allow you to change this behavior. +/// Sets current gas limit `gl` to the minimum of limit and `gm`, and resets the gas credit `gc` to zero. +/// If the gas consumed so far (including the present instruction) exceeds the resulting value of `gl`, +/// an (unhandled) out of gas exception is thrown before setting new gas limits. +fun setGasLimit(limit: int): void + asm "SETGASLIMIT"; + +/// Calculates fee (amount in nanotoncoins to be paid) for a transaction which consumed [gasUsed] gas units. +fun calculateGasFee(workchain: int, gasUsed: int): int + asm(gasUsed workchain) "GETGASFEE"; + +/// Same as [calculateGasFee], but without flat price (you have supposed to read https://docs.ton.org/develop/howto/fees-low-level) +fun calculateGasFeeWithoutFlatPrice(workchain: int, gasUsed: int): int + asm(gasUsed workchain) "GETGASFEESIMPLE"; + +/// Calculates amount of nanotoncoins you should pay for storing a contract of provided size for [seconds]. +/// [bits] and [cells] represent contract state (code + data). +fun calculateStorageFee(workchain: int, seconds: int, bits: int, cells: int): int + asm(cells bits seconds workchain) "GETSTORAGEFEE"; + +/// Calculates amount of nanotoncoins you should pay to send a message of specified size. +fun calculateMessageFee(workchain: int, bits: int, cells: int): int + asm(cells bits workchain) "GETFORWARDFEE"; + +/// Same as [calculateMessageFee], but without lump price (you have supposed to read https://docs.ton.org/develop/howto/fees-low-level) +fun calculateMessageFeeWithoutLumpPrice(workchain: int, bits: int, cells: int): int + asm(cells bits workchain) "GETFORWARDFEESIMPLE"; + +/// Calculates fee that was paid by the sender of an incoming internal message. +fun calculateOriginalMessageFee(workchain: int, incomingFwdFee: int): int + asm(incomingFwdFee workchain) "GETORIGINALFWDFEE"; + +/// Returns the amount of nanotoncoins current contract debts for storage. ("due" and "debt" are synonyms) +/// If it has no debt, `0` is returned. +fun getMyStorageDuePayment(): int + asm "DUEPAYMENT"; diff --git a/crypto/smartcont/tolk-stdlib/lisp-lists.tolk b/crypto/smartcont/tolk-stdlib/lisp-lists.tolk new file mode 100644 index 000000000..94c045237 --- /dev/null +++ b/crypto/smartcont/tolk-stdlib/lisp-lists.tolk @@ -0,0 +1,38 @@ +// A part of standard library for Tolk +tolk 0.6 + +/** + Lisp-style lists are nested 2-elements tuples: `(1, (2, (3, null)))` represents list `[1, 2, 3]`. + Elements of a list can be of different types. + Empty list is conventionally represented as TVM `null` value. + */ + +@pure +fun createEmptyList(): tuple + asm "PUSHNULL"; + +/// Adds an element to the beginning of lisp-style list. +/// Note, that it does not mutate the list: instead, it returns a new one (it's a lisp pattern). +@pure +fun listPrepend(head: X, tail: tuple): tuple + asm "CONS"; + +/// Extracts the head and the tail of lisp-style list. +@pure +fun listSplit(list: tuple): (X, tuple) + asm "UNCONS"; + +/// Extracts the tail and the head of lisp-style list. +@pure +fun ~listNext(list: tuple): (tuple, X) + asm( -> 1 0) "UNCONS"; + +/// Returns the head of lisp-style list. +@pure +fun listGetHead(list: tuple): X + asm "CAR"; + +/// Returns the tail of lisp-style list. +@pure +fun listGetTail(list: tuple): tuple + asm "CDR"; diff --git a/crypto/smartcont/tolk-stdlib/tvm-dicts.tolk b/crypto/smartcont/tolk-stdlib/tvm-dicts.tolk new file mode 100644 index 000000000..1e3c10ec8 --- /dev/null +++ b/crypto/smartcont/tolk-stdlib/tvm-dicts.tolk @@ -0,0 +1,447 @@ +// A part of standard library for Tolk +tolk 0.6 + +/** + Dictionaries are represented as `cell` data type (cells can store anything, dicts in particular). + Currently, they have very low-level API very close to TVM internals. + Most of functions are duplicated for three common cases: + - iDict* - dicts with signed integer keys + - uDict* - dicts with unsigned integer keys + - sDict* - dicts with arbitrary slice keys + When accessing a dict element, you should not only provide a key, but provide keyLen, + since for optimization, for optimization, key length is not stored in the dictionary itself. + */ + +/// Creates an empty dictionary, which is actually a null value. Equivalent to PUSHNULL +@pure +fun createEmptyDict(): cell + asm "NEWDICT"; + +/// Checks whether a dictionary is empty. +@pure +fun dictIsEmpty(c: cell): int + asm "DICTEMPTY"; + + +@pure +fun iDictGet(dict: cell, keyLen: int, key: int): (slice, int) + asm(key dict keyLen) "DICTIGET" "NULLSWAPIFNOT"; + +@pure +fun uDictGet(dict: cell, keyLen: int, key: int): (slice, int) + asm(key dict keyLen) "DICTUGET" "NULLSWAPIFNOT"; + +@pure +fun sDictGet(dict: cell, keyLen: int, key: slice): (slice, int) + asm(key dict keyLen) "DICTGET" "NULLSWAPIFNOT"; + + +@pure +fun iDictSet(dict: cell, keyLen: int, key: int, value: slice): cell + asm(value key dict keyLen) "DICTISET"; + +@pure +fun ~iDictSet(dict: cell, keyLen: int, key: int, value: slice): (cell, ()) + asm(value key dict keyLen) "DICTISET"; + +@pure +fun uDictSet(dict: cell, keyLen: int, key: int, value: slice): cell + asm(value key dict keyLen) "DICTUSET"; + +@pure +fun ~uDictSet(dict: cell, keyLen: int, key: int, value: slice): (cell, ()) + asm(value key dict keyLen) "DICTUSET"; + +@pure +fun sDictSet(dict: cell, keyLen: int, key: slice, value: slice): cell + asm(value key dict keyLen) "DICTSET"; + +@pure +fun ~sDictSet(dict: cell, keyLen: int, key: slice, value: slice): (cell, ()) + asm(value key dict keyLen) "DICTSET"; + + +@pure +fun iDictSetRef(dict: cell, keyLen: int, key: int, value: cell): cell + asm(value key dict keyLen) "DICTISETREF"; + +@pure +fun ~iDictSetRef(dict: cell, keyLen: int, key: int, value: cell): (cell, ()) + asm(value key dict keyLen) "DICTISETREF"; + +@pure +fun uDictSetRef(dict: cell, keyLen: int, key: int, value: cell): cell + asm(value key dict keyLen) "DICTUSETREF"; + +@pure +fun ~uDictSetRef(dict: cell, keyLen: int, key: int, value: cell): (cell, ()) + asm(value key dict keyLen) "DICTUSETREF"; + +@pure +fun sDictSetRef(dict: cell, keyLen: int, key: slice, value: cell): cell + asm(value key dict keyLen) "DICTSETREF"; + +@pure +fun ~sDictSetRef(dict: cell, keyLen: int, key: slice, value: cell): (cell, ()) + asm(value key dict keyLen) "DICTSETREF"; + + +@pure +fun iDictSetIfNotExists(dict: cell, keyLen: int, key: int, value: slice): (cell, int) + asm(value key dict keyLen) "DICTIADD"; + +@pure +fun ~iDictSetIfNotExists(dict: cell, keyLen: int, key: int, value: slice): (cell, int) + asm(value key dict keyLen) "DICTIADD"; + +@pure +fun uDictSetIfNotExists(dict: cell, keyLen: int, key: int, value: slice): (cell, int) + asm(value key dict keyLen) "DICTUADD"; + +@pure +fun ~uDictSetIfNotExists(dict: cell, keyLen: int, key: int, value: slice): (cell, int) + asm(value key dict keyLen) "DICTUADD"; + + +@pure +fun iDictSetIfExists(dict: cell, keyLen: int, key: int, value: slice): (cell, int) + asm(value key dict keyLen) "DICTIREPLACE"; + +@pure +fun ~iDictSetIfExists(dict: cell, keyLen: int, key: int, value: slice): (cell, int) + asm(value key dict keyLen) "DICTIREPLACE"; + +@pure +fun uDictSetIfExists(dict: cell, keyLen: int, key: int, value: slice): (cell, int) + asm(value key dict keyLen) "DICTUREPLACE"; + +@pure +fun ~uDictSetIfExists(dict: cell, keyLen: int, key: int, value: slice): (cell, int) + asm(value key dict keyLen) "DICTUREPLACE"; + + +@pure +fun iDictGetRef(dict: cell, keyLen: int, key: int): (cell, int) + asm(key dict keyLen) "DICTIGETREF" "NULLSWAPIFNOT"; + +@pure +fun uDictGetRef(dict: cell, keyLen: int, key: int): (cell, int) + asm(key dict keyLen) "DICTUGETREF" "NULLSWAPIFNOT"; + +@pure +fun sDictGetRef(dict: cell, keyLen: int, key: slice): (cell, int) + asm(key dict keyLen) "DICTGETREF" "NULLSWAPIFNOT"; + + +@pure +fun iDictGetRefOrNull(dict: cell, keyLen: int, key: int): cell + asm(key dict keyLen) "DICTIGETOPTREF"; + +@pure +fun uDictGetRefOrNull(dict: cell, keyLen: int, key: int): cell + asm(key dict keyLen) "DICTUGETOPTREF"; + +@pure +fun sDictGetRefOrNull(dict: cell, keyLen: int, key: slice): cell + asm(key dict keyLen) "DICTGETOPTREF"; + + +@pure +fun iDictDelete(dict: cell, keyLen: int, key: int): (cell, int) + asm(key dict keyLen) "DICTIDEL"; + +@pure +fun ~iDictDelete(dict: cell, keyLen: int, key: int): (cell, int) + asm(key dict keyLen) "DICTIDEL"; + +@pure +fun uDictDelete(dict: cell, keyLen: int, key: int): (cell, int) + asm(key dict keyLen) "DICTUDEL"; + +@pure +fun ~uDictDelete(dict: cell, keyLen: int, key: int): (cell, int) + asm(key dict keyLen) "DICTUDEL"; + +@pure +fun sDictDelete(dict: cell, keyLen: int, key: slice): (cell, int) + asm(key dict keyLen) "DICTDEL"; + +@pure +fun ~sDictDelete(dict: cell, keyLen: int, key: slice): (cell, int) + asm(key dict keyLen) "DICTDEL"; + + +@pure +fun iDictSetAndGet(dict: cell, keyLen: int, key: int, value: slice): (cell, slice, int) + asm(value key dict keyLen) "DICTISETGET" "NULLSWAPIFNOT"; + +@pure +fun ~iDictSetAndGet(dict: cell, keyLen: int, key: int, value: slice): (cell, (slice, int)) + asm(value key dict keyLen) "DICTISETGET" "NULLSWAPIFNOT"; + +@pure +fun uDictSetAndGet(dict: cell, keyLen: int, key: int, value: slice): (cell, slice, int) + asm(value key dict keyLen) "DICTUSETGET" "NULLSWAPIFNOT"; + +@pure +fun ~uDictSetAndGet(dict: cell, keyLen: int, key: int, value: slice): (cell, (slice, int)) + asm(value key dict keyLen) "DICTUSETGET" "NULLSWAPIFNOT"; + +@pure +fun sDictSetAndGet(dict: cell, keyLen: int, key: slice, value: slice): (cell, slice, int) + asm(value key dict keyLen) "DICTSETGET" "NULLSWAPIFNOT"; + +@pure +fun ~sDictSetAndGet(dict: cell, keyLen: int, key: slice, value: slice): (cell, (slice, int)) + asm(value key dict keyLen) "DICTSETGET" "NULLSWAPIFNOT"; + + +@pure +fun iDictSetAndGetPreviousRefOrNull(dict: cell, keyLen: int, key: int, value: cell): (cell, cell) + asm(value key dict keyLen) "DICTISETGETOPTREF"; + +@pure +fun ~iDictSetAndGetPreviousRefOrNull(dict: cell, keyLen: int, key: int, value: cell): (cell, cell) + asm(value key dict keyLen) "DICTISETGETOPTREF"; + +@pure +fun uDictSetAndGetPreviousRefOrNull(dict: cell, keyLen: int, key: int, value: cell): (cell, cell) + asm(value key dict keyLen) "DICTUSETGETOPTREF"; + +@pure +fun ~uDictSetAndGetPreviousRefOrNull(dict: cell, keyLen: int, key: int, value: cell): (cell, cell) + asm(value key dict keyLen) "DICTUSETGETOPTREF"; + + +@pure +fun iDictDeleteAndGet(dict: cell, keyLen: int, key: int): (cell, slice, int) + asm(key dict keyLen) "DICTIDELGET" "NULLSWAPIFNOT"; + +@pure +fun ~iDictDeleteAndGet(dict: cell, keyLen: int, key: int): (cell, (slice, int)) + asm(key dict keyLen) "DICTIDELGET" "NULLSWAPIFNOT"; + +@pure +fun uDictDeleteAndGet(dict: cell, keyLen: int, key: int): (cell, slice, int) + asm(key dict keyLen) "DICTUDELGET" "NULLSWAPIFNOT"; + +@pure +fun ~uDictDeleteAndGet(dict: cell, keyLen: int, key: int): (cell, (slice, int)) + asm(key dict keyLen) "DICTUDELGET" "NULLSWAPIFNOT"; + +@pure +fun sDictDeleteAndGet(dict: cell, keyLen: int, key: slice): (cell, slice, int) + asm(key dict keyLen) "DICTDELGET" "NULLSWAPIFNOT"; + +@pure +fun ~sDictDeleteAndGet(dict: cell, keyLen: int, key: slice): (cell, (slice, int)) + asm(key dict keyLen) "DICTDELGET" "NULLSWAPIFNOT"; + + +@pure +fun iDictSetBuilder(dict: cell, keyLen: int, key: int, value: builder): cell + asm(value key dict keyLen) "DICTISETB"; + +@pure +fun ~iDictSetBuilder(dict: cell, keyLen: int, key: int, value: builder): (cell, ()) + asm(value key dict keyLen) "DICTISETB"; + +@pure +fun uDictSetBuilder(dict: cell, keyLen: int, key: int, value: builder): cell + asm(value key dict keyLen) "DICTUSETB"; + +@pure +fun ~uDictSetBuilder(dict: cell, keyLen: int, key: int, value: builder): (cell, ()) + asm(value key dict keyLen) "DICTUSETB"; + +@pure +fun sDictSetBuilder(dict: cell, keyLen: int, key: slice, value: builder): cell + asm(value key dict keyLen) "DICTSETB"; + +@pure +fun ~sDictSetBuilder(dict: cell, keyLen: int, key: slice, value: builder): (cell, ()) + asm(value key dict keyLen) "DICTSETB"; + + +@pure +fun iDictSetBuilderIfNotExists(dict: cell, keyLen: int, key: int, value: builder): (cell, int) + asm(value key dict keyLen) "DICTIADDB"; + +@pure +fun ~iDictSetBuilderIfNotExists(dict: cell, keyLen: int, key: int, value: builder): (cell, int) + asm(value key dict keyLen) "DICTIADDB"; + +@pure +fun uDictSetBuilderIfNotExists(dict: cell, keyLen: int, key: int, value: builder): (cell, int) + asm(value key dict keyLen) "DICTUADDB"; + +@pure +fun ~uDictSetBuilderIfNotExists(dict: cell, keyLen: int, key: int, value: builder): (cell, int) + asm(value key dict keyLen) "DICTUADDB"; + +@pure +fun iDictSetBuilderIfExists(dict: cell, keyLen: int, key: int, value: builder): (cell, int) + asm(value key dict keyLen) "DICTIREPLACEB"; + +@pure +fun ~iDictSetBuilderIfExists(dict: cell, keyLen: int, key: int, value: builder): (cell, int) + asm(value key dict keyLen) "DICTIREPLACEB"; + +@pure +fun uDictSetBuilderIfExists(dict: cell, keyLen: int, key: int, value: builder): (cell, int) + asm(value key dict keyLen) "DICTUREPLACEB"; + +@pure +fun ~uDictSetBuilderIfExists(dict: cell, keyLen: int, key: int, value: builder): (cell, int) + asm(value key dict keyLen) "DICTUREPLACEB"; + + +@pure +fun iDictDeleteFirstAndGet(dict: cell, keyLen: int): (cell, int, slice, int) + asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; + +@pure +fun ~iDictDeleteFirstAndGet(dict: cell, keyLen: int): (cell, (int, slice, int)) + asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; + +@pure +fun uDictDeleteFirstAndGet(dict: cell, keyLen: int): (cell, int, slice, int) + asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; + +@pure +fun ~uDictDeleteFirstAndGet(dict: cell, keyLen: int): (cell, (int, slice, int)) + asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; + +@pure +fun sDictDeleteFirstAndGet(dict: cell, keyLen: int): (cell, slice, slice, int) + asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; + +@pure +fun ~sDictDeleteFirstAndGet(dict: cell, keyLen: int): (cell, (slice, slice, int)) + asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; + + +@pure +fun iDictDeleteLastAndGet(dict: cell, keyLen: int): (cell, int, slice, int) + asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; + +@pure +fun ~iDictDeleteLastAndGet(dict: cell, keyLen: int): (cell, (int, slice, int)) + asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; + +@pure +fun uDictDeleteLastAndGet(dict: cell, keyLen: int): (cell, int, slice, int) + asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; + +@pure +fun ~uDictDeleteLastAndGet(dict: cell, keyLen: int): (cell, (int, slice, int)) + asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; + +@pure +fun sDictDeleteLastAndGet(dict: cell, keyLen: int): (cell, slice, slice, int) + asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; + +@pure +fun ~sDictDeleteLastAndGet(dict: cell, keyLen: int): (cell, (slice, slice, int)) + asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; + + +@pure +fun iDictGetFirst(dict: cell, keyLen: int): (int, slice, int) + asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; + +@pure +fun uDictGetFirst(dict: cell, keyLen: int): (int, slice, int) + asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; + +@pure +fun sDictGetFirst(dict: cell, keyLen: int): (slice, slice, int) + asm (-> 1 0 2) "DICTMIN" "NULLSWAPIFNOT2"; + +@pure +fun iDictGetFirstAsRef(dict: cell, keyLen: int): (int, cell, int) + asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; + +@pure +fun uDictGetFirstAsRef(dict: cell, keyLen: int): (int, cell, int) + asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; + +@pure +fun sDictGetFirstAsRef(dict: cell, keyLen: int): (slice, cell, int) + asm (-> 1 0 2) "DICTMINREF" "NULLSWAPIFNOT2"; + + +@pure +fun iDictGetLast(dict: cell, keyLen: int): (int, slice, int) + asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; + +@pure +fun uDictGetLast(dict: cell, keyLen: int): (int, slice, int) + asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; + +@pure +fun sDictGetLast(dict: cell, keyLen: int): (slice, slice, int) + asm (-> 1 0 2) "DICTMAX" "NULLSWAPIFNOT2"; + +@pure +fun iDictGetLastAsRef(dict: cell, keyLen: int): (int, cell, int) + asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; + +@pure +fun uDictGetLastAsRef(dict: cell, keyLen: int): (int, cell, int) + asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; + +@pure +fun sDictGetLastAsRef(dict: cell, keyLen: int): (slice, cell, int) + asm (-> 1 0 2) "DICTMAXREF" "NULLSWAPIFNOT2"; + + +@pure +fun iDictGetNext(dict: cell, keyLen: int, pivot: int): (int, slice, int) + asm(pivot dict keyLen -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; + +@pure +fun uDictGetNext(dict: cell, keyLen: int, pivot: int): (int, slice, int) + asm(pivot dict keyLen -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; + +@pure +fun iDictGetNextOrEqual(dict: cell, keyLen: int, pivot: int): (int, slice, int) + asm(pivot dict keyLen -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; + +@pure +fun uDictGetNextOrEqual(dict: cell, keyLen: int, pivot: int): (int, slice, int) + asm(pivot dict keyLen -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; + + +@pure +fun iDictGetPrev(dict: cell, keyLen: int, pivot: int): (int, slice, int) + asm(pivot dict keyLen -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; + +@pure +fun uDictGetPrev(dict: cell, keyLen: int, pivot: int): (int, slice, int) + asm(pivot dict keyLen -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; + +@pure +fun iDictGetPrevOrEqual(dict: cell, keyLen: int, pivot: int): (int, slice, int) + asm(pivot dict keyLen -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; + +@pure +fun uDictGetPrevOrEqual(dict: cell, keyLen: int, pivot: int): (int, slice, int) + asm(pivot dict keyLen -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; + + +/** + Prefix dictionary primitives. + */ + +@pure +fun prefixDictGet(dict: cell, keyLen: int, key: slice): (slice, slice, slice, int) + asm(key dict keyLen) "PFXDICTGETQ" "NULLSWAPIFNOT2"; + +@pure +fun prefixDictSet(dict: cell, keyLen: int, key: slice, value: slice): (cell, int) + asm(value key dict keyLen) "PFXDICTSET"; + +@pure +fun prefixDictDelete(dict: cell, keyLen: int, key: slice): (cell, int) + asm(key dict keyLen) "PFXDICTDEL"; diff --git a/crypto/smartcont/tolk-stdlib/tvm-lowlevel.tolk b/crypto/smartcont/tolk-stdlib/tvm-lowlevel.tolk new file mode 100644 index 000000000..b4f44a1bf --- /dev/null +++ b/crypto/smartcont/tolk-stdlib/tvm-lowlevel.tolk @@ -0,0 +1,29 @@ +// A part of standard library for Tolk +tolk 0.6 + +/// 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`. +@pure +fun getTvmRegisterC3(): continuation + asm "c3 PUSH"; + +/// Updates the current value of `c3`. Usually, it is used for updating smart contract code in run-time. +/// Note that after execution of this primitive the current code +/// (and the stack of recursive function calls) won't change, +/// but any other function call will use a function from the new code. +fun setTvmRegisterC3(c: continuation): void + asm "c3 POP"; + +/// Transforms a `slice` [s] into a simple ordinary continuation `c`, with `c.code = s` and an empty stack and savelist. +@pure +fun transformSliceToContinuation(s: slice): continuation + asm "BLESS"; + +/// Moves a variable or a value [x] to the top of the stack. +@pure +fun stackMoveToTop(x: X): X + asm "NOP"; + +/// Mark a variable as used, such that the code which produced it won't be deleted even if it is not impure. +fun stackMoveToTopImpure(x: X): void // todo needs to be deleted, check verified contracts + asm "DROP"; diff --git a/tolk-tester/tests/a10.tolk b/tolk-tester/tests/a10.tolk index 8ea137748..1526a1220 100644 --- a/tolk-tester/tests/a10.tolk +++ b/tolk-tester/tests/a10.tolk @@ -1,3 +1,5 @@ +fun pair_first(p: [X, Y]): X asm "FIRST"; + fun one(dummy: tuple) { return 1; } @@ -30,6 +32,29 @@ fun test88(x: int) { } } +@method_id(89) +fun test89(last: int) { + var t: tuple = createEmptyTuple(); + t~tuplePush(1); + t~tuplePush(2); + t~tuplePush(3); + t~tuplePush(last); + return (t.tupleAt(0), t.tupleAt(t.tupleSize() - 1), t.tupleFirst(), t.tupleLast()); +} + +@method_id(93) +fun testStartBalanceCodegen1() { + var t = getMyOriginalBalanceWithExtraCurrencies(); + var first = t.pair_first(); + return first; +} + +@method_id(94) +fun testStartBalanceCodegen2() { + var first = getMyOriginalBalance(); + return first; +} + /** method_id | in | out @testcase | 0 | 101 15 | 100 1 @@ -39,4 +64,24 @@ fun test88(x: int) { @testcase | 0 | 100 10 | 100 0 @testcase | 88 | 5 | 234 @testcase | 88 | 50 | 0 +@testcase | 89 | 4 | 1 4 1 4 + + +@fif_codegen +""" + testStartBalanceCodegen1 PROC:<{ + // + BALANCE // t + FIRST // first + }> +""" + +@fif_codegen +""" + testStartBalanceCodegen2 PROC:<{ + // + BALANCE + FIRST // first + }> +""" */ diff --git a/tolk-tester/tests/a6.tolk b/tolk-tester/tests/a6.tolk index 3bdcdbdf8..da692a78e 100644 --- a/tolk-tester/tests/a6.tolk +++ b/tolk-tester/tests/a6.tolk @@ -4,7 +4,7 @@ fun f(a: int, b: int, c: int, d: int, e: int, f: int): (int, int) { return (Dx/D,Dy/D); };;;; -fun mulDivR(x: int, y: int, z: int): int { return muldivr(x, y, z); } +fun mulDivR(x: int, y: int, z: int): int { return mulDivRound(x, y, z); } fun calc_phi(): int { var n = 1; diff --git a/tolk-tester/tests/asm_arg_order.tolk b/tolk-tester/tests/asm_arg_order.tolk index 8bf46e3e2..a2d66bc2d 100644 --- a/tolk-tester/tests/asm_arg_order.tolk +++ b/tolk-tester/tests/asm_arg_order.tolk @@ -4,8 +4,8 @@ asm "NIL"; @pure fun tpush2(t: tuple, x: X): (tuple, ()) asm "TPUSH"; -fun emptyTuple(): tuple { return empty_tuple2(); } -fun tuplePush(t: tuple, value: X): (tuple, ()) { return tpush2(t, value); } +fun myEmptyTuple(): tuple { return empty_tuple2(); } +fun myTuplePush(t: tuple, value: X): (tuple, ()) { return tpush2(t, value); } @pure fun asm_func_1(x: int, y: int, z: int): tuple @@ -31,7 +31,7 @@ fun asmFuncModify(a: tuple, b: int, c: int): (tuple, ()) { return asm_func_modif global t: tuple; fun foo(x: int): int { - t~tuplePush(x); + t~myTuplePush(x); return x * 10; } @@ -44,7 +44,7 @@ fun test_old_1(): (tuple, tuple) { @method_id(12) fun test_old_2(): (tuple, tuple) { - t = emptyTuple(); + t = myEmptyTuple(); var t2: tuple = asm_func_2(foo(11), foo(22), foo(33)); return (t, t2); } @@ -58,7 +58,7 @@ fun test_old_3(): (tuple, tuple) { @method_id(14) fun test_old_4(): (tuple, tuple) { - t = emptyTuple(); + t = myEmptyTuple(); var t2: tuple = empty_tuple2(); // This actually computes left-to-right even without compute-asm-ltr t2 = asm_func_4(foo(11), (foo(22), (foo(33), foo(44))), foo(55)); diff --git a/tolk-tester/tests/camel1.tolk b/tolk-tester/tests/camel1.tolk index a9f1bf3e4..291206a95 100644 --- a/tolk-tester/tests/camel1.tolk +++ b/tolk-tester/tests/camel1.tolk @@ -7,40 +7,45 @@ // without affecting performance and even bytecode hashes. // This works with ~functions also. And even works with wrappers of wrappers. // Moreover, such wrappers can reorder input parameters, see a separate test camel2.tolk. +import "@stdlib/tvm-dicts" -fun myBeginCell(): builder { return begin_cell(); } -fun myEndCell(b: builder): cell { return end_cell(b); } -fun myStoreRef(b: builder, c: cell): builder { return store_ref(b, c); } -fun myStoreUint(b: builder, i: int, bw: int): builder { return store_uint(b, i, bw); } +fun myBeginCell(): builder { return beginCell(); } +fun myEndCell(b: builder): cell { return endCell(b); } +fun myStoreRef(b: builder, c: cell): builder { return storeRef(b, c); } +fun myStoreUint(b: builder, i: int, bw: int): builder { return storeUint(b, i, bw); } // 'inline' is not needed actually, but if it exists, it's just ignored @inline @pure -fun myBeginParse(c: cell): slice { return begin_parse(c); } +fun myBeginParse(c: cell): slice { return beginParse(c); } @inline @pure -fun mySkipBits(s: slice, len: int): slice { return skip_bits(s, len); } +fun mySkipBits(s: slice, len: int): slice { return skipBits(s, len); } @inline @pure -fun ~mySkipBits(s: slice, len: int): (slice, ()) { return ~skip_bits(s, len); } +fun ~mySkipBits(s: slice, len: int): (slice, ()) { return ~skipBits(s, len); } @inline @pure -fun ~myLoadUint(s: slice, len: int): (slice, int) { return load_uint(s, len); } +fun ~myLoadUint(s: slice, len: int): (slice, int) { return loadUint(s, len); } -fun myComputeDataSize(c: cell, maxCells: int): (int, int, int) { return compute_data_size(c, maxCells); } +fun myComputeDataSize(c: cell, maxCells: int): (int, int, int) { return calculateCellSizeStrict(c, maxCells); } -fun dict__new(): cell { return new_dict(); } -fun dict__iset(dict: cell, keyLen: int, index: int, value: slice): cell { return idict_set(dict, keyLen, index, value); } -fun ~dict__iset(dict: cell, keyLen: int, index: int, value: slice): (cell, ()) { return ~idict_set(dict, keyLen, index, value); } -fun dict__tryIGet(dict: cell, keyLen: int, index: int): (slice, int) { return idict_get?(dict, keyLen, index); } -fun dict__tryIGetMin(dict: cell, keyLen: int): (int, slice, int) { return idict_get_min?(dict, keyLen); } +fun dict__new(): cell { return createEmptyDict(); } +fun dict__iset(dict: cell, keyLen: int, index: int, value: slice): cell { return iDictSet(dict, keyLen, index, value); } +fun ~dict__iset(dict: cell, keyLen: int, index: int, value: slice): (cell, ()) { return ~iDictSet(dict, keyLen, index, value); } +fun dict__tryIGet(dict: cell, keyLen: int, index: int): (slice, int) { return iDictGet(dict, keyLen, index); } +fun dict__tryIGetMin(dict: cell, keyLen: int): (int, slice, int) { return iDictGetFirst(dict, keyLen); } -fun myEmptyTuple(): tuple { return empty_tuple(); } +@pure +fun triple_second(p: [X, Y, Z]): Y + asm "SECOND"; + +fun myEmptyTuple(): tuple { return createEmptyTuple(); } fun emptyTuple1(): tuple { return myEmptyTuple(); } fun emptyTuple11(): tuple { return emptyTuple1(); } -fun myTuplePush(t: tuple, value: X): tuple { return tpush(t, value); } -fun ~myTuplePush(t: tuple, value: X): (tuple, ()) { return ~tpush(t, value); } -fun myTupleAt(t: tuple, index: int): X { return at(t, index); } +fun myTuplePush(t: tuple, value: X): tuple { return tuplePush(t, value); } +fun ~myTuplePush(t: tuple, value: X): (tuple, ()) { return ~tuplePush(t, value); } +fun myTupleAt(t: tuple, index: int): X { return tupleAt(t, index); } fun tripleSecond(p: [X1, Y2, Z3]): Y2 { return triple_second(p); } @pure fun nullValue(): X diff --git a/tolk-tester/tests/camel2.tolk b/tolk-tester/tests/camel2.tolk index 121b7f784..51344b843 100644 --- a/tolk-tester/tests/camel2.tolk +++ b/tolk-tester/tests/camel2.tolk @@ -5,15 +5,15 @@ // But swapping arguments may sometimes lead to bytecode changes (see test2), // both with compute-asm-ltr and without it. -fun myBeginCell(): builder { return begin_cell(); } -fun myEndCell(b: builder): cell { return end_cell(b); } -fun myStoreRef1(b: builder, c: cell): builder { return store_ref(b, c); } -fun myStoreRef2(c: cell, b: builder): builder { return store_ref(b, c); } -fun myStoreUint1(b: builder, x: int, bw: int): builder { return store_uint(b, x, bw); } -fun myStoreUint2(b: builder, bw: int, x: int): builder { return store_uint(b, x, bw); } +fun myBeginCell(): builder { return beginCell(); } +fun myEndCell(b: builder): cell { return endCell(b); } +fun myStoreRef1(b: builder, c: cell): builder { return storeRef(b, c); } +fun myStoreRef2(c: cell, b: builder): builder { return storeRef(b, c); } +fun myStoreUint1(b: builder, x: int, bw: int): builder { return storeUint(b, x, bw); } +fun myStoreUint2(b: builder, bw: int, x: int): builder { return storeUint(b, x, bw); } -fun computeDataSize1(c: cell, maxCells: int): (int, int, int) { return compute_data_size(c, maxCells); } -fun computeDataSize2(maxCells: int, c: cell): (int, int, int) { return compute_data_size(c, maxCells); } +fun computeDataSize1(c: cell, maxCells: int): (int, int, int) { return calculateCellSizeStrict(c, maxCells); } +fun computeDataSize2(maxCells: int, c: cell): (int, int, int) { return calculateCellSizeStrict(c, maxCells); } fun fake(a: int, b: int, c: int): void asm "DROP DROP DROP"; @@ -48,24 +48,24 @@ fun test2(): (int, int, int) { fun test3(): (int, int, int) { var x: int = 1; var y: int = 1; - var to_be_ref: cell = begin_cell().end_cell(); - var in_c: builder = begin_cell().store_uint(123, 8); - in_c = store_ref(in_c, to_be_ref); - var (a, b, c) = compute_data_size(in_c.end_cell(), 10); + var to_be_ref: cell = beginCell().endCell(); + var in_c: builder = beginCell().storeUint(123, 8); + in_c = storeRef(in_c, to_be_ref); + var (a, b, c) = calculateCellSizeStrict(in_c.endCell(), 10); return (a, b + x, c + y); } -fun beginCell1(): builder { return begin_cell(); } +fun beginCell1(): builder { return beginCell(); } fun beginCell11(): builder { return beginCell1(); } fun beginCell111(): builder { return beginCell11(); } -fun endCell1(b: builder): cell { return end_cell(b); } +fun endCell1(b: builder): cell { return endCell(b); } fun endCell11(b: builder): cell { return endCell1(b); } -fun beginParse1(c: cell): slice { return begin_parse(c); } +fun beginParse1(c: cell): slice { return beginParse(c); } fun beginParse11(c: cell): slice { return beginParse1(c); } -fun storeInt1(b: builder, bw: int, x: int): builder { return store_int(b, x, bw); } +fun storeInt1(b: builder, bw: int, x: int): builder { return storeInt(b, x, bw); } fun storeInt11(bw: int, x: int, b: builder): builder { return storeInt1(b, bw, x); } fun storeInt111(b: builder, x: int, bw: int): builder { return storeInt11(bw, x, b); } diff --git a/tolk-tester/tests/camel3.tolk b/tolk-tester/tests/camel3.tolk index e76c02b7d..23b16e5fb 100644 --- a/tolk-tester/tests/camel3.tolk +++ b/tolk-tester/tests/camel3.tolk @@ -4,17 +4,17 @@ // (save to a variable, return from a function, etc.) // it also works, since a function becomes codegenerated (though direct calls are expectedly inlined). -fun myBeginCell(): builder { return begin_cell(); } -fun myEndCell(b: builder): cell { return end_cell(b); } -fun myStoreRef(b: builder, c: cell): builder { return store_ref(b, c); } -fun myStoreUint3(i: int, bw: int, b: builder): builder { return store_uint(b, i, bw); } +fun myBeginCell(): builder { return beginCell(); } +fun myEndCell(b: builder): cell { return endCell(b); } +fun myStoreRef(b: builder, c: cell): builder { return storeRef(b, c); } +fun myStoreUint3(i: int, bw: int, b: builder): builder { return storeUint(b, i, bw); } -fun computeDataSize2(maxCells: int, c: cell): (int, int, int) { return compute_data_size(c, maxCells); } +fun computeDataSize2(maxCells: int, c: cell): (int, int, int) { return calculateCellSizeStrict(c, maxCells); } -fun myEmptyTuple(): tuple { return empty_tuple(); } -fun myTuplePush(t: tuple, value: X): tuple { return tpush(t, value); } -fun ~myTuplePush(t: tuple, value: X): (tuple, ()) { return ~tpush(t, value); } -fun tupleGetFirst(t: tuple): X { return first(t); } +fun myEmptyTuple(): tuple { return createEmptyTuple(); } +fun myTuplePush(t: tuple, value: X): tuple { return tuplePush(t, value); } +fun ~myTuplePush(t: tuple, value: X): (tuple, ()) { return ~tuplePush(t, value); } +fun myTupleGetFirst(t: tuple): X { return tupleFirst(t); } @inline @@ -32,7 +32,7 @@ fun test1(): (int, int, int) { var t: tuple = myEmptyTuple(); t~myTuplePush(myStoreRef); - var refStorer = tupleGetFirst(t); + var refStorer = myTupleGetFirst(t); var x: int = 1; var y: int = 1; diff --git a/tolk-tester/tests/camel4.tolk b/tolk-tester/tests/camel4.tolk index c6be62685..a33e3fd36 100644 --- a/tolk-tester/tests/camel4.tolk +++ b/tolk-tester/tests/camel4.tolk @@ -1,7 +1,7 @@ // Here we test that a just-return function is not a valid wrapper, it will not be inlined. // (doesn't use all arguments, has different pureness, has method_id, etc.) -fun myStoreUint(b: builder, x: int, unused: int): builder { return store_uint(b, x, x); } +fun myStoreUint(b: builder, x: int, unused: int): builder { return storeUint(b, x, x); } fun throwIf(excNo: int, cond: int) { assert(!cond) throw excNo; } fun initial1(x: auto) { return x; } @@ -17,11 +17,11 @@ fun postpone_elections(): int { } fun setAndGetData(ret: int): int { - var c: cell = begin_cell().store_uint(ret, 8).end_cell(); - set_data(c); - var s: slice = get_data().begin_parse(); + var c: cell = beginCell().storeUint(ret, 8).endCell(); + setContractData(c); + var s: slice = getContractData().beginParse(); throwIf(101, 0); - return s~load_uint(8); + return s~loadUint(8); } fun setAndGetDataWrapper(ret: int): int { @@ -30,9 +30,9 @@ fun setAndGetDataWrapper(ret: int): int { @method_id(101) fun test1(): int { - var c: cell = begin_cell().myStoreUint(32, 10000000).end_cell(); - var s: slice = c.begin_parse(); - return s~load_uint(32); + var c: cell = beginCell().myStoreUint(32, 10000000).endCell(); + var s: slice = c.beginParse(); + return s~loadUint(32); } get fun test2(ret: int): int { @@ -47,13 +47,13 @@ fun test3(): int { global t: tuple; fun foo(x: int): int { - t~tpush(x); + t~tuplePush(x); return x * 10; } @method_id(104) fun test4(): (tuple, tuple) { - t = empty_tuple(); + t = createEmptyTuple(); var t2: tuple = asmFunc4(foo(11), (foo(22), (foo(33), foo(44))), foo(55)); return (t, t2); } diff --git a/tolk-tester/tests/cells-slices.tolk b/tolk-tester/tests/cells-slices.tolk index 508cd31db..1bf0742d9 100644 --- a/tolk-tester/tests/cells-slices.tolk +++ b/tolk-tester/tests/cells-slices.tolk @@ -1,32 +1,32 @@ fun store_u32(b: builder, value: int): builder { - return b.store_uint(value, 32); + return b.storeUint(value, 32); } fun ~store_u32(b: builder, value: int): (builder, ()) { - return ~store_uint(b, value, 32); + return ~storeUint(b, value, 32); } fun load_u32(cs: slice): (slice, int) { - return cs.load_uint(32); + return cs.loadUint(32); } -fun my_load_int(s: slice, len: int): (slice, int) +fun my_loadInt(s: slice, len: int): (slice, int) asm(s len -> 1 0) "LDIX"; // top is "value slice" -fun my_store_int(b: builder, x: int, len: int): builder +fun my_storeInt(b: builder, x: int, len: int): builder asm(x b len) "STIX"; -fun ~my_store_int(b: builder, x: int, len: int): (builder, ()) +fun ~my_storeInt(b: builder, x: int, len: int): (builder, ()) asm(x b len) "STIX"; @method_id(101) fun test1(): [int,int,int,int,int] { - var b: builder = begin_cell().store_uint(1, 32); - b = b.store_uint(2, 32); - b~store_uint(3, 32); + var b: builder = beginCell().storeUint(1, 32); + b = b.storeUint(2, 32); + b~storeUint(3, 32); b = b.store_u32(4); b~store_u32(5); - var cs: slice = b.end_cell().begin_parse(); - var (cs redef, one: int) = cs.load_uint(32); - var (two: int, three: int) = (cs~load_uint(32), cs~load_u32()); + var cs: slice = b.endCell().beginParse(); + var (cs redef, one: int) = cs.loadUint(32); + var (two: int, three: int) = (cs~loadUint(32), cs~load_u32()); var (cs redef, four: int) = cs.load_u32(); var five: int = cs~load_u32(); @@ -35,82 +35,82 @@ fun test1(): [int,int,int,int,int] { @method_id(102) fun test2(): [int,int,int] { - var b: builder = begin_cell().my_store_int(1, 32); - b = b.my_store_int(2, 32); - b~my_store_int(3, 32); + var b: builder = beginCell().my_storeInt(1, 32); + b = b.my_storeInt(2, 32); + b~my_storeInt(3, 32); - var cs: slice = b.end_cell().begin_parse(); - var (cs redef, one: int) = cs.my_load_int(32); - var (two: int, three: int) = (cs~my_load_int(32), cs~my_load_int(32)); + var cs: slice = b.endCell().beginParse(); + var (cs redef, one: int) = cs.my_loadInt(32); + var (two: int, three: int) = (cs~my_loadInt(32), cs~my_loadInt(32)); return [one,two,three]; } @method_id(103) fun test3(ret: int): int { - var (_, same: int) = begin_cell().store_uint(ret,32).end_cell().begin_parse().load_uint(32); + var (_, same: int) = beginCell().storeUint(ret,32).endCell().beginParse().loadUint(32); return same; } @method_id(104) fun test4(): [int,int] { - var b: builder = my_store_int(begin_cell(), 1, 32); - b = store_int(store_int(b, 2, 32), 3, 32); + var b: builder = my_storeInt(beginCell(), 1, 32); + b = storeInt(storeInt(b, 2, 32), 3, 32); - var cs: slice = b.end_cell().begin_parse(); - var cs32: slice = cs.first_bits(32); // todo s.first_bits()~load_uint() doesn't work, 'lvalue expected' - var (one, _, three) = (cs32~load_int(32), cs~skip_bits(64), cs~load_u32()); + var cs: slice = b.endCell().beginParse(); + var cs32: slice = cs.getFirstBits(32); // todo s.first_bits()~loadUint() doesn't work, 'lvalue expected' + var (one, _, three) = (cs32~loadInt(32), cs~skipBits(64), cs~load_u32()); return [one,three]; } @method_id(105) fun test5(): [int,int] { - var cref: cell = end_cell(store_u32(begin_cell(), 105)); - var c: cell = begin_cell().store_ref(cref).store_ref(cref).store_u32(1).end_cell(); - - var cs: slice = begin_parse(c); - // todo I want cs~load_ref().begin_parse()~load_u32(), but 'lvalue expected' - var ref1 = cs~load_ref().begin_parse(); - var ref2 = cs~load_ref().begin_parse(); - var sto5x2: int = ref1~load_u32() + ref2~load_uint(32); + var cref: cell = endCell(store_u32(beginCell(), 105)); + var c: cell = beginCell().storeRef(cref).storeRef(cref).store_u32(1).endCell(); + + var cs: slice = beginParse(c); + // todo I want cs~loadRef().beginParse()~load_u32(), but 'lvalue expected' + var ref1 = cs~loadRef().beginParse(); + var ref2 = cs~loadRef().beginParse(); + var sto5x2: int = ref1~load_u32() + ref2~loadUint(32); return [sto5x2, cs~load_u32()]; } fun ~sumNumbersInSlice(s: slice): (slice, int) { var result = 0; - while (!slice_data_empty?(s)) { - result += s~load_uint(32); + while (!s.isEndOfSliceBits()) { + result += s~loadUint(32); } return (s, result); } @method_id(106) fun test6() { - var ref = begin_cell().store_int(100, 32).end_cell(); - var s: slice = begin_cell().store_int(1, 32).store_int(2, 32).store_ref(ref).end_cell().begin_parse(); - var result = (slice_bits(s), s~sumNumbersInSlice(), slice_bits(s), slice_empty?(s), slice_data_empty?(s), slice_refs_empty?(s)); - var ref2: cell = s~load_ref(); - var s2: slice = ref2.begin_parse(); - s.end_parse(); - return (result, s2~load_int(32), s2.slice_empty?()); + var ref = beginCell().storeInt(100, 32).endCell(); + var s: slice = beginCell().storeInt(1, 32).storeInt(2, 32).storeRef(ref).endCell().beginParse(); + var result = (getRemainingBitsCount(s), s~sumNumbersInSlice(), getRemainingBitsCount(s), isEndOfSlice(s), isEndOfSliceBits(s), isEndOfSliceRefs(s)); + var ref2: cell = s~loadRef(); + var s2: slice = ref2.beginParse(); + s.assertEndOfSlice(); + return (result, s2~loadInt(32), s2.isEndOfSlice()); } @method_id(107) fun test7() { - var s: slice = begin_cell().store_int(1, 32).store_int(2, 32).store_int(3, 32).store_int(4, 32).store_int(5, 32).store_int(6, 32).store_int(7, 32).end_cell().begin_parse(); - var size1 = slice_bits(s); - s~skip_bits(32); - var s1: slice = s.first_bits(64); - var n1 = s1~load_int(32); - var size2 = slice_bits(s); - s~load_int(32); - var size3 = slice_bits(s); - s~skip_last_bits(32); - var size4 = slice_bits(s); - var n2 = s~load_int(32); - var size5 = slice_bits(s); + var s: slice = beginCell().storeInt(1, 32).storeInt(2, 32).storeInt(3, 32).storeInt(4, 32).storeInt(5, 32).storeInt(6, 32).storeInt(7, 32).endCell().beginParse(); + var size1 = getRemainingBitsCount(s); + s~skipBits(32); + var s1: slice = s.getFirstBits(64); + var n1 = s1~loadInt(32); + var size2 = getRemainingBitsCount(s); + s~loadInt(32); + var size3 = getRemainingBitsCount(s); + s~removeLastBits(32); + var size4 = getRemainingBitsCount(s); + var n2 = s~loadInt(32); + var size5 = getRemainingBitsCount(s); return (n1, n2, size1, size2, size3, size4, size5); } @@ -118,13 +118,13 @@ fun test7() { fun test108() { var (result1, result2) = (0, 0); try { - begin_cell().store_ref(begin_cell().end_cell()).end_cell().begin_parse().end_parse(); + beginCell().storeRef(beginCell().endCell()).endCell().beginParse().assertEndOfSlice(); result1 = 100; } catch (code) { result1 = code; } try { - begin_cell().end_cell().begin_parse().end_parse(); + beginCell().endCell().beginParse().assertEndOfSlice(); result2 = 100; } catch (code) { result2 = code; @@ -134,18 +134,48 @@ fun test108() { @method_id(109) fun test109() { - var ref2 = begin_cell().store_int(1, 32).end_cell(); - var ref1 = begin_cell().store_int(1, 32).store_ref(ref2).end_cell(); - var c = begin_cell().store_int(444, 32).store_ref(ref1).store_ref(ref1).store_ref(ref1).store_ref(ref2).store_int(4, 32).end_cell(); - var (n_cells1, n_bits1, n_refs1) = c.compute_data_size(10); - var s = c.begin_parse(); - s~load_ref(); - s~load_ref(); - var n = s~load_int(32); - var (n_cells2, n_bits2, n_refs2) = s.slice_compute_data_size(10); + var ref2 = beginCell().storeInt(1, 32).endCell(); + var ref1 = beginCell().storeInt(1, 32).storeRef(ref2).endCell(); + var c = beginCell().storeInt(444, 32).storeRef(ref1).storeRef(ref1).storeRef(ref1).storeRef(ref2).storeInt(4, 32).endCell(); + var (n_cells1, n_bits1, n_refs1) = c.calculateCellSizeStrict(10); + var s = c.beginParse(); + s~loadRef(); + s~loadRef(); + var n = s~loadInt(32); + var (n_cells2, n_bits2, n_refs2) = s.calculateSliceSizeStrict(10); return ([n_cells1, n_bits1, n_refs1], [n_cells2, n_bits2, n_refs2], n); } +@method_id(110) +fun test110(x: int) { + var s = beginCell().storeBool(x < 0).storeBool(0).storeBool(x).endCell().beginParse(); + return (s~loadBool(), s~loadBool(), s~loadBool()); +} + +@method_id(111) +fun test111() { + var s = beginCell().storeMessageOp(123).storeMessageQueryId(456) + .storeAddressNone().storeAddressNone() + .storeUint(0, 32) + .storeUint(123, 32).storeUint(456, 64).storeUint(789, 64) + .endCell().beginParse(); + var op1 = s~loadUint(32); + var q1 = s~loadUint(64); + if (s.addressIsNone()) { + s~skipBits(2); + } + if (s~loadBool() == 0) { + assert(s~loadBool() == 0) throw 444; + s~skipBits(32); + } + var op2 = s~loadMessageOp(); + var q2 = s~loadMessageQueryId(); + s~skipBits(64); + s.assertEndOfSlice(); + assert(isMessageBounced(0x001)) throw 444; + return (op1, q1, op2, q2); +} + fun main(): int { return 0; } @@ -160,4 +190,7 @@ fun main(): int { @testcase | 107 | | 2 3 224 192 160 128 96 @testcase | 108 | | 9 100 @testcase | 109 | | [ 3 128 5 ] [ 2 96 3 ] 444 +@testcase | 110 | -1 | -1 0 -1 +@testcase | 110 | 0 | 0 0 0 +@testcase | 111 | | 123 456 123 456 */ diff --git a/tolk-tester/tests/co1.tolk b/tolk-tester/tests/co1.tolk index bc56dfa89..5ad9d8e41 100644 --- a/tolk-tester/tests/co1.tolk +++ b/tolk-tester/tests/co1.tolk @@ -43,7 +43,7 @@ asm "SDEQ"; fun stslicer(b: builder, s: slice): builder asm "STSLICER"; -fun storeUint(b: builder, x: int, len: int): builder { return store_uint(b, x, len); } +fun myStoreUint(b: builder, x: int, len: int): builder { return storeUint(b, x, len); } fun endSlice(b: builder): slice { return endcs(b); } fun main() { @@ -59,9 +59,9 @@ fun main() { var s2: slice = sget2(); var s3: slice = newc().stslicer(str1).stslicer(str2r).endcs(); - assert(sdeq(s1, newc().storeUint(str1int, 12 * nibbles).endcs())) throw int111; - assert(sdeq(s2, newc().store_uint(str2int, 6 * nibbles).endSlice())) throw 112; - assert(sdeq(s3, newc().store_uint(0x636f6e737431AABBCC, 18 * nibbles).endcs())) throw 113; + assert(sdeq(s1, newc().myStoreUint(str1int, 12 * nibbles).endcs())) throw int111; + assert(sdeq(s2, newc().storeUint(str2int, 6 * nibbles).endSlice())) throw 112; + assert(sdeq(s3, newc().storeUint(0x636f6e737431AABBCC, 18 * nibbles).endcs())) throw 113; var i4: int = iget240(); assert(i4 == 240) throw ((104)); diff --git a/tolk-tester/tests/dicts-demo.tolk b/tolk-tester/tests/dicts-demo.tolk new file mode 100644 index 000000000..5852b175c --- /dev/null +++ b/tolk-tester/tests/dicts-demo.tolk @@ -0,0 +1,106 @@ +import "@stdlib/tvm-dicts" + +fun ~addIntToIDict(iDict: cell, key: int, number: int): (cell, ()) { + iDict~iDictSetBuilder(32, key, beginCell().storeInt(number, 32)); + return (iDict, ()); +} + +fun calculateDictLen(d: cell) { + var len = 0; + var (k, v, f) = d.uDictGetFirst(32); + while (f) { + len += 1; + (k, v, f) = d.uDictGetNext(32, k); + } + return len; +} + +fun ~loadTwoDigitNumberFromSlice(s: slice): (slice, int) { + var n1 = s~loadInt(8); + var n2 = s~loadInt(8); + return (s, (n1 - 48) * 10 + (n2 - 48)); +} + + +@method_id(101) +fun test101(getK1: int, getK2: int, getK3: int) { + var dict = createEmptyDict(); + dict~uDictSetBuilder(32, 1, beginCell().storeUint(1, 32)); + var (old1: slice, found1) = dict~uDictSetAndGet(32, getK1, beginCell().storeUint(2, 32).endCell().beginParse()); + var (old2: slice, found2) = dict~uDictSetAndGet(32, getK2, beginCell().storeUint(3, 32).endCell().beginParse()); + var (cur3: slice, found3) = dict.uDictGet(32, getK3); + return ( + found1 ? old1~loadUint(32) : -1, + found2 ? old2~loadUint(32) : -1, + found3 ? cur3~loadUint(32) : -1 + ); +} + +@method_id(102) +fun test102() { + var dict = createEmptyDict(); + dict~addIntToIDict(2, 102); + dict~addIntToIDict(1, 101); + dict~addIntToIDict(4, 104); + dict~addIntToIDict(3, 103); + var deleted = createEmptyTuple(); + var shouldBreak = false; + while (!shouldBreak) { + var (kDel, kVal, wasDel) = dict~iDictDeleteLastAndGet(32); + if (wasDel) { + deleted~tuplePush([kDel, kVal~loadInt(32)]); + } else { + shouldBreak = true; + } + } + return deleted; +} + +@method_id(103) +fun test103() { + var dict = createEmptyDict(); + dict~uDictSetBuilderIfNotExists(32, 1,beginCell().storeInt(1, 32)); + dict~uDictSetBuilderIfNotExists(32, 1,beginCell().storeInt(1, 32)); + var len1 = calculateDictLen(dict); + dict~uDictSetBuilderIfExists(32, 2,beginCell().storeInt(1, 32)); + dict~uDictSetBuilderIfExists(32, 2,beginCell().storeInt(1, 32)); + var len2 = calculateDictLen(dict); + dict~uDictSetBuilder(32, 3,beginCell().storeInt(1, 32)); + dict~uDictSetBuilderIfExists(32, 3,beginCell().storeInt(1, 32)); + var len3 = calculateDictLen(dict); + var (delK1, _, _) = dict~uDictDeleteFirstAndGet(32); + var (delK2, _, _) = dict~uDictDeleteFirstAndGet(32); + var (delK3, _, _) = dict~uDictDeleteFirstAndGet(32); + return (len1, len2, len3, delK1, delK2, delK3); +} + +@method_id(104) +fun test104() { + var dict = createEmptyDict(); + dict~sDictSetBuilder(32, "7800", beginCell().storeUint(5 + 48, 8).storeUint(6 + 48, 8)); + dict~sDictSet(32, "key1", "12"); + var (old1, _) = dict~sDictSetAndGet(32, "key1", "34"); + var (old2, _) = dict~sDictDeleteAndGet(32, "key1"); + var (restK, restV, _) = dict.sDictGetFirst(32); + var (restK1, restV1, _) = dict~sDictDeleteLastAndGet(32); + assert (restK.isSliceBitsEqual(restK1)) throw 123; + assert (restV.isSliceBitsEqual(restV1)) throw 123; + return ( + old1~loadTwoDigitNumberFromSlice(), + old2~loadTwoDigitNumberFromSlice(), + restV~loadTwoDigitNumberFromSlice(), + restK~loadTwoDigitNumberFromSlice(), + restK~loadTwoDigitNumberFromSlice() + ); +} + +fun main() {} + +/** +@testcase | 101 | 1 1 1 | 1 2 3 +@testcase | 101 | 1 2 1 | 1 -1 2 +@testcase | 101 | 1 2 3 | 1 -1 -1 +@testcase | 102 | | [ [ 4 104 ] [ 3 103 ] [ 2 102 ] [ 1 101 ] ] +@testcase | 103 | | 1 1 2 1 3 (null) +@testcase | 104 | | 12 34 56 78 0 + */ diff --git a/tolk-tester/tests/imports/use-dicts-err.tolk b/tolk-tester/tests/imports/use-dicts-err.tolk new file mode 100644 index 000000000..a4ee9aede --- /dev/null +++ b/tolk-tester/tests/imports/use-dicts-err.tolk @@ -0,0 +1,21 @@ +fun prepareDict_3_30_4_40_5_x(valueAt5: int): cell { + var dict: cell = createEmptyDict(); + dict~idict_set_builder(32, 3, begin_cell().store_int(30, 32)); + dict~idict_set_builder(32, 4, begin_cell().store_int(40, 32)); + dict~idict_set_builder(32, 5, begin_cell().store_int(valueAt5, 32)); + return dict; +} + +fun lookupIdxByValue(idict32: cell, value: int): int { + var cur_key = -1; + do { + var (cur_key redef, cs: slice, found: int) = idict32.idict_get_next?(32, cur_key); + // one-line condition (via &) doesn't work, since right side is calculated immediately + if (found) { + if (cs~load_int(32) == value) { + return cur_key; + } + } + } while (found); + return -1; +} diff --git a/tolk-tester/tests/imports/use-dicts.tolk b/tolk-tester/tests/imports/use-dicts.tolk new file mode 100644 index 000000000..358a5673a --- /dev/null +++ b/tolk-tester/tests/imports/use-dicts.tolk @@ -0,0 +1,23 @@ +import "@stdlib/tvm-dicts" + +fun prepareDict_3_30_4_40_5_x(valueAt5: int): cell { + var dict: cell = createEmptyDict(); + dict~iDictSetBuilder(32, 3, beginCell().storeInt(30, 32)); + dict~iDictSetBuilder(32, 4, beginCell().storeInt(40, 32)); + dict~iDictSetBuilder(32, 5, beginCell().storeInt(valueAt5, 32)); + return dict; +} + +fun lookupIdxByValue(idict32: cell, value: int): int { + var cur_key = -1; + do { + var (cur_key redef, cs: slice, found: int) = idict32.iDictGetNext(32, cur_key); + // one-line condition (via &) doesn't work, since right side is calculated immediately + if (found) { + if (cs~loadInt(32) == value) { + return cur_key; + } + } + } while (found); + return -1; +} diff --git a/tolk-tester/tests/invalid-import.tolk b/tolk-tester/tests/invalid-import.tolk index b1c01518e..416764b62 100644 --- a/tolk-tester/tests/invalid-import.tolk +++ b/tolk-tester/tests/invalid-import.tolk @@ -4,6 +4,8 @@ /** @compilation_should_fail -@stderr invalid-import.tolk:2:7: error: Failed to import: cannot find file +On Linux/Mac, `realpath()` returns an error, and the error message is `cannot find file` +On Windows, it fails after, on reading, with a message "cannot open file" +@stderr invalid-import.tolk:2:7: error: Failed to import: cannot @stderr import "unexisting.tolk"; */ diff --git a/tolk-tester/tests/invalid-no-import.tolk b/tolk-tester/tests/invalid-no-import-1.tolk similarity index 100% rename from tolk-tester/tests/invalid-no-import.tolk rename to tolk-tester/tests/invalid-no-import-1.tolk diff --git a/tolk-tester/tests/invalid-no-import-2.tolk b/tolk-tester/tests/invalid-no-import-2.tolk new file mode 100644 index 000000000..d78346b90 --- /dev/null +++ b/tolk-tester/tests/invalid-no-import-2.tolk @@ -0,0 +1,9 @@ +import "@stdlib/tvm-dicts" +import "imports/use-dicts-err.tolk" + +/** +@compilation_should_fail +@stderr imports/use-dicts-err.tolk:2:22 +@stderr Using a non-imported symbol `createEmptyDict` +@stderr Forgot to import "@stdlib/tvm-dicts"? + */ diff --git a/tolk-tester/tests/invalid-pure-2.tolk b/tolk-tester/tests/invalid-pure-2.tolk index 5f8f40ec8..213206834 100644 --- a/tolk-tester/tests/invalid-pure-2.tolk +++ b/tolk-tester/tests/invalid-pure-2.tolk @@ -2,7 +2,7 @@ global g: int; @pure fun f_pure(): builder { - var b: builder = begin_cell(); + var b: builder = beginCell(); g = g + 1; return b; } diff --git a/tolk-tester/tests/invalid-pure-3.tolk b/tolk-tester/tests/invalid-pure-3.tolk index 0e1b4104a..f64b81ce7 100644 --- a/tolk-tester/tests/invalid-pure-3.tolk +++ b/tolk-tester/tests/invalid-pure-3.tolk @@ -1,12 +1,12 @@ @pure fun validate_input(input: cell): (int, int) { - var (x, y, z, correct) = compute_data_size?(input, 10); + var (x, y, z, correct) = calculateCellSize(input, 10); assert(correct) throw 102; } @pure fun someF(): int { - var c: cell = begin_cell().end_cell(); + var c: cell = beginCell().endCell(); validate_input(c); return 0; } diff --git a/tolk-tester/tests/invalid-redefinition-1.tolk b/tolk-tester/tests/invalid-redefinition-1.tolk index 49771cea1..5238a6801 100644 --- a/tolk-tester/tests/invalid-redefinition-1.tolk +++ b/tolk-tester/tests/invalid-redefinition-1.tolk @@ -1,7 +1,7 @@ -global moddiv: int; +global mulDivMod: int; /** @compilation_should_fail -@stderr global moddiv: int; +@stderr global mulDivMod: int; @stderr redefinition of built-in symbol */ diff --git a/tolk-tester/tests/invalid-symbol-1.tolk b/tolk-tester/tests/invalid-symbol-1.tolk index 5d392f529..08a86f176 100644 --- a/tolk-tester/tests/invalid-symbol-1.tolk +++ b/tolk-tester/tests/invalid-symbol-1.tolk @@ -4,11 +4,11 @@ fun main(x: int): int { } else { var y: slice = "20"; } - ~dump(y); + debugPrint(y); } /** @compilation_should_fail -@stderr ~dump(y); +@stderr debugPrint(y); @stderr undefined symbol `y` */ diff --git a/tolk-tester/tests/logical-operators.tolk b/tolk-tester/tests/logical-operators.tolk index b73c25220..ec0e7a87c 100644 --- a/tolk-tester/tests/logical-operators.tolk +++ b/tolk-tester/tests/logical-operators.tolk @@ -1,3 +1,5 @@ +import "imports/use-dicts.tolk" + fun simpleAllConst() { return (!0, !!0 & !false, !!!0, !1, !!1, !-1, !!-1, (!5 == 0) == !0, !0 == true); } @@ -43,29 +45,10 @@ fun someSum(upto: int) { return x; } - -fun lookupIdxByValue(idict32: cell, value: int) { - var cur_key = -1; - do { - var (cur_key redef, cs: slice, found: int) = idict32.idict_get_next?(32, cur_key); - // todo one-line condition (via &) doesn't work, since right side is calculated immediately - if (found) { - if (cs~load_int(32) == value) { - return cur_key; - } - } - } while (found); - return -1; -} - @method_id(104) fun testDict(last: int) { - // prepare dict: [3 => 30, 4 => 40, 5 => 50] - var dict: cell = new_dict(); - dict~idict_set_builder(32, 3, begin_cell().store_int(30, 32)); - dict~idict_set_builder(32, 4, begin_cell().store_int(40, 32)); - dict~idict_set_builder(32, 5, begin_cell().store_int(!last ? 100 : last, 32)); - + // prepare dict: [3 => 30, 4 => 40, 5 => x] + var dict = prepareDict_3_30_4_40_5_x(!last ? 100 : last); return (lookupIdxByValue(dict, 30), lookupIdxByValue(dict, last), lookupIdxByValue(dict, 100)); } diff --git a/tolk-tester/tests/no-spaces.tolk b/tolk-tester/tests/no-spaces.tolk index 018c99da2..aecfdabaa 100644 --- a/tolk-tester/tests/no-spaces.tolk +++ b/tolk-tester/tests/no-spaces.tolk @@ -50,14 +50,14 @@ fun add3(a: int, b: int, c: int) { return a+b+c; } } fun `load:u32`(cs: slice): (slice, int) { - return cs.load_uint(32); + return cs.loadUint(32); } @method_id(116) fun `call_~_via_backticks`():[int,int,int,int] { - var b:builder = begin_cell().store_uint(1, 32).store_uint(2, 32).store_uint(3, 32).store_uint(4, 32); - var `cs`:slice = b.end_cell().begin_parse(); - var (`cs` redef,one:int) = `cs`.`load_uint`(32); - var (two:int,three:int) = (`cs`~`load_uint`(32), cs~`load:u32`()); + var b:builder = beginCell().storeUint(1, 32).storeUint(2, 32).storeUint(3, 32).storeUint(4, 32); + var `cs`:slice = b.endCell().beginParse(); + var (`cs` redef,one:int) = `cs`.`loadUint`(32); + var (two:int,three:int) = (`cs`~`loadUint`(32), cs~`load:u32`()); var (cs redef,four:int) = cs.`load:u32`(); return [one,two,three,four]; } diff --git a/tolk-tester/tests/null-keyword.tolk b/tolk-tester/tests/null-keyword.tolk index 0be4966ff..f0f85fd60 100644 --- a/tolk-tester/tests/null-keyword.tolk +++ b/tolk-tester/tests/null-keyword.tolk @@ -1,18 +1,19 @@ -import "../../crypto/smartcont/stdlib.tolk" +import "@stdlib/lisp-lists" + @method_id(101) fun test1() { - var numbers: tuple = null; - numbers = cons(1, numbers); - numbers = cons(2, numbers); - numbers = cons(3, numbers); - numbers = cons(4, numbers); - var (h, numbers redef) = uncons(numbers); - h += car(numbers); - - var t = empty_tuple(); + var numbers: tuple = createEmptyList(); + numbers = listPrepend(1, numbers); + numbers = listPrepend(2, numbers); + numbers = listPrepend(3, numbers); + numbers = listPrepend(4, numbers); + var (h, numbers redef) = listSplit(numbers); + h += listGetHead(numbers); + + var t = createEmptyTuple(); do { - var num = numbers~list_next(); - t~tpush(num); + var num = numbers~listNext(); + t~tuplePush(num); } while (numbers != null); return (h, numbers == null, t); @@ -52,7 +53,7 @@ fun getUntypedNull() { @method_id(104) fun test4() { - var (_, (_, untyped)) = (3, (empty_tuple, null)); + var (_, (_, untyped)) = (3, (createEmptyTuple, null)); if (true) { return untyped; } @@ -62,7 +63,7 @@ fun test4() { @method_id(105) fun test5() { var n = getUntypedNull(); - return !(null == n) ? n~load_int(32) : 100; + return !(null == n) ? n~loadInt(32) : 100; } @method_id(106) @@ -72,9 +73,9 @@ fun test6(x: int) { @method_id(107) fun test7() { - var b = begin_cell().store_maybe_ref(null); - var s = b.end_cell().begin_parse(); - var c = s~load_maybe_ref(); + var b = beginCell().storeMaybeRef(null); + var s = b.endCell().beginParse(); + var c = s~loadMaybeRef(); return (null == c) * 10 + (b != null); } diff --git a/tolk-tester/tests/pure-functions.tolk b/tolk-tester/tests/pure-functions.tolk index 59b2f0da5..6e7a6ddad 100644 --- a/tolk-tester/tests/pure-functions.tolk +++ b/tolk-tester/tests/pure-functions.tolk @@ -11,16 +11,16 @@ fun f_pure2(): int { @pure fun get_contract_data(): (int, int) { - var c: cell = get_data(); - var cs: slice = c.begin_parse(); - cs~load_bits(32); - var value: int = cs~load_uint(16); + var c: cell = getContractData(); + var cs: slice = c.beginParse(); + cs~loadBits(32); + var value: int = cs~loadUint(16); return (1, value); } fun save_contract_data(value: int) { - var b: builder = begin_cell().store_int(1, 32).store_uint(value, 16); - set_data(b.end_cell()); + var b: builder = beginCell().storeInt(1, 32).storeUint(value, 16); + setContractData(b.endCell()); } @pure diff --git a/tolk-tester/tests/s1.tolk b/tolk-tester/tests/s1.tolk index 3f75f1a70..c7c4f6946 100644 --- a/tolk-tester/tests/s1.tolk +++ b/tolk-tester/tests/s1.tolk @@ -43,10 +43,10 @@ fun main() { var i_mini: int = string_minihash(); var i_maxi: int = string_maxihash(); var i_crc: int = string_crc32(); - assert(sdeq(s_ascii, newc().store_uint(0x737472696E67, 12 * 4).endcs())) throw 101; - assert(sdeq(s_raw, newc().store_uint(0xABCDEF, 6 * 4).endcs())) throw 102; - assert(sdeq(s_addr, newc().store_uint(4, 3).store_int(-1, 8) - .store_uint(0x3333333333333333333333333333333333333333333333333333333333333333, 256).endcs()), 103); + assert(sdeq(s_ascii, newc().storeUint(0x737472696E67, 12 * 4).endcs())) throw 101; + assert(sdeq(s_raw, newc().storeUint(0xABCDEF, 6 * 4).endcs())) throw 102; + assert(sdeq(s_addr, newc().storeUint(4, 3).storeInt(-1, 8) + .storeUint(0x3333333333333333333333333333333333333333333333333333333333333333, 256).endcs()), 103); assert(i_hex == 0x4142434445464748494A4B4C4D4E4F505152535455565758595A303132333435) throw 104; assert(i_mini == 0x7a62e8a8) throw 105; assert(i_maxi == 0x7a62e8a8ebac41bd6de16c65e7be363bc2d2cbc6a0873778dead4795c13db979) throw 106; diff --git a/tolk-tester/tests/test-math.tolk b/tolk-tester/tests/test-math.tolk index dde4c2b32..7c9873f09 100644 --- a/tolk-tester/tests/test-math.tolk +++ b/tolk-tester/tests/test-math.tolk @@ -1,4 +1,982 @@ -import "../../crypto/smartcont/mathlib.tolk"; +// this is actually `mathlib.fc` transformed to Tolk + +import "@stdlib/tvm-lowlevel" + +/*--------------- MISSING OPERATIONS AND BUILT-INS ----------------*/ + +/// compute floor(log2(x))+1 +@pure +fun log2_floor_p1(x: int): int + asm "UBITSIZE"; + +@pure +fun mulrshiftr(x: int, y: int, s: int): int + asm "MULRSHIFTR"; + +@pure +fun mulrshiftr256(x: int, y: int): int + asm "256 MULRSHIFTR#"; + +@pure +fun mulrshift256mod(x: int, y: int): (int, int) + asm "256 MULRSHIFT#MOD"; + +@pure +fun mulrshiftr256mod(x: int, y: int): (int, int) + asm "256 MULRSHIFTR#MOD"; + +@pure +fun mulrshiftr255mod(x: int, y: int): (int, int) + asm "255 MULRSHIFTR#MOD"; + +@pure +fun mulrshiftr248mod(x: int, y: int): (int, int) + asm "248 MULRSHIFTR#MOD"; + +@pure +fun mulrshiftr5mod(x: int, y: int): (int, int) + asm "5 MULRSHIFTR#MOD"; + +@pure +fun mulrshiftr6mod(x: int, y: int): (int, int) + asm "6 MULRSHIFTR#MOD"; + +@pure +fun mulrshiftr7mod(x: int, y: int): (int, int) + asm "7 MULRSHIFTR#MOD"; + +@pure +fun lshift256divr(x: int, y: int): int + asm "256 LSHIFT#DIVR"; + +@pure +fun lshift256divmodr(x: int, y: int): (int, int) + asm "256 LSHIFT#DIVMODR"; + +@pure +fun lshift255divmodr(x: int, y: int): (int, int) + asm "255 LSHIFT#DIVMODR"; + +@pure +fun lshift2divmodr(x: int, y: int): (int, int) + asm "2 LSHIFT#DIVMODR"; + +@pure +fun lshift7divmodr(x: int, y: int): (int, int) + asm "7 LSHIFT#DIVMODR"; + +@pure +fun lshiftdivmodr(x: int, y: int, s: int): (int, int) + asm "LSHIFTDIVMODR"; + +@pure +fun rshiftr256mod(x: int): (int, int) + asm "256 RSHIFTR#MOD"; + +@pure +fun rshiftr248mod(x: int): (int, int) + asm "248 RSHIFTR#MOD"; + +@pure +fun rshiftr4mod(x: int): (int, int) + asm "4 RSHIFTR#MOD"; + +@pure +fun rshift3mod(x: int): (int, int) + asm "3 RSHIFT#MOD"; + +/// computes y - x (Tolk compiler does not try to use this by itself) +@pure +fun sub_rev(x: int, y: int): int + asm "SUBR"; + +@pure +fun nan(): int + asm "PUSHNAN"; + +@pure +fun is_nan(x: int): int + asm "ISNAN"; + +/*----------------------- SQUARE ROOTS ---------------------------*/ + +/// computes sqrt(a*b) exactly rounded to the nearest integer +/// for all 0 <= a, b <= 2^256-1 +/// may be used with b=1 or b=scale of fixed-point numbers +@pure +@inline_ref +fun geom_mean(a: int, b: int): int { + if (!min(a, b)) { + return 0; + } + var s: int = log2_floor_p1(a); // throws out of range error if a < 0 or b < 0 + var t: int = log2_floor_p1(b); + // NB: (a-b)/2+b == (a+b)/2, but without overflow for large a and b + var x: int = (s == t ? (a - b) / 2 + b : 1 << ((s + t) / 2)); + do { + // if always used with b=2^const, may be optimized to "const LSHIFTDIVC#" + // it is important to use `mulDivCeil` here, not `mulDivFloor` or `mulDivRound` + var q: int = (mulDivCeil(a, b, x) - x) / 2; + x += q; + } while (q); + return x; +} + +/// integer square root, computes round(sqrt(a)) for all a>=0. +/// note: `inline` is better than `inline_ref` for such simple functions +@pure +@inline +fun sqrt(a: int): int { + return geom_mean(a, 1); +} + +/// version for fixed248 = fixed-point numbers with scale 2^248 +/// fixed248 sqrt(fixed248 x) +@pure +@inline +fun fixed248_sqrt(x: int): int { + return geom_mean(x, 1 << 248); +} + +/// fixed255 sqrt(fixed255 x) +@pure +@inline +fun fixed255_sqrt(x: int): int { + return geom_mean(x, 1 << 255); +} + +/// fixed248 sqr(fixed248 x); +@pure +@inline +fun fixed248_sqr(x: int): int { + return mulDivRound(x, x, 1 << 248); +} + +/// fixed255 sqr(fixed255 x); +@pure +@inline +fun fixed255_sqr(x: int): int { + return mulDivRound(x, x, 1 << 255); +} + +const fixed248_One: int = (1 << 248); +const fixed255_One: int = (1 << 255); + +/*------------------- USEFUL CONSTANTS -------------------*/ + +/// store huge constants in inline_ref functions for reuse +/// (y,z) where y=round(log(2)*2^256), z=round((log(2)*2^256-y)*2^128) +/// then log(2) = y/2^256 + z/2^384 +@pure +@inline_ref +fun log2_xconst_f256(): (int, int) { + return (80260960185991308862233904206310070533990667611589946606122867505419956976172, -32272921378999278490133606779486332143); +} + +/// (y,z) where Pi = y/2^254 + z/2^382 +@pure +@inline_ref +fun Pi_xconst_f254(): (int, int) { + return (90942894222941581070058735694432465663348344332098107489693037779484723616546, 108051869516004014909778934258921521947); +} + +/// atan(1/16) as fixed260 +@pure +@inline_ref +fun Atan1_16_f260(): int { + return 115641670674223639132965820642403718536242645001775371762318060545014644837101; // true value is ...101.0089... +} + +/// atan(1/8) as fixed259 +@pure +@inline_ref +fun Atan1_8_f259(): int { + return 115194597005316551477397594802136977648153890007566736408151129975021336532841; // correction -0.1687... +} + +/// atan(1/32) as fixed261 +@pure +@inline_ref +fun Atan1_32_f261(): int { + return 115754418570128574501879331591757054405465733718902755858991306434399246026247; // correction 0.395... +} + +/// inline is better than inline_ref for such very small functions +@pure +@inline +fun log2_const_f256(): int { + var (c: int, _) = log2_xconst_f256(); + return c; +} + +@pure +@inline +fun fixed248_log2_const(): int { + return log2_const_f256() ~>> 8; +} + +@pure +@inline +fun Pi_const_f254(): int { + var (c: auto, _) = Pi_xconst_f254(); + return c; +} + +@pure +@inline +fun fixed248_Pi_const(): int { + return Pi_const_f254() ~>> 6; +} + +/*-------------- HYPERBOLIC TANGENT AND EXPONENT ------------------*/ + +/// hyperbolic tangent of small x via n+2 terms of Lambert's continued fraction +/// n=17: good for |x| < log(2)/4 = 0.173 +/// fixed258 tanh_f258(fixed258 x, int n) +@pure +@inline_ref +fun tanh_f258(x: int, n: int): int { + var x2: int = mulDivRound(x, x, 1 << 255); // x^2 as fixed261 + var a: int = (2 * n + 5) << 250; // a=2n+5 as fixed250 + var c = a; + var Two: int = (1 << 251); // 2. as fixed250 + repeat (n) { + a = (c -= Two) + mulDivRound(x2, 1 << 239, a); // a := 2k+1+x^2/a as fixed250, k=n+1,n,...,2 + } + a = (stackMoveToTop(3) << 254) + mulDivRound(x2, 1 << 243, a); // a := 3+x^2/a as fixed254 + // y = x/(1+a') = x - x*a'/(1+a') = x - x*x^2/(a+x^2) where a' = x^2/a + return x - (mulDivRound(x, x2, a + (x2 ~>> 7)) ~>> 7); +} + +/// fixed257 expm1_f257(fixed257 x) +/// computes exp(x)-1 for small x via 19 terms of Lambert's continued fraction for tanh(x/2) +/// good for |x| < log(2)/2 = 0.347 (n=17); consumes ~3500 gas +@pure +@inline_ref +fun expm1_f257(x: int): int { + // (almost) compute tanh(x/2) first; x/2 as fixed258 = x as fixed257 + var x2: int = mulDivRound(x, x, 1 << 255); // x^2 as fixed261 + var Two: int = (1 << 251); // 2. as fixed250 + var a: int = stackMoveToTop(39) << 250; // a=2n+5 as fixed250 + var c = a; + repeat (17) { + a = (c -= Two) + mulDivRound(x2, 1 << 239, a); // a := 2k+1+x^2/a as fixed250, k=n+1,n,...,2 + } + a = (stackMoveToTop(3) << 254) + mulDivRound(x2, 1 << 243, a); // a := 3+x^2/a as fixed254 + // now tanh(x/2) = x/(1+a') where a'=x^2/a ; apply exp(x)-1=2*tanh(x/2)/(1-tanh(x/2)) + var t: int = (x ~>> 4) - a; // t:=x-a as fixed254 + return x - mulDivRound(x2, t / 2, a + mulrshiftr256(x, t) ~/ 4) ~/ 4; // x - x^2 * (x-a) / (a + x*(x-a)) +} + +/// expm1_f257() may be used to implement specific fixed-point exponentials +/// example: +/// fixed248 exp(fixed248 x) +@pure +@inline_ref +fun fixed248_exp(x: int): int { + var (l2c, l2d) = log2_xconst_f256(); + // divide x by log(2) and convert to fixed257 + // (int q, x) = muldivmodr(x, 256, l2c); // unfortunately, no such built-in + var (q: int, x redef) = lshiftdivmodr(x, l2c, 8); + x = 2 * x - mulDivRound(q, l2d, 1 << 127); + var y: int = expm1_f257(x); + // result is (1 + y) * (2^q) --> ((1 << 257) + y) >> (9 - q) + return (y ~>> (9 - q)) - (-1 << (248 + q)); + // note that (y ~>> (9 - q)) + (1 << (248 + q)) leads to overflow when q=8 +} + +/// compute 2^x in fixed248 +/// fixed248 exp2(fixed248 x) +@pure +@inline_ref +fun fixed248_exp2(x: int): int { + // (int q, x) = divmodr(x, 1 << 248); // no such built-in + var (q: int, x redef) = rshiftr248mod(x); + x = mulDivRound(x, log2_const_f256(), 1 << 247); + var y: int = expm1_f257(x); + return (y ~>> (9 - q)) - (-1 << (248 + q)); +} + +/*-------------------- TRIGONOMETRIC FUNCTIONS ----------------------*/ + +/// fixed260 tan(fixed260 x); +/// computes tan(x) for small |x|> 10)) ~>> 9); +} + +/// fixed260 tan(fixed260 x); +@pure +@inline_ref +fun tan_f260(x: int): int { + return tan_f260_inlined(x); +} + +/// fixed258 tan(fixed258 x); +/// computes tan(x) for small |x|> 6)) ~>> 5); +} + +/// fixed258 tan(fixed258 x); +@pure +@inline_ref +fun tan_f258(x: int): int { + return tan_f258_inlined(x); +} + +/// (fixed259, fixed263) sincosm1(fixed259 x) +/// computes (sin(x), 1-cos(x)) for small |x|<2*atan(1/16) +@pure +@inline +fun sincosm1_f259_inlined(x: int): (int, int) { + var t: int = tan_f260_inlined(x); // t=tan(x/2) as fixed260 + var tt: int = mulrshiftr256(t, t); // t^2 as fixed264 + var y: int = tt ~/ 512 + (1 << 255); // 1+t^2 as fixed255 + // 2*t/(1+t^2) as fixed259 and 2*t^2/(1+t^2) as fixed263 + // return (mulDivRound(t, 1 << 255, y), mulDivRound(tt, 1 << 255, y)); + return (t - mulDivRound(t / 2, tt, y) ~/ 256, tt - mulDivRound(tt / 2, tt, y) ~/ 256); +} + +@pure +@inline_ref +fun sincosm1_f259(x: int): (int, int) { + return sincosm1_f259_inlined(x); +} + +/// computes (sin(x+xe),-cos(x+xe)) for |x| <= Pi/4, xe very small +/// this function is very accurate, error less than 0.7 ulp (consumes ~ 5500 gas) +/// (fixed256, fixed256) sincosn(fixed256 x, fixed259 xe) +@pure +@inline_ref +fun sincosn_f256(x: int, xe: int): (int, int) { + // var (q, x1) = muldivmodr(x, 8, Atan1_8_f259()); // no muldivmodr() builtin + var (q, x1) = lshift2divmodr(abs(x), Atan1_8_f259()); // reduce mod theta where theta=2*atan(1/8) + var (si, co) = sincosm1_f259(x1 * 2 + xe); + var (a, b, c) = (-1, 0, 1); + repeat (q) { + // (a+b*I) *= (8+I)^2 = 63+16*I + (a, b, c) = (63 * a - 16 * b, 16 * a + 63 * b, 65 * c); + } + // now a/c = cos(q*theta), b/c = sin(q*theta) exactly(!) + // compute (a+b*I)*(1-co+si*I)/c + // (b, a) = (lshift256divr(b, c), lshift256divr(a, c)); + var (b redef, br: int) = lshift256divmodr(b, c); br = mulDivRound(br, 128, c); + var (a redef, ar: int) = lshift256divmodr(a, c); ar = mulDivRound(ar, 128, c); + return (sign(x) * (((mulrshiftr256(b, co) - br) ~/ 16 - mulrshiftr256(a, si)) ~/ 8 - b), + a - ((mulrshiftr256(a, co) - ar) ~/ 16 + mulrshiftr256(b, si)) ~/ 8); +} + +/// compute (sin(x),1-cos(x)) in fixed256 for |x| < 16*atan(1/16) = 0.9987 +/// (fixed256, fixed257) sincosm1_f256(fixed256 x); +/// slightly less accurate than sincosn_f256() (error up to 3/2^256), but faster (~ 4k gas) and shorter +@pure +@inline_ref +fun sincosm1_f256(x: int): (int, int) { + var (si, co) = sincosm1_f259_inlined(x); // compute (sin,1-cos)(x/8) in (fixed259,fixed263) + var r: int = 7; + repeat (r / 2) { + // 1-cos(2*x) = 2*sin(x)^2, sin(2*x) = 2*sin(x)*cos(x) + (co, si) = (mulrshiftr256(si, si), si - (mulrshiftr256(si, co) ~>> r)); + r -= 2; + } + return (si, co); +} + +/// compute (p, q) such that p/q = tan(x) for |x|<2*atan(1/2)=1899/2048=0.927 +/// (int, int) tan_aux(fixed256 x); +@pure +@inline_ref +fun tan_aux_f256(x: int): (int, int) { + var t: int = tan_f258_inlined(x); // t=tan(x/4) as fixed258 + // t:=2*t/(1-t^2)=2*(t-t^3/(t^2-1)) + var tt: int = mulrshiftr256(t, t); // t^2 as fixed260 + t = mulDivRound(t, tt, tt ~/ 16 + (-1 << 256)) ~/ 16 - t; // now t=-tan(x/2) as fixed259 + return (t, mulrshiftr256(t, t) ~/ 4 + (-1 << 256)); // return (2*t, t^2-1) as fixed256 +} + +/// sincosm1_f256() and sincosn_f256() may be used to implement trigonometric functions for different fixed-point types +/// example: +/// (fixed248, fixed248) sincos(fixed248 x); +@pure +@inline_ref +fun fixed248_sincos(x: int): (int, int) { + var (Pic, Pid) = Pi_xconst_f254(); + // (int q, x) = muldivmodr(x, 128, Pic); // no muldivmodr() builtin + var (q: int, x redef) = lshift7divmodr(x, Pic); // reduce mod Pi/2 + x = 2 * x - mulDivRound(q, Pid, 1 << 127); + var (si: int, co: int) = sincosm1_f256(x); // doesn't make sense to use more accurate sincosn_f256() + co = (1 << 248) - (co ~>> 9); + si = si ~>> 8; + repeat (q & 3) { + (si, co) = (co, -si); + } + return (si, co); +} + +/// fixed248 sin(fixed248 x); +/// inline is better than inline_ref for such simple functions +@pure +@inline +fun fixed248_sin(x: int): int { + var (si: int, _) = fixed248_sincos(x); + return si; +} + +/// fixed248 cos(fixed248 x); +@pure +@inline +fun fixed248_cos(x: int): int { + var (_, co: int) = fixed248_sincos(x); + return co; +} + +/// similarly, tan_aux_f256() may be used to implement tan() and cot() for specific fixed-point formats +/// fixed248 tan(fixed248 x); +/// not very accurate when |tan(x)| is very large (difficult to do better without floating-point numbers) +/// however, the relative accuracy is approximately 2^-247 in all cases, which is good enough for arguments given up to 2^-249 +@pure +@inline_ref +fun fixed248_tan(x: int): int { + var (Pic, Pid) = Pi_xconst_f254(); + // (int q, x) = muldivmodr(x, 128, Pic); // no muldivmodr() builtin + var (q: int, x redef) = lshift7divmodr(x, Pic); // reduce mod Pi/2 + x = 2 * x - mulDivRound(q, Pid, 1 << 127); + var (a, b) = tan_aux_f256(x); // now a/b = tan(x') + if (q & 1) { + (a, b) = (b, -a); + } + return mulDivRound(a, 1 << 248, b); // either -b/a or a/b as fixed248 +} + +/// fixed248 cot(fixed248 x); +@pure +@inline_ref +fun fixed248_cot(x: int): int { + var (Pic, Pid) = Pi_xconst_f254(); + var (q: int, x redef) = lshift7divmodr(x, Pic); // reduce mod Pi/2 + x = 2 * x - mulDivRound(q, Pid, 1 << 127); + var (b, a) = tan_aux_f256(x); // now b/a = tan(x') + if (q & 1) { + (a, b) = (b, -a); + } + return mulDivRound(a, 1 << 248, b); // either -b/a or a/b as fixed248 +} + +/*---------------- INVERSE HYPERBOLIC TANGENT AND LOGARITHMS ----------------*/ + +/// inverse hyperbolic tangent of small x, evaluated by means of n terms of the continued fraction +/// valid for |x| < 2^-2.5 ~ 0.18 if n=37 (slightly less accurate with n=36) +/// |x| < 1/8 if n=32; |x| < 2^-3.5 if n=28; |x| < 1/16 if n=25 +/// |x| < 2^-4.5 if n=23; |x| < 1/32 if n=21; |x| < 1/64 if n=18 +/// fixed258 atanh(fixed258 x); +@pure +@inline_ref +fun atanh_f258(x: int, n: int): int { + var x2: int = mulrshiftr256(x, x); // x^2 as fixed260 + var One: int = (1 << 254); + var a: int = One ~/ n + (1 << 255); // a := 2 + 1/n as fixed254 + repeat (n - 1) { + // a := 1 + (1 - x^2 / a)(1 + 1/n) as fixed254 + var t: int = One - mulDivRound(x2, 1 << 248, a); // t := 1 - x^2 / a + var n1: int = n - 1; + a = mulDivRound(t, n, n1) + One; + n = n1; + } + // x / (1 - x^2 / a) = x / (1 - d) = x + x * d / (1 - d) for d = x^2 / a + // int d = mulDivRound(x2, 1 << 255, a - (x2 ~>> 6)); // d/(1-d) = x^2/(a-x^2) as fixed261 + // return x + (mulrshiftr256(x, d) ~>> 5); + return x + mulDivRound(x, x2 / 2, a - x2 ~/ 64) ~/ 32; +} + +/// number of terms n should be chosen as for atanh_f258() +/// fixed261 atanh(fixed261 x); +@pure +@inline +fun atanh_f261_inlined(x: int, n: int): int { + var x2: int = mulrshiftr256(x, x); // x^2 as fixed266 + var One: int = (1 << 254); + var a: int = One ~/ n + (1 << 255); // a := 2 + 1/n as fixed254 + repeat (n - 1) { + // a := 1 + (1 - x^2 / a)(1 + 1/n) as fixed254 + var t: int = One - mulDivRound(x2, 1 << 242, a); // t := 1 - x^2 / a + var n1: int = n - 1; + a = mulDivRound(t, n, n1) + One; + n = n1; + } + // x / (1 - x^2 / a) = x / (1 - d) = x + x * d / (1 - d) for d = x^2 / a + // int d = mulDivRound(x2, 1 << 255, a - (x2 ~>> 12)); // d/(1-d) = x^2/(a-x^2) as fixed267 + // return x + (mulrshiftr256(x, d) ~>> 11); + return x + mulDivRound(x, x2, a - x2 ~/ 4096) ~/ 4096; +} + +/// fixed261 atanh(fixed261 x); +@pure +@inline_ref +fun atanh_f261(x: int, n: int): int { + return atanh_f261_inlined(x, n); +} + +/// returns (y, s) such that log(x) = y/2^257 + s*log(2) for positive integer x +/// (fixed257, int) log_aux(int x) +@pure +@inline_ref +fun log_aux_f257(x: int): (int, int) { + var s: int = log2_floor_p1(x); + x <<= 256 - s; + var t: int = stackMoveToTop(-1 << 256); + if ((x >> 249) <= 90) { + // t~stackMoveToTop(); + t >>= 1; + s -= 1; + } + x += t; + var `2x`: int = 2 * x; + var y: int = lshift256divr(`2x`, (x >> 1) - t); + // y = `2x` - (mulrshiftr256(2x, y) ~>> 2); // this line could improve precision on very rare occasions + return (atanh_f258(y, 36), s); +} + +/// computes 33^m for small m +@pure +@inline +fun pow33(m: int): int { + var t: int = 1; + repeat (m) { + t *= 33; + } + return t; +} + +/// computes 33^m for small 0<=m<=22 +/// slightly faster than pow33() +@pure +@inline +fun pow33b(m: int): int { + var (mh: int, ml: int) = divMod(m, 5); + var t: int = 1; + repeat (ml) { + t *= 33; + } + repeat (mh) { + t *= 33 * 33 * 33 * 33 * 33; + } + return t; +} + +/// returns (s, q, y) such that log(x) = s*log(2) + q*log(33/32) + y/2^260 for positive integer x +/// (int, int, fixed260) log_auxx_f260(int x); +@pure +@inline_ref +fun log_auxx_f260(x: int): (int, int, int) { + var s: int = log2_floor_p1(x) - 1; + x <<= 255 - s; // rescale to 1 <= x < 2 as fixed255 + var t: int = stackMoveToTop(2873) << 244; // ~ (33/32)^11 ~ sqrt(2) as fixed255 + var x1: int = (x - t) >> 1; + var q: int = mulDivRound(x1, 65, x1 + t) + 11; // crude approximation to round(log(x)/log(33/32)) + // t = 1; repeat (q) { t *= 33; } // t:=33^q, 0<=q<=22 + t = pow33b(q); + t <<= (51 - q) * 5; // t:=(33/32)^q as fixed255, nearest power of 33/32 to x + x -= t; + var y: int = lshift256divr(x << 4, (x >> 1) + t); // y = (x-t)/(x+t) as fixed261 + y = atanh_f261(y, 18); // atanh((x-t)/(x+t)) as fixed261, or log(x/t) as fixed260 + return (s, q, y); +} + +/// returns (y, s) such that log(x) = y/2^256 + s*log(2) for positive integer x +/// this function is very precise (error less than 0.6 ulp) and consumes < 7k gas +/// may be used to implement specific fixed-point instances of log() and log2() +/// (fixed256, int) log_aux_f256(int x); +@pure +@inline_ref +fun log_aux_f256(x: int): (int, int) { + var (s, q, y) = log_auxx_f260(x); + var (yh, yl) = rshiftr4mod(y); // y ~/% 16 , but Tolk does not optimize this to RSHIFTR#MOD + // int Log33_32 = 3563114646320977386603103333812068872452913448227778071188132859183498739150; // log(33/32) as fixed256 + // int Log33_32_l = -3769; // log(33/32) = Log33_32 / 2^256 + Log33_32_l / 2^269 + yh += (yl * 512 + q * -3769) ~>> 13; // compensation, may be removed if slightly worse accuracy is acceptable + var Log33_32: int = 3563114646320977386603103333812068872452913448227778071188132859183498739150; // log(33/32) as fixed256 + return (yh + q * Log33_32, s); +} + +/// returns (y, s) such that log2(x) = y/2^256 + s for positive integer x +/// this function is very precise (error less than 0.6 ulp) and consumes < 7k gas +/// may be used to implement specific fixed-point instances of log() and log2() +/// (fixed256, int) log2_aux_f256(int x); +@pure +@inline_ref +fun log2_aux_f256(x: int): (int, int) { + var (s, q, y) = log_auxx_f260(x); + y = lshift256divr(y, log2_const_f256()) ~>> 4; // y/log(2) as fixed256 + var Log33_32: int = 5140487830366106860412008603913034462883915832139695448455767612111363481357; // log_2(33/32) as fixed256 + // Log33_32/2^256 happens to be a very precise approximation to log_2(33/32), no compensation required + return (y + q * Log33_32, s); +} + + +/// fixed248 log(fixed248 x) +@pure +@inline_ref +fun fixed248_log(x: int): int { + var (y, s) = log_aux_f256(x); + return mulDivRound(s - 248, log2_const_f256(), 1 << 8) + (y ~>> 8); + // return mulDivRound(s - 248, 80260960185991308862233904206310070533990667611589946606122867505419956976172, 1 << 8) + (y ~>> 8); +} + +/// fixed248 log2(fixed248 x) +@pure +@inline +fun fixed248_log2(x: int): int { + var (y, s) = log2_aux_f256(x); + return ((s - 248) << 248) + (y ~>> 8); +} + +/// computes x^y as exp(y*log(x)), x >= 0 +/// fixed248 pow(fixed248 x, fixed248 y); +@pure +@inline_ref +fun fixed248_pow(x: int, y: int): int { + if (!y) { + return 1 << 248; // x^0 = 1 + } + if (x <= 0) { + var bad: int = (x | y) < 0; + return 0 >> bad; // 0^y = 0 if x=0 and y>=0; "out of range" exception otherwise + } + var (l, s) = log2_aux_f256(x); + s -= 248; // log_2(x) = s+l, l is fixed256, 0<=l<1 + // compute (s+l)*y = q+ll + var (q1, r1) = mulrshiftr248mod(s, y); // muldivmodr(s, y, 1 << 248) + var (q2, r2) = mulrshift256mod(l, y); + r2 >>= 247; + var (q3, r3) = rshiftr248mod(q2); // divmodr(q2, 1 << 248); + var (q, ll) = rshiftr248mod(r1 + r3); + ll = 512 * ll + r2; + q += q1 + q3; + // now log_2(x^y) = y*log_2(x) = q + ll, ss integer, ll fixed257, -1/2<=ll<1/2 + var sq: int = q + 248; + if (sq <= 0) { + return -(sq == 0); // underflow + } + y = expm1_f257(mulrshiftr256(ll, log2_const_f256())); + return (y ~>> (9 - q)) - (-1 << sq); +} + +/*-------------------- INVERSE TRIGONOMETRIC FUNCTIONS ------------------*/ + +/// number of terms n should be chosen as for atanh_f258() +/// fixed259 atan(fixed259 x); +@pure +@inline_ref +fun atan_f259(x: int, n: int): int { + var x2: int = mulrshiftr256(x, x); // x^2 as fixed262 + var One: int = (1 << 254); + var a: int = One ~/ n + (1 << 255); // a := 2 + 1/n as fixed254 + repeat (n - 1) { + // a := 1 + (1 + x^2 / a)(1 + 1/n) as fixed254 + var t: int = One + mulDivRound(x2, 1 << 246, a); // t := 1 + x^2 / a + var n1: int = n - 1; + a = mulDivRound(t, n, n1) + One; + n = n1; + } + // x / (1 + x^2 / a) = x / (1 + d) = x - x * d / (1 + d) = x - x * x^2/(a+x^2) for d = x^2 / a + return x - mulDivRound(x, x2, a + x2 ~/ 256) ~/ 256; +} + +/// number of terms n should be chosen as for atanh_f261() +/// fixed261 atan(fixed261 x); +@pure +@inline +fun atan_f261_inlined(x: int, n: int): int { + var x2: int = mulrshiftr256(x, x); // x^2 as fixed266 + var One: int = (1 << 254); + var a: int = One ~/ n + (1 << 255); // a := 2 + 1/n as fixed254 + repeat (n - 1) { + // a := 1 + (1 + x^2 / a)(1 + 1/n) as fixed254 + var t: int = One + mulDivRound(x2, 1 << 242, a); // t := 1 + x^2 / a + var n1: int = n - 1; + a = mulDivRound(t, n, n1) + One; + n = n1; + } + // x / (1 + x^2 / a) = x / (1 + d) = x - x * d / (1 + d) = x - x * x^2/(a+x^2) for d = x^2 / a + return x - mulDivRound(x, x2, a + x2 ~/ 4096) ~/ 4096; +} + +/// fixed261 atan(fixed261 x); +@pure +@inline_ref +fun atan_f261(x: int, n: int): int { + return atan_f261_inlined(x, n); +} + +/// computes (q,a,b) such that q is approximately atan(x)/atan(1/32) and a+b*I=(1+I/32)^q as fixed255 +/// then b/a=atan(q*atan(1/32)) exactly, and (a,b) is almost a unit vector pointing in the direction of (1,x) +/// must have |x|<1.1, x is fixed24 +/// (int, fixed255, fixed255) atan_aux_prereduce(fixed24 x); +@pure +@inline_ref +fun atan_aux_prereduce(x: int): (int, int, int) { + var xu: int = abs(x); + var tc: int = 7214596; // tan(13*theta) as fixed24 where theta=atan(1/32) + var t1: int = mulDivRound(xu - tc, 1 << 88, xu * tc + (1 << 48)); // tan(x') as fixed64 where x'=atan(x)-13*theta + // t1/(3+t1^2) * 3073/32 = x'/3 * 3072/32 = x' / (96/3072) = x' / theta + var q: int = mulDivRound(t1 * 3073, 1 << 59, t1 * t1 + (stackMoveToTop(3) << 128)) + 13; // approximately round(atan(x)/theta), 0<=q<=25 + var (pa, pb) = (33226912, 5232641); // (32+I)^5 + var (qh, ql) = divMod(q, 5); + var (a, b) = (1 << (5 * (51 - q)), 0); // (1/32^q, 0) as fixed255 + repeat (ql) { + // a+b*I *= 32+I + (a, b) = (sub_rev(stackMoveToTop(b), 32 * a), a + 32 * b); // same as (32 * a - b, 32 * b + a), but more efficient + } + repeat (qh) { + // a+b*I *= (32+I)^5 = pa + pb*I + (a, b) = (a * pa - b * pb, a * pb + b * pa); + } + var xs: int = sign(x); + return (xs * q, a, xs * b); +} + +/// compute (q, z) such that atan(x)=q*atan(1/32)+z for -1 <= x < 1 +/// this function is reasonably accurate (error < 7 ulp with ulp = 2^-261), but it consumes >7k gas +/// this is sufficient for most purposes +/// (int, fixed261) atan_aux(fixed256 x) +@pure +@inline_ref +fun atan_aux_f256(x: int): (int, int) { + var (q, a, b) = atan_aux_prereduce(x ~>> 232); // convert x to fixed24 + // now b/a = tan(q*atan(1/32)) exactly, where q is near atan(x)/atan(1/32); so b/a is near x + // compute y = u/v = (a*x-b)/(a+b*x) as fixed261 ; then |y|<0.0167 = 1.07/64 and atan(x)=atan(y)+q*atan(1/32) + var (u, ul) = mulrshiftr256mod(a, x); + u = (ul ~>> 250) + ((u - b) << 6); // |u| < 1/32, convert fixed255 -> fixed261 + var v: int = a + mulrshiftr256(b, x); // v is scalar product of (a,b) and (1,x), it is approximately in [1..sqrt(2)] as fixed255 + var y: int = mulDivRound(u, 1 << 255, v); // y = u/v as fixed261 + var z: int = atan_f261_inlined(y, 18); // z = atan(x)-q*atan(1/32) + return (q, z); +} + +/// compute (q, z) such that atan(x)=q*atan(1/32)+z for -1 <= x < 1 +/// this function is very accurate (error < 2 ulp), but it consumes >7k gas +/// in most cases, faster function atan_aux_f256() should be used +/// (int, fixed261) atan_auxx(fixed256 x) +@pure +@inline_ref +fun atan_auxx_f256(x: int): (int, int) { + var (q, a, b) = atan_aux_prereduce(x ~>> 232); // convert x to fixed24 + // now b/a = tan(q*atan(1/32)) exactly, where q is near atan(x)/atan(1/32); so b/a is near x + // compute y = (a*x-b)/(a+b*x) as fixed261 ; then |y|<0.0167 = 1.07/64 and atan(x)=atan(y)+q*atan(1/32) + // use sort of double precision arithmetic for this + var (u, ul) = mulrshiftr256mod(a, x); + ul /= 2; + u -= b; // |u| < 1/32 as fixed255 + var (v, vl) = mulrshiftr256mod(b, x); + vl /= 2; + v += a; // v is scalar product of (a,b) and (1,x), it is approximately in [1..sqrt(2)] as fixed255 + // y = (u + ul*eps) / (v + vl*eps) = u/v + (ul - vl * u/v)/v * eps where eps=1/2^255 + var (y, r) = lshift255divmodr(u, v); // y = u/v as fixed255 + var yl: int = mulDivRound(ul + r, 1 << 255, v) - mulDivRound(vl, y, v); // y/2^255 + yl/2^510 represent u/v + y = (yl ~>> 249) + (y << 6); // convert y to fixed261 + var z: int = atan_f261_inlined(y, 18); // z = atan(x)-q*atan(1/32) + return (q, z); +} + +/// consumes ~ 8k gas +/// fixed255 atan(fixed255 x); +@pure +@inline_ref +fun atan_f255(x: int): int { + var s: int = (x ~>> 256); + stackMoveToTop(x); + if (s) { + x = lshift256divr(-1 << 255, x); // x:=-1/x as fixed256 + } else { + x *= 2; // convert to fixed256 + } + var (q, z) = atan_aux_f256(x); + // now atan(x) = z + q*atan(1/32) + s*(Pi/2), z is fixed261 + var (Pi_h, Pi_l) = Pi_xconst_f254(); // Pi/2 as fixed255 + fixed383 + var (qh, ql) = mulrshiftr6mod(q, Atan1_32_f261()); + return qh + s * Pi_h + (z + ql + mulDivRound(s, Pi_l, 1 << 122)) ~/ 64; +} + +/// computes atan(x) for -1 <= x < 1 only +/// fixed256 atan_small(fixed256 x); +@pure +@inline_ref +fun atan_f256_small(x: int): int { + var (q, z) = atan_aux_f256(x); + // now atan(x) = z + q*atan(1/32), z is fixed261 + var (qh, ql) = mulrshiftr5mod(q, Atan1_32_f261()); + return qh + (z + ql) ~/ 32; +} + +/// fixed255 asin(fixed255 x); +@pure +@inline_ref +fun asin_f255(x: int): int { + var a: int = fixed255_One - fixed255_sqr(x); // a:=1-x^2 + if (!a) { + return sign(x) * Pi_const_f254(); // Pi/2 or -Pi/2 + } + var y: int = fixed255_sqrt(a); // sqrt(1-x^2) + var t: int = -lshift256divr(x, (-1 << 255) - y); // t = x/(1+sqrt(1-x^2)) avoiding overflow + return atan_f256_small(t); // asin(x)=2*atan(t) +} + +/// fixed254 acos(fixed255 x); +@pure +@inline_ref +fun acos_f255(x: int): int { + var Pi: int = Pi_const_f254(); + if (x == (-1 << 255)) { + return Pi; // acos(-1) = Pi + } + Pi /= 2; + var y: int = fixed255_sqrt(fixed255_One - fixed255_sqr(x)); // sqrt(1-x^2) + var t: int = lshift256divr(x, (-1 << 255) - y); // t = -x/(1+sqrt(1-x^2)) avoiding overflow + return Pi + atan_f256_small(t) ~/ 2; // acos(x)=Pi/2 + 2*atan(t) +} + +/// consumes ~ 10k gas +/// fixed248 asin(fixed248 x) +@pure +@inline +fun fixed248_asin(x: int): int { + return asin_f255(x << 7) ~>> 7; +} + +/// consumes ~ 10k gas +/// fixed248 acos(fixed248 x) +@pure +@inline +fun fixed248_acos(x: int): int { + return acos_f255(x << 7) ~>> 6; +} + +/// consumes ~ 7500 gas +/// fixed248 atan(fixed248 x); +@pure +@inline_ref +fun fixed248_atan(x: int): int { + var s: int = (x ~>> 249); + stackMoveToTop(x); + if (s) { + s = sign(s); + x = lshift256divr(-1 << 248, x); // x:=-1/x as fixed256 + } else { + x <<= 8; // convert to fixed256 + } + var (q, z) = atan_aux_f256(x); + // now atan(x) = z + q*atan(1/32) + s*(Pi/2), z is fixed261 + return (z ~/ 64 + s * Pi_const_f254() + mulDivRound(q, Atan1_32_f261(), 64)) ~/ 128; // compute in fixed255, then convert +} + +/// fixed248 acot(fixed248 x); +@pure +@inline_ref +fun fixed248_acot(x: int): int { + var s: int = (x ~>> 249); + stackMoveToTop(x); + if (s) { + x = lshift256divr(-1 << 248, x); // x:=-1/x as fixed256 + s = 0; + } else { + x <<= 8; // convert to fixed256 + s = sign(x); + } + var (q, z) = atan_aux_f256(x); + // now acot(x) = - z - q*atan(1/32) + s*(Pi/2), z is fixed261 + return (s * Pi_const_f254() - z ~/ 64 - mulDivRound(q, Atan1_32_f261(), 64)) ~/ 128; // compute in fixed255, then convert +} + +/*-------------------- PSEUDO-RANDOM NUMBERS ------------------*/ + +/// random number with standard normal distribution N(0,1) +/// generated by Kinderman--Monahan ratio method modified by J.Leva +/// spends ~ 2k..3k gas on average +/// fixed252 nrand(); +@inline_ref +fun nrand_f252(): int { + var (x, s, t, A, B, r0) = (nan(), stackMoveToTop(29483) << 236, stackMoveToTop(-3167) << 239, 12845, 16693, 9043); + // 4/sqrt(e*Pi) = 1.369 loop iterations on average + do { + var (u, v) = (random() / 16 + 1, mulDivRound(random() - (1 << 255), 7027, 1 << 16)); // fixed252; 7027=ceil(sqrt(8/e)*2^12) + var va: int = abs(v); + var (u1, v1) = (u - s, va - t); // (u - 29483/2^16, abs(v) + 3167/2^13) as fixed252 + // Q := u1^2 + v1 * (A*v1 - B*u1) as fixed252 where A=12845/2^16, B=16693/2^16 + var Q: int = mulDivRound(u1, u1, 1 << 252) + mulDivRound(v1, mulDivRound(v1, A, 1 << 16) - mulDivRound(u1, B, 1 << 16), 1 << 252); + // must have 9043 / 2^15 < Q < 9125 / 2^15, otherwise accept if smaller, reject if larger + var Qd: int = (Q >> 237) - r0; + if ((Qd < 9125 - 9043) & (va / u < 16)) { + x = mulDivRound(v, 1 << 252, u); // x:=v/u as fixed252; reject immediately if |v/u| >= 16 + if (Qd >= 0) { + // immediately accept if Qd < 0 + // rarely taken branch - 0.012 times per call on average + // check condition v^2 < -4*u^2*log(u), or equivalent condition u < exp(-x^2/4) for x=v/u + var xx: int = mulrshiftr256(x, x) ~/ 4; // x^2/4 as fixed248 + var ex: int = fixed248_exp(-xx) * 16; // exp(-x^2/4) as fixed252 + if (u > ex) { + x = nan(); // condition false, reject + } + } + } + } while (!(~ is_nan(x))); + return x; +} + +/// generates a random number approximately distributed according to the standard normal distribution +/// much faster than nrand_f252(), should be suitable for most purposes when only several random numbers are needed +/// fixed252 nrand_fast(); +@inline_ref +fun nrand_fast_f252(): int { + var t: int = stackMoveToTop(-3) << 253; // -6. as fixed252 + repeat (12) { + t += random() / 16; // add together 12 uniformly random numbers + } + return t; +} + +/// random number uniformly distributed in [0..1) +/// fixed248 random(); +@inline +fun fixed248_random(): int { + return random() >> 8; +} + +/// random number with standard normal distribution +/// fixed248 nrand(); +@inline +fun fixed248_nrand(): int { + return nrand_f252() ~>> 4; +} + +/// generates a random number approximately distributed according to the standard normal distribution +/// fixed248 nrand_fast(); +@inline +fun fixed248_nrand_fast(): int { + return nrand_fast_f252() ~>> 4; +} @pure fun ~tset(t: tuple, idx: int, value: X): (tuple, ()) @@ -11,8 +989,8 @@ fun acos_prepare_slow_f255(x: int): int { x -= (x == 0); var t: int = 1; repeat (255) { - t = t * sgn(x) * 2 + 1; // decode Gray code (sgn(x_0), sgn(x_1), ...) - x = (-1 << 255) - muldivr(x, - x, 1 << 254); // iterate x := 2*x^2 - 1 = cos(2*acos(x)) + t = t * sign(x) * 2 + 1; // decode Gray code (sign(x_0), sign(x_1), ...) + x = (-1 << 255) - mulDivRound(x, - x, 1 << 254); // iterate x := 2*x^2 - 1 = cos(2*acos(x)) } return abs(t); } @@ -29,19 +1007,19 @@ fun acos_slow_f255(x: int): int { @inline_ref fun asin_slow_f255(x: int): int { var t: int = acos_prepare_slow_f255(abs(x)) % (1 << 255); - return muldivr(t, Pi_const_f254(), 1 << 255) * sgn(x); + return mulDivRound(t, Pi_const_f254(), 1 << 255) * sign(x); } @inline_ref fun test_nrand(n: int): tuple { - var t: tuple = empty_tuple(); + var t: tuple = createEmptyTuple(); repeat (255) { - t~tpush(0); + t~tuplePush(0); } repeat (n) { var x: int = fixed248_nrand(); var bucket: int = (abs(x) >> 243); // 255 buckets starting from x=0, each 1/32 wide - t~tset(bucket, t.at(bucket) + 1); + t~tset(bucket, t.tupleAt(bucket) + 1); } return t; } @@ -186,15 +1164,15 @@ fun main() { // return sincosn_f256(Pi_const_f254(), 0); // (sin,-cos)(Pi/4) // return sincosn_f256((1 << 255) + 1, 0); // (sin,-cos)(1/2+1/2^256) // return sincosn_f256(1 << 254, 0); - // return sincosn_f256(touch(15) << 252, 0); // (sin,-cos)(15/16) - // return sincosm1_f256(touch(15) << 252); // (sin,1-cos)(15/16) + // return sincosn_f256(stackMoveToTop(15) << 252, 0); // (sin,-cos)(15/16) + // return sincosm1_f256(stackMoveToTop(15) << 252); // (sin,1-cos)(15/16) // return sincosn_f256(60628596148627720713372490462954977108898896221398738326462025186323149077698, 0); // (sin,-cos)(Pi/6) // return sincosm1_f256(60628596148627720713372490462954977108898896221398738326462025186323149077698); // (sin,1-cos)(Pi/6) // return tan_aux_f256(1899 << 245); // (p,q) such that p/q=tan(1899/2048) // return fixed248_tan(11 << 248); // tan(11) // return atanh_alt_f258(1 << 252); // atanh(1/64) * 2^258 // return atanh_f258(1 << 252, 18); // atanh(1/64) * 2^258 - // return atanh_f261(muldivr(64, 1 << 255, 55), 18); // atanh(1/55) * 2^261 + // return atanh_f261(mulDivRound(64, 1 << 255, 55), 18); // atanh(1/55) * 2^261 // return log2_aux_f256(1 << 255); // return log2_aux_f256(-1 - (-1 << 256)); // log2(2-1/2^255))*2^256 ~ 2^256 - 1.43 // return log_aux_f256(-1 - (-1 << 256)); @@ -213,10 +1191,10 @@ fun main() { // return fixed248_exp2((1 << 248) ~/ 5); // 2^(1/5)*2^248 // return fixed248_pow(3 << 248, -3 << 247); // 3^(-1.5) // return fixed248_pow(10 << 248, -70 << 248); // 10^(-70) - // return fixed248_pow(fixed248_Pi_const(), touch(3) << 248); // Pi^3 ~ 31.006, computed more precisely + // return fixed248_pow(fixed248_Pi_const(), stackMoveToTop(3) << 248); // Pi^3 ~ 31.006, computed more precisely // return fixed248_pow(fixed248_Pi_const(), fixed248_Pi_const()); // Pi^Pi, more precisely // return fixed248_exp(fixed248_log(fixed248_Pi_const()) * 3); // Pi^3 ~ 31.006 - // return fixed248_exp(muldivr(fixed248_log(fixed248_Pi_const()), fixed248_Pi_const(), 1 << 248)); // Pi^Pi + // return fixed248_exp(mulDivRound(fixed248_log(fixed248_Pi_const()), fixed248_Pi_const(), 1 << 248)); // Pi^Pi // return fixed248_sin(fixed248_log(fixed248_exp(fixed248_Pi_const()))); // sin(log(e^Pi)) // return expm1_f257(1 << 255); // (exp(1/4)-1)*2^256 // return expm1_f257(-1 << 256); // (exp(-1/2)-1)*2^256 (argument out of range, will overflow) @@ -225,21 +1203,21 @@ fun main() { // return tanh_f258(log2_const_f256(), 17); // tanh(log(2)/4)*2^258 // return atan_f255(0xa0 << 247); // return atan_f259(1 << 255, 26); // atan(1/16) - // return atan_f259(touch(2273) << 244, 26); // atan(2273/2^15) + // return atan_f259(stackMoveToTop(2273) << 244, 26); // atan(2273/2^15) // return atan_aux_f256(0xa0 << 248); // return atan_aux_f256(-1 - (-1 << 256)); // return atan_aux_f256(-1 << 256); // return atan_aux_f256(1); // atan(1/2^256)*2^261 = 32 //return fixed248_nrand(); // return test_nrand(100000); - var One2: int = touch(1 << 255); + var One2: int = stackMoveToTop(1 << 255); // return asin_f255(One); // return asin_f255(-2 * One ~/ -3); - var arg: int = muldivr(12, One2, 17); // 12/17 + var arg: int = mulDivRound(12, One2, 17); // 12/17 // return [ asin_slow_f255(arg), asin_f255(arg) ]; // return [ acos_slow_f255(arg), acos_f255(arg) ]; // return 4 * atan_f255(One ~/ 5) - atan_f255(One ~/ 239); // 4 * atan(1/5) - atan(1/239) = Pi/4 as fixed255 - var One3: int = touch(1 << 248); + var One3: int = stackMoveToTop(1 << 248); // return fixed248_atan(One) ~/ 5); // atan(1/5) // return fixed248_acot(One ~/ 239); // atan(1/5) } diff --git a/tolk-tester/tests/use-before-declare.tolk b/tolk-tester/tests/use-before-declare.tolk index 84569c0cf..2486df1c3 100644 --- a/tolk-tester/tests/use-before-declare.tolk +++ b/tolk-tester/tests/use-before-declare.tolk @@ -1,7 +1,7 @@ fun main(): int { - var c: cell = my_begin_cell().store_int(demo_10, 32).my_end_cell(); + var c: cell = my_begin_cell().storeInt(demo_10, 32).my_end_cell(); var cs: slice = my_begin_parse(c); - var ten: int = cs~load_int(32); + var ten: int = cs~loadInt(32); return 1 + demo1(ten) + demo_var; } diff --git a/tolk-tester/tests/w2.tolk b/tolk-tester/tests/w2.tolk index b013ab06d..24820f143 100644 --- a/tolk-tester/tests/w2.tolk +++ b/tolk-tester/tests/w2.tolk @@ -1,6 +1,6 @@ @method_id(101) fun test1(cs: slice) { - return cs~load_uint(8)+cs~load_uint(8)+cs~load_uint(8)+cs~load_uint(8); + return cs~loadUint(8)+cs~loadUint(8)+cs~loadUint(8)+cs~loadUint(8); } @method_id(102) @@ -12,15 +12,15 @@ fun test2(cs: slice) { } fun main(cs: slice) { - return (cs~load_uint(8), cs~load_uint(8), cs~load_uint(8), cs~load_uint(8)); + return (cs~loadUint(8), cs~loadUint(8), cs~loadUint(8), cs~loadUint(8)); } fun f(cs: slice) { - return (cs~load_uint(8), cs~load_uint(8), cs~load_uint(8), cs~load_uint(8), - cs~load_uint(8), cs~load_uint(8), cs~load_uint(8), cs~load_uint(8), - cs~load_uint(8), cs~load_uint(8), cs~load_uint(8), cs~load_uint(8), - cs~load_uint(8), cs~load_uint(8), cs~load_uint(8), cs~load_uint(8), - cs~load_uint(8), cs~load_uint(8), cs~load_uint(8), cs~load_uint(8)); + return (cs~loadUint(8), cs~loadUint(8), cs~loadUint(8), cs~loadUint(8), + cs~loadUint(8), cs~loadUint(8), cs~loadUint(8), cs~loadUint(8), + cs~loadUint(8), cs~loadUint(8), cs~loadUint(8), cs~loadUint(8), + cs~loadUint(8), cs~loadUint(8), cs~loadUint(8), cs~loadUint(8), + cs~loadUint(8), cs~loadUint(8), cs~loadUint(8), cs~loadUint(8)); } diff --git a/tolk-tester/tolk-tester.js b/tolk-tester/tolk-tester.js index 3fb92ff08..2a3eb776a 100644 --- a/tolk-tester/tolk-tester.js +++ b/tolk-tester/tolk-tester.js @@ -27,6 +27,7 @@ const TOLKFIFTLIB_MODULE = getenv('TOLKFIFTLIB_MODULE') const TOLKFIFTLIB_WASM = getenv('TOLKFIFTLIB_WASM') const FIFT_EXECUTABLE = getenv('FIFT_EXECUTABLE') const FIFT_LIBS_FOLDER = getenv('FIFTPATH') // this env is needed for fift to work properly +const STDLIB_FOLDER = __dirname + '/../crypto/smartcont/tolk-stdlib' const TMP_DIR = os.tmpdir() class CmdLineOptions { @@ -475,25 +476,33 @@ function copyToCStringPtr(mod, str, ptr) { return allocated; } +/** @return {string} */ function copyFromCString(mod, ptr) { return mod.UTF8ToString(ptr); } /** @return {{status: string, message: string, fiftCode: string, codeBoc: string, codeHashHex: string}} */ function compileFile(mod, filename, experimentalOptions) { - // see tolk-wasm.cpp: typedef void (*CStyleReadFileCallback)(int, char const*, char**, char**) + // see tolk-wasm.cpp: typedef void (*WasmFsReadCallback)(int, char const*, char**, char**) const callbackPtr = mod.addFunction((kind, dataPtr, destContents, destError) => { if (kind === 0) { // realpath try { - const relativeFilename = copyFromCString(mod, dataPtr) - copyToCStringPtr(mod, fs.realpathSync(relativeFilename), destContents); + let relative = copyFromCString(mod, dataPtr) + if (relative.startsWith('@stdlib/')) { + // import "@stdlib/filename" or import "@stdlib/filename.tolk" + relative = STDLIB_FOLDER + '/' + relative.substring(7) + if (!relative.endsWith('.tolk')) { + relative += '.tolk' + } + } + copyToCStringPtr(mod, fs.realpathSync(relative), destContents); } catch (err) { copyToCStringPtr(mod, 'cannot find file', destError); } } else if (kind === 1) { // read file try { - const filename = copyFromCString(mod, dataPtr) // already normalized (as returned above) - copyToCStringPtr(mod, fs.readFileSync(filename).toString('utf-8'), destContents); + const absolute = copyFromCString(mod, dataPtr) // already normalized (as returned above) + copyToCStringPtr(mod, fs.readFileSync(absolute).toString('utf-8'), destContents); } catch (err) { copyToCStringPtr(mod, err.message || err.toString(), destError); } @@ -506,7 +515,6 @@ function compileFile(mod, filename, experimentalOptions) { optimizationLevel: 2, withStackComments: true, experimentalOptions: experimentalOptions || undefined, - stdlibLocation: __dirname + '/../crypto/smartcont/stdlib.tolk', entrypointFileName: filename }; diff --git a/tolk/CMakeLists.txt b/tolk/CMakeLists.txt index 245021c3b..09e02c0ac 100644 --- a/tolk/CMakeLists.txt +++ b/tolk/CMakeLists.txt @@ -35,12 +35,6 @@ if (${TOLK_DEBUG}) # -DTOLK_DEBUG=1 in CMake options => #define TOLK_DEBUG (for target_compile_definitions(tolk PRIVATE TOLK_DEBUG=1) endif() -if (NOT USE_EMSCRIPTEN) - get_filename_component(STDLIB_TOLK_IF_BUILD_FROM_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/../crypto/smartcont/stdlib.tolk" REALPATH) - target_compile_definitions(tolk PRIVATE STDLIB_TOLK_IF_BUILD_FROM_SOURCES="${STDLIB_TOLK_IF_BUILD_FROM_SOURCES}") -endif() - - if (USE_EMSCRIPTEN) add_executable(tolkfiftlib tolk-wasm.cpp ${TOLK_SOURCE}) target_include_directories(tolkfiftlib PUBLIC $) diff --git a/tolk/builtins.cpp b/tolk/builtins.cpp index 52144d419..a123b0a85 100644 --- a/tolk/builtins.cpp +++ b/tolk/builtins.cpp @@ -1121,10 +1121,6 @@ void define_builtins() { define_builtin_func("_~/_", arith_bin_op, std::bind(compile_div, _1, _2, _3, 0)); define_builtin_func("_^/_", arith_bin_op, std::bind(compile_div, _1, _2, _3, 1)); define_builtin_func("_%_", arith_bin_op, std::bind(compile_mod, _1, _2, _3, -1)); - define_builtin_func("divmod", TypeExpr::new_map(Int2, Int2), AsmOp::Custom("DIVMOD", 2, 2)); - define_builtin_func("~divmod", TypeExpr::new_map(Int2, Int2), AsmOp::Custom("DIVMOD", 2, 2)); - define_builtin_func("moddiv", TypeExpr::new_map(Int2, Int2), AsmOp::Custom("DIVMOD", 2, 2), {}, {1, 0}); - define_builtin_func("~moddiv", TypeExpr::new_map(Int2, Int2), AsmOp::Custom("DIVMOD", 2, 2), {}, {1, 0}); define_builtin_func("_<<_", arith_bin_op, compile_lshift); define_builtin_func("_>>_", arith_bin_op, std::bind(compile_rshift, _1, _2, _3, -1)); define_builtin_func("_~>>_", arith_bin_op, std::bind(compile_rshift, _1, _2, _3, 0)); @@ -1144,10 +1140,10 @@ void define_builtins() { define_builtin_func("^_&=_", arith_bin_op, compile_bitwise_and); define_builtin_func("^_|=_", arith_bin_op, compile_bitwise_or); define_builtin_func("^_^=_", arith_bin_op, compile_bitwise_xor); - define_builtin_func("muldiv", TypeExpr::new_map(Int3, Int), std::bind(compile_muldiv, _1, _2, _3, -1)); - define_builtin_func("muldivr", TypeExpr::new_map(Int3, Int), std::bind(compile_muldiv, _1, _2, _3, 0)); - define_builtin_func("muldivc", TypeExpr::new_map(Int3, Int), std::bind(compile_muldiv, _1, _2, _3, 1)); - define_builtin_func("muldivmod", TypeExpr::new_map(Int3, Int2), AsmOp::Custom("MULDIVMOD", 3, 2)); + define_builtin_func("mulDivFloor", TypeExpr::new_map(Int3, Int), std::bind(compile_muldiv, _1, _2, _3, -1)); + define_builtin_func("mulDivRound", TypeExpr::new_map(Int3, Int), std::bind(compile_muldiv, _1, _2, _3, 0)); + define_builtin_func("mulDivCeil", TypeExpr::new_map(Int3, Int), std::bind(compile_muldiv, _1, _2, _3, 1)); + define_builtin_func("mulDivMod", TypeExpr::new_map(Int3, Int2), AsmOp::Custom("MULDIVMOD", 3, 2)); define_builtin_func("_==_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 2)); define_builtin_func("_!=_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 5)); define_builtin_func("_<_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 4)); @@ -1162,24 +1158,23 @@ void define_builtins() { define_builtin_func("__throw", impure_un_op, compile_throw, true); define_builtin_func("__throw_arg", throw_arg_op, compile_throw_arg, true); define_builtin_func("__throw_if_unless", TypeExpr::new_map(Int3, Unit), std::bind(compile_throw_if_unless, _1, _2), true); - define_builtin_func("load_int", fetch_int_op, std::bind(compile_fetch_int, _1, _2, true, true), {}, {1, 0}); - define_builtin_func("load_uint", fetch_int_op, std::bind(compile_fetch_int, _1, _2, true, false), {}, {1, 0}); - define_builtin_func("preload_int", prefetch_int_op, std::bind(compile_fetch_int, _1, _2, false, true)); - define_builtin_func("preload_uint", prefetch_int_op, std::bind(compile_fetch_int, _1, _2, false, false)); - define_builtin_func("store_int", store_int_op, std::bind(compile_store_int, _1, _2, true), {1, 0, 2}); - define_builtin_func("store_uint", store_int_op, std::bind(compile_store_int, _1, _2, false), {1, 0, 2}); - define_builtin_func("~store_int", store_int_method, std::bind(compile_store_int, _1, _2, true), {1, 0, 2}); - define_builtin_func("~store_uint", store_int_method, std::bind(compile_store_int, _1, _2, false), {1, 0, 2}); - define_builtin_func("load_bits", fetch_slice_op, std::bind(compile_fetch_slice, _1, _2, true), {}, {1, 0}); - define_builtin_func("preload_bits", prefetch_slice_op, std::bind(compile_fetch_slice, _1, _2, false)); - define_builtin_func("at", TypeExpr::new_forall({X}, TypeExpr::new_map(TupleInt, X)), compile_tuple_at); - define_builtin_func("touch", TypeExpr::new_forall({X}, TypeExpr::new_map(X, X)), AsmOp::Nop()); - define_builtin_func("~touch", TypeExpr::new_forall({X}, TypeExpr::new_map(X, TypeExpr::new_tensor({X, Unit}))), - AsmOp::Nop()); - define_builtin_func("~dump", TypeExpr::new_forall({X}, TypeExpr::new_map(X, TypeExpr::new_tensor({X, Unit}))), - AsmOp::Custom("s0 DUMP", 1, 1), true); - define_builtin_func("~strdump", TypeExpr::new_forall({X}, TypeExpr::new_map(X, TypeExpr::new_tensor({X, Unit}))), - AsmOp::Custom("STRDUMP", 1, 1), true); + define_builtin_func("loadInt", fetch_int_op, std::bind(compile_fetch_int, _1, _2, true, true), {}, {1, 0}); + define_builtin_func("loadUint", fetch_int_op, std::bind(compile_fetch_int, _1, _2, true, false), {}, {1, 0}); + define_builtin_func("loadBits", fetch_slice_op, std::bind(compile_fetch_slice, _1, _2, true), {}, {1, 0}); + define_builtin_func("preloadInt", prefetch_int_op, std::bind(compile_fetch_int, _1, _2, false, true)); + define_builtin_func("preloadUint", prefetch_int_op, std::bind(compile_fetch_int, _1, _2, false, false)); + define_builtin_func("preloadBits", prefetch_slice_op, std::bind(compile_fetch_slice, _1, _2, false)); + define_builtin_func("storeInt", store_int_op, std::bind(compile_store_int, _1, _2, true), {1, 0, 2}); + define_builtin_func("storeUint", store_int_op, std::bind(compile_store_int, _1, _2, false), {1, 0, 2}); + define_builtin_func("~storeInt", store_int_method, std::bind(compile_store_int, _1, _2, true), {1, 0, 2}); + define_builtin_func("~storeUint", store_int_method, std::bind(compile_store_int, _1, _2, false), {1, 0, 2}); + define_builtin_func("tupleAt", TypeExpr::new_forall({X}, TypeExpr::new_map(TupleInt, X)), compile_tuple_at); + define_builtin_func("debugPrint", TypeExpr::new_forall({X}, TypeExpr::new_map(X, Unit)), + AsmOp::Custom("s0 DUMP DROP", 1, 1), true); + define_builtin_func("debugPrintString", TypeExpr::new_forall({X}, TypeExpr::new_map(X, Unit)), + AsmOp::Custom("STRDUMP DROP", 1, 1), true); + define_builtin_func("debugDumpStack", TypeExpr::new_map(Unit, Unit), + AsmOp::Custom("DUMPSTK", 0, 0), true); } } // namespace tolk diff --git a/tolk/compiler-state.h b/tolk/compiler-state.h index 23df230b6..aec1945e2 100644 --- a/tolk/compiler-state.h +++ b/tolk/compiler-state.h @@ -52,10 +52,9 @@ struct CompilerSettings { int optimization_level = 2; bool stack_layout_comments = true; - std::string entrypoint_filename; std::string output_filename; std::string boc_output_filename; - std::string stdlib_filename; + std::string stdlib_folder; // a path to tolk-stdlib/; files imported via @stdlib/xxx are there FsReadCallback read_callback; @@ -82,8 +81,6 @@ struct CompilerState { std::vector all_code_functions, all_global_vars, all_get_methods, all_constants; AllRegisteredSrcFiles all_src_files; - std::string generated_from; - bool is_verbosity(int gt_eq) const { return settings.verbosity >= gt_eq; } }; diff --git a/tolk/pipe-ast-to-legacy.cpp b/tolk/pipe-ast-to-legacy.cpp index 539652c06..3e713e726 100644 --- a/tolk/pipe-ast-to-legacy.cpp +++ b/tolk/pipe-ast-to-legacy.cpp @@ -979,12 +979,6 @@ void pipeline_convert_ast_to_legacy_Expr_Op(const AllSrcFiles& all_src_files) { for (const SrcFile* file : all_src_files) { tolk_assert(file->ast); - if (!file->is_stdlib_file()) { - // file->ast->debug_print(); - G.generated_from += file->rel_filename; - G.generated_from += ", "; - } - for (AnyV v : file->ast->as()->get_toplevel_declarations()) { if (auto v_func = v->try_as()) { if (v_func->is_asm_function()) { diff --git a/tolk/pipe-discover-parse-sources.cpp b/tolk/pipe-discover-parse-sources.cpp index c57c9c1d4..a8445ae95 100644 --- a/tolk/pipe-discover-parse-sources.cpp +++ b/tolk/pipe-discover-parse-sources.cpp @@ -42,10 +42,11 @@ AllSrcFiles pipeline_discover_and_parse_sources(const std::string& stdlib_filena for (AnyV v_toplevel : file->ast->as()->get_toplevel_declarations()) { if (auto v_import = v_toplevel->try_as()) { - size_t pos = file->rel_filename.rfind('/'); - std::string rel_filename = pos == std::string::npos - ? v_import->get_file_name() - : file->rel_filename.substr(0, pos + 1) + v_import->get_file_name(); + std::string imported_str = v_import->get_file_name(); + size_t cur_slash_pos = file->rel_filename.rfind('/'); + std::string rel_filename = cur_slash_pos == std::string::npos || imported_str[0] == '@' + ? std::move(imported_str) + : file->rel_filename.substr(0, cur_slash_pos + 1) + imported_str; SrcFile* imported = G.all_src_files.locate_and_register_source_file(rel_filename, v_import->loc); file->imports.push_back(SrcFile::ImportStatement{imported}); diff --git a/tolk/pipe-generate-fif-output.cpp b/tolk/pipe-generate-fif-output.cpp index 627b510f7..65225d828 100644 --- a/tolk/pipe-generate-fif-output.cpp +++ b/tolk/pipe-generate-fif-output.cpp @@ -132,9 +132,20 @@ static void generate_output_func(SymDef* func_sym) { } } -void pipeline_generate_fif_output_to_std_cout() { +void pipeline_generate_fif_output_to_std_cout(const AllSrcFiles& all_src_files) { std::cout << "\"Asm.fif\" include\n"; - std::cout << "// automatically generated from " << G.generated_from << std::endl; + std::cout << "// automatically generated from "; + bool need_comma = false; + for (const SrcFile* file : all_src_files) { + if (!file->is_stdlib_file()) { + if (need_comma) { + std::cout << ", "; + } + std::cout << file->rel_filename; + need_comma = true; + } + } + std::cout << std::endl; std::cout << "PROGRAM{\n"; bool has_main_procedure = false; diff --git a/tolk/pipe-register-symbols.cpp b/tolk/pipe-register-symbols.cpp index c84474f80..2e6d26dd8 100644 --- a/tolk/pipe-register-symbols.cpp +++ b/tolk/pipe-register-symbols.cpp @@ -296,7 +296,7 @@ static void register_function(V v) { v->error("`builtin` used for non-builtin function"); } #ifdef TOLK_DEBUG - // in release, we don't need this check, since `builtin` is used only in stdlib.tolk, which is our responsibility + // in release, we don't need this check, since `builtin` is used only in stdlib, which is our responsibility if (!func_val->sym_type->equals_to(func_type) || func_val->is_marked_as_pure() != v->marked_as_pure) { v->error("declaration for `builtin` function doesn't match an actual one"); } diff --git a/tolk/pipeline.h b/tolk/pipeline.h index 1330c97a4..fdfd2b996 100644 --- a/tolk/pipeline.h +++ b/tolk/pipeline.h @@ -36,6 +36,6 @@ void pipeline_register_global_symbols(const AllSrcFiles&); void pipeline_convert_ast_to_legacy_Expr_Op(const AllSrcFiles&); void pipeline_find_unused_symbols(); -void pipeline_generate_fif_output_to_std_cout(); +void pipeline_generate_fif_output_to_std_cout(const AllSrcFiles&); } // namespace tolk diff --git a/tolk/src-file.cpp b/tolk/src-file.cpp index b6c7e2d4a..e5533f697 100644 --- a/tolk/src-file.cpp +++ b/tolk/src-file.cpp @@ -87,6 +87,11 @@ AllSrcFiles AllRegisteredSrcFiles::get_all_files() const { return src_files_immutable; } +bool SrcFile::is_stdlib_file() const { + std::string_view rel(rel_filename); + return rel.size() > 10 && rel.substr(0, 8) == "@stdlib/"; // common.tolk, tvm-dicts.tolk, etc +} + bool SrcFile::is_offset_valid(int offset) const { return offset >= 0 && offset < static_cast(text.size()); } diff --git a/tolk/src-file.h b/tolk/src-file.h index 28de75680..815dccbed 100644 --- a/tolk/src-file.h +++ b/tolk/src-file.h @@ -51,7 +51,7 @@ struct SrcFile { SrcFile(const SrcFile& other) = delete; SrcFile &operator=(const SrcFile&) = delete; - bool is_stdlib_file() const { return file_id == 0; /* stdlib always exists, has no imports and parsed the first */ } + bool is_stdlib_file() const; bool is_offset_valid(int offset) const; SrcPosition convert_offset(int offset) const; diff --git a/tolk/tolk-main.cpp b/tolk/tolk-main.cpp index 2dc5a0df6..7f939670b 100644 --- a/tolk/tolk-main.cpp +++ b/tolk/tolk-main.cpp @@ -29,9 +29,14 @@ #include "td/utils/port/path.h" #include #include -#include #include -#include +#ifdef TD_DARWIN +#include +#elif TD_WINDOWS +#include +#else // linux +#include +#endif #include "git.h" using namespace tolk; @@ -50,42 +55,89 @@ void usage(const char* progname) { std::exit(2); } -static bool stdlib_file_exists(std::filesystem::path& stdlib_tolk) { +static bool stdlib_folder_exists(const char* stdlib_folder) { struct stat f_stat; - stdlib_tolk = stdlib_tolk.lexically_normal(); - int res = stat(stdlib_tolk.c_str(), &f_stat); - return res == 0 && S_ISREG(f_stat.st_mode); + int res = stat(stdlib_folder, &f_stat); + return res == 0 && (f_stat.st_mode & S_IFMT) == S_IFDIR; } -static std::string auto_discover_stdlib_location(const char* argv0) { - // first, the user can specify env var that points directly to stdlib (useful for non-standard compiler locations) - if (const char* env_var = getenv("TOLK_STDLIB")) { - return env_var; +// getting current executable path is a complicated and not cross-platform task +// for instance, we can't just use argv[0] or even filesystem::canonical +// https://stackoverflow.com/questions/1023306/finding-current-executables-path-without-proc-self-exe/1024937 +static bool get_current_executable_filename(std::string& out) { +#ifdef TD_DARWIN + char name_buf[1024]; + unsigned int size = 1024; + if (0 == _NSGetExecutablePath(name_buf, &size)) { // may contain ../, so normalize it + char *exe_path = realpath(name_buf, nullptr); + if (exe_path != nullptr) { + out = exe_path; + return true; + } } +#elif TD_WINDOWS + char exe_path[1024]; + if (GetModuleFileNameA(nullptr, exe_path, 1024)) { + out = exe_path; + std::replace(out.begin(), out.end(), '\\', '/'); // modern Windows correctly deals with / separator + return true; + } +#else // linux + char exe_path[1024]; + ssize_t res = readlink("/proc/self/exe", exe_path, 1024 - 1); + if (res >= 0) { + exe_path[res] = 0; + out = exe_path; + return true; + } +#endif + return false; +} +// simple join "/some/folder/" (guaranteed to end with /) and "../relative/path" +static std::string join_path(std::string dir, const char* relative) { + while (relative[0] == '.' && relative[1] == '.' && relative[2] == '/') { + size_t slash_pos = dir.find_last_of('/', dir.size() - 2); // last symbol is slash, find before it + if (slash_pos != std::string::npos) { + dir = dir.substr(0, slash_pos + 1); + } + relative += 3; + } + + return dir + relative; +} + +static std::string auto_discover_stdlib_folder() { // if the user launches tolk compiler from a package installed (e.g. /usr/bin/tolk), // locate stdlib in /usr/share/ton/smartcont (this folder exists on package installation) // (note, that paths are not absolute, they are relative to the launched binary) // consider https://github.com/ton-blockchain/packages for actual paths - std::filesystem::path executable_dir = std::filesystem::canonical(argv0).remove_filename(); + std::string executable_filename; + if (!get_current_executable_filename(executable_filename)) { + return {}; + } + + // extract dirname to concatenate with relative paths (separator / is ok even for windows) + size_t slash_pos = executable_filename.find_last_of('/'); + std::string executable_dir = executable_filename.substr(0, slash_pos + 1); #ifdef TD_DARWIN - auto def_location = executable_dir / "../share/ton/ton/smartcont/stdlib.tolk"; + std::string def_location = join_path(executable_dir, "../share/ton/ton/smartcont/tolk-stdlib"); #elif TD_WINDOWS - auto def_location = executable_dir / "smartcont/stdlib.tolk"; + std::string def_location = join_path(executable_dir, "smartcont/tolk-stdlib"); #else // linux - auto def_location = executable_dir / "../share/ton/smartcont/stdlib.tolk"; + std::string def_location = join_path(executable_dir, "../share/ton/smartcont/tolk-stdlib"); #endif - if (stdlib_file_exists(def_location)) { + if (stdlib_folder_exists(def_location.c_str())) { return def_location; } // so, the binary is not from a system package // maybe it's just built from sources? e.g. ~/ton/cmake-build-debug/tolk/tolk // then, check the ~/ton/crypto/smartcont folder - auto near_when_built_from_sources = executable_dir / "../../crypto/smartcont/stdlib.tolk"; - if (stdlib_file_exists(near_when_built_from_sources)) { + std::string near_when_built_from_sources = join_path(executable_dir, "../../crypto/smartcont/tolk-stdlib"); + if (stdlib_folder_exists(near_when_built_from_sources.c_str())) { return near_when_built_from_sources; } @@ -95,10 +147,31 @@ static std::string auto_discover_stdlib_location(const char* argv0) { td::Result fs_read_callback(CompilerSettings::FsReadCallbackKind kind, const char* query) { switch (kind) { + case CompilerSettings::FsReadCallbackKind::Realpath: { + td::Result res_realpath; + if (query[0] == '@' && strlen(query) > 8 && !strncmp(query, "@stdlib/", 8)) { + // import "@stdlib/filename" or import "@stdlib/filename.tolk" + std::string path = G.settings.stdlib_folder + static_cast(query + 7); + if (strncmp(path.c_str() + path.size() - 5, ".tolk", 5) != 0) { + path += ".tolk"; + } + res_realpath = td::realpath(td::CSlice(path.c_str())); + } else { + // import "relative/to/cwd/path.tolk" + res_realpath = td::realpath(td::CSlice(query)); + } + + if (res_realpath.is_error()) { + // note, that for non-existing files, `realpath()` on Linux/Mac returns an error, + // whereas on Windows, it returns okay, but fails after, on reading, with a message "cannot open file" + return td::Status::Error(std::string{"cannot find file "} + query); + } + return res_realpath; + } case CompilerSettings::FsReadCallbackKind::ReadFile: { struct stat f_stat; - int res = stat(query, &f_stat); - if (res != 0 || !S_ISREG(f_stat.st_mode)) { + int res = stat(query, &f_stat); // query here is already resolved realpath + if (res != 0 || (f_stat.st_mode & S_IFMT) != S_IFREG) { return td::Status::Error(std::string{"cannot open file "} + query); } @@ -110,15 +183,8 @@ td::Result fs_read_callback(CompilerSettings::FsReadCallbackKind ki fclose(f); return std::move(str); } - case CompilerSettings::FsReadCallbackKind::Realpath: { - td::Result res_realpath = td::realpath(td::CSlice(query)); - if (res_realpath.is_error()) { - return td::Status::Error(std::string{"cannot find file "} + query); - } - return res_realpath; - } default: { - return td::Status::Error("Unknown query kind"); + return td::Status::Error("unknown query kind"); } } } @@ -185,16 +251,26 @@ int main(int argc, char* const argv[]) { return 2; } - // locate stdlib.tolk based on env or default system paths - G.settings.stdlib_filename = auto_discover_stdlib_location(argv[0]); - if (G.settings.stdlib_filename.empty()) { - std::cerr << "Failed to discover stdlib.tolk.\n" + // locate tolk-stdlib/ based on env or default system paths + if (const char* env_var = getenv("TOLK_STDLIB")) { + std::string stdlib_filename = static_cast(env_var) + "/common.tolk"; + td::Result res = td::realpath(td::CSlice(stdlib_filename.c_str())); + if (res.is_error()) { + std::cerr << "Environment variable TOLK_STDLIB is invalid: " << res.move_as_error().message().c_str() << std::endl; + return 2; + } + G.settings.stdlib_folder = env_var; + } else { + G.settings.stdlib_folder = auto_discover_stdlib_folder(); + } + if (G.settings.stdlib_folder.empty()) { + std::cerr << "Failed to discover Tolk stdlib.\n" "Probably, you have a non-standard Tolk installation.\n" - "Please, provide env variable TOLK_STDLIB referencing to it.\n"; + "Please, provide env variable TOLK_STDLIB referencing to tolk-stdlib/ folder.\n"; return 2; } if (G.is_verbosity(2)) { - std::cerr << "stdlib located at " << G.settings.stdlib_filename << std::endl; + std::cerr << "stdlib folder: " << G.settings.stdlib_folder << std::endl; } if (optind != argc - 1) { @@ -202,8 +278,8 @@ int main(int argc, char* const argv[]) { return 2; } - G.settings.entrypoint_filename = argv[optind]; G.settings.read_callback = fs_read_callback; - return tolk_proceed(G.settings.entrypoint_filename); + int exit_code = tolk_proceed(argv[optind]); + return exit_code; } diff --git a/tolk/tolk-wasm.cpp b/tolk/tolk-wasm.cpp index a093a7f6b..e74589ce8 100644 --- a/tolk/tolk-wasm.cpp +++ b/tolk/tolk-wasm.cpp @@ -34,21 +34,18 @@ using namespace tolk; -td::Result compile_internal(char *config_json) { +static td::Result compile_internal(char *config_json) { TRY_RESULT(input_json, td::json_decode(td::MutableSlice(config_json))) td::JsonObject& config = input_json.get_object(); TRY_RESULT(opt_level, td::get_json_object_int_field(config, "optimizationLevel", true, 2)); - TRY_RESULT(stdlib_tolk, td::get_json_object_string_field(config, "stdlibLocation", false)); TRY_RESULT(stack_comments, td::get_json_object_bool_field(config, "withStackComments", true, false)); TRY_RESULT(entrypoint_filename, td::get_json_object_string_field(config, "entrypointFileName", false)); TRY_RESULT(experimental_options, td::get_json_object_string_field(config, "experimentalOptions", true)); G.settings.verbosity = 0; G.settings.optimization_level = std::max(0, opt_level); - G.settings.stdlib_filename = stdlib_tolk; G.settings.stack_layout_comments = stack_comments; - G.settings.entrypoint_filename = entrypoint_filename; if (!experimental_options.empty()) { G.settings.parse_experimental_options_cmd_arg(experimental_options.c_str()); } @@ -56,8 +53,8 @@ td::Result compile_internal(char *config_json) { std::ostringstream outs, errs; std::cout.rdbuf(outs.rdbuf()); std::cerr.rdbuf(errs.rdbuf()); - int tolk_res = tolk::tolk_proceed(entrypoint_filename); - if (tolk_res != 0) { + int exit_code = tolk_proceed(entrypoint_filename); + if (exit_code != 0) { return td::Status::Error("Tolk compilation error: " + errs.str()); } @@ -78,32 +75,29 @@ td::Result compile_internal(char *config_json) { /// Callback used to retrieve file contents from a "not file system". See tolk-js for implementation. /// The callback must fill either destContents or destError. /// The implementor must use malloc() for them and use free() after tolk_compile returns. -typedef void (*CStyleReadFileCallback)(int kind, char const* data, char** destContents, char** destError); - -CompilerSettings::FsReadCallback wrapReadCallback(CStyleReadFileCallback _readCallback) -{ - CompilerSettings::FsReadCallback readCallback; - if (_readCallback) { - readCallback = [=](CompilerSettings::FsReadCallbackKind kind, char const* data) -> td::Result { - char* destContents = nullptr; - char* destError = nullptr; +typedef void (*WasmFsReadCallback)(int kind, char const* data, char** destContents, char** destError); + +static CompilerSettings::FsReadCallback wrap_wasm_read_callback(WasmFsReadCallback _readCallback) { + return [_readCallback](CompilerSettings::FsReadCallbackKind kind, char const* data) -> td::Result { + char* destContents = nullptr; + char* destError = nullptr; + if (_readCallback) { _readCallback(static_cast(kind), data, &destContents, &destError); - if (!destContents && !destError) { - return td::Status::Error("Callback not supported"); - } - if (destContents) { - return destContents; - } + } + if (destContents) { + return destContents; + } + if (destError) { return td::Status::Error(std::string(destError)); - }; - } - return readCallback; + } + return td::Status::Error("Invalid callback from wasm"); + }; } extern "C" { const char* version() { - auto version_json = td::JsonBuilder(); + td::JsonBuilder version_json = td::JsonBuilder(); auto obj = version_json.enter_object(); obj("tolkVersion", TOLK_VERSION); obj("tolkFiftLibCommitHash", GitMetadata::CommitSHA1()); @@ -112,23 +106,22 @@ const char* version() { return strdup(version_json.string_builder().as_cslice().c_str()); } -const char *tolk_compile(char *config_json, CStyleReadFileCallback callback) { - G.settings.read_callback = wrapReadCallback(callback); +const char *tolk_compile(char *config_json, WasmFsReadCallback callback) { + G.settings.read_callback = wrap_wasm_read_callback(callback); td::Result res = compile_internal(config_json); if (res.is_error()) { - auto result = res.move_as_error(); - auto error_res = td::JsonBuilder(); - auto error_o = error_res.enter_object(); - error_o("status", "error"); - error_o("message", result.message().str()); - error_o.leave(); + td::JsonBuilder error_res = td::JsonBuilder(); + auto obj = error_res.enter_object(); + obj("status", "error"); + obj("message", res.move_as_error().message().str()); + obj.leave(); return strdup(error_res.string_builder().as_cslice().c_str()); } - auto res_string = res.move_as_ok(); - + std::string res_string = res.move_as_ok(); return strdup(res_string.c_str()); } -} + +} // extern "C" diff --git a/tolk/tolk.cpp b/tolk/tolk.cpp index 46eb4dc92..9268cc62d 100644 --- a/tolk/tolk.cpp +++ b/tolk/tolk.cpp @@ -48,21 +48,16 @@ int tolk_proceed(const std::string &entrypoint_filename) { define_builtins(); lexer_init(); + // on any error, an exception is thrown, and the message is printed out below + // (currently, only a single error can be printed) try { - if (G.settings.stdlib_filename.empty()) { - throw Fatal("stdlib filename not specified"); - } - - // on any error, an exception is thrown, and the message is printed out below - // (currently, only a single error can be printed) - - AllSrcFiles all_files = pipeline_discover_and_parse_sources(G.settings.stdlib_filename, entrypoint_filename); + AllSrcFiles all_files = pipeline_discover_and_parse_sources("@stdlib/common.tolk", entrypoint_filename); pipeline_register_global_symbols(all_files); pipeline_convert_ast_to_legacy_Expr_Op(all_files); pipeline_find_unused_symbols(); - pipeline_generate_fif_output_to_std_cout(); + pipeline_generate_fif_output_to_std_cout(all_files); return 0; } catch (Fatal& fatal) {