diff --git a/crypto/smartcont/tolk-stdlib/common.tolk b/crypto/smartcont/tolk-stdlib/common.tolk index 8ff2010bcb..c9e116860d 100644 --- a/crypto/smartcont/tolk-stdlib/common.tolk +++ b/crypto/smartcont/tolk-stdlib/common.tolk @@ -1,7 +1,7 @@ // Standard library for Tolk (LGPL licence). // It contains common functions that are available out of the box, the user doesn't have to import anything. // More specific functions are required to be imported explicitly, like "@stdlib/tvm-dicts". -tolk 0.12 +tolk 0.13 /// In Tolk v1.x there would be a type `map`. /// Currently, working with dictionaries is still low-level, with raw cells. @@ -55,6 +55,11 @@ fun tuple.size(self): int fun tuple.last(self): T asm "LAST"; +/// Pops and returns the last element of a non-empty tuple. +@pure +fun tuple.pop(mutate self): T + asm "TPOP"; + /** Mathematical primitives. @@ -133,10 +138,10 @@ fun mulDivMod(x: int, y: int, z: int): (int, int) /// Example: `contract.getCode()` and other methods. struct contract {} -/// 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]. +/// Returns the internal address of the current smart contract. +/// If necessary, it can be parsed further using [address.getWorkchain] and others. @pure -fun contract.getAddress(): slice +fun contract.getAddress(): address asm "MYADDR"; /// Returns the balance (in nanotoncoins) of the smart contract at the start of Computation Phase. @@ -210,6 +215,179 @@ fun commitContractDataAndActions(): void asm "COMMIT"; +/** + Auto packing structures to/from cells. +*/ + +/// PackOptions allows you to control behavior of `obj.toCell()` and similar functions. +struct PackOptions { + /// when a struct has a field of type `bits128` and similar (it's a slice under the hood), + /// by default, compiler inserts runtime checks (get bits/refs count + compare with 128 + compare with 0); + /// these checks ensure that serialized binary data will be correct, but they cost gas; + /// however, if you guarantee that a slice is valid (for example, it comes from trusted sources), + /// set this option to true to disable runtime checks; + /// note: `int32` and other are always validated for overflow without any extra gas, + /// so this flag controls only rarely used `bytesN` / `bitsN` types + skipBitsNFieldsValidation: bool = false, +} + +/// UnpackOptions allows you to control behavior of `MyStruct.fromCell(c)` and similar functions. +struct UnpackOptions { + // after finished reading all fields from a cell/slice, call [slice.assertEnd] to ensure no remaining data left; + // it's the default behavior, it ensures that you've fully described data you're reading with a struct; + // example: `struct Point { x: int8; y: int8 }`, input "0102" is ok, "0102FF" will throw excno 9; + // note: setting this to false does not decrease gas (DROP from a stack and ENDS cost the same); + // note: this option controls [T.fromCell] and [T.fromSlice], but is ignored by [slice.loadAny] + assertEndAfterReading: bool = true, + + /// this excNo is thrown if a prefix doesn't match, e.g. for `struct (0x01) A` given input "88..."; + /// similarly, for a union type, this is thrown when none of the opcodes match + throwIfOpcodeDoesNotMatch: int = 63, +} + +/// Convert anything to a cell (most likely, you'll call it for structures). +/// Example: +/// ``` +/// var st: MyStorage = { ... }; +/// contract.setData(st.toCell()); +/// ``` +/// Internally, a builder is created, all fields are serialized one by one, and a builder is flushed +/// (beginCell() + serialize fields + endCell()). +@pure +fun T.toCell(self, options: PackOptions = {}): Cell + builtin; + +/// Parse anything from a cell (most likely, you'll call it for structures). +/// Example: +/// ``` +/// var st = MyStorage.fromCell(contract.getData()); +/// ``` +/// Internally, a cell is unpacked to a slice, and that slice is parsed +/// (packedCell.beginParse() + read from slice). +@pure +fun T.fromCell(packedCell: cell, options: UnpackOptions = {}): T + builtin; + +/// Parse anything from a slice (most likely, you'll call it for structures). +/// Example: +/// ``` +/// var msg = CounterIncrement.fromSlice(cs); +/// ``` +/// All fields are read from a slice immediately. +/// If a slice is corrupted, an exception is thrown (most likely, excode 9 "cell underflow"). +/// Note, that a passed slice is NOT mutated, its internal pointer is NOT shifted. +/// If you need to mutate it, like `cs.loadInt()`, consider calling `cs.loadAny()`. +@pure +fun T.fromSlice(rawSlice: slice, options: UnpackOptions = {}): T + builtin; + +/// Parse anything from a slice, shifting its internal pointer. +/// Similar to `slice.loadUint()` and others, but allows loading structures. +/// Example: +/// ``` +/// var st: MyStorage = cs.loadAny(); // or cs.loadAny() +/// ``` +/// Similar to `MyStorage.fromSlice(cs)`, but called as a slice method and mutates the slice. +/// Note: [options.assertEndAfterReading] is ignored by this function, because it's actually intended +/// to read data from the middle. +@pure +fun slice.loadAny(mutate self, options: UnpackOptions = {}): T + builtin; + +/// Skip anything in a slice, shifting its internal pointer. +/// Similar to `slice.skipBits()` and others, but allows skipping structures. +/// Example: +/// ``` +/// struct TwoInts { a: int32; b: int32; } +/// cs.skipAny(); // skips 64 bits +/// ``` +@pure +fun slice.skipAny(mutate self, options: UnpackOptions = {}): self + builtin; + +/// Store anything to a builder. +/// Similar to `builder.storeUint()` and others, but allows storing structures. +/// Example: +/// ``` +/// var b = beginCell().storeUint(32).storeAny(msgBody).endCell(); +/// ``` +@pure +fun builder.storeAny(mutate self, v: T, options: PackOptions = {}): self + builtin; + +/// Returns serialization prefix of a struct. Works at compile-time. +/// Example: for `struct (0xF0) AssetRegular { ... }` will return `240`. +@pure +fun T.getDeclaredPackPrefix(): int + builtin; + +/// Returns serialization prefix length of a struct. Works at compile-time. +/// Example: for `struct (0xF0) AssetRegular { ... }` will return `16`. +@pure +fun T.getDeclaredPackPrefixLen(): int + builtin; + +/// Cell represents a typed cell reference (as opposed to untyped `cell`). +/// Example: +/// ``` +/// struct ExtraData { ... } +/// +/// struct MyStorage { +/// ... +/// extra: Cell; // TL-B `^ExtraData` +/// optional: Cell?; // TL-B `(Maybe ^ExtraData)` +/// code: cell; // TL-B `^Cell` +/// data: cell?; // TL-B `(Maybe ^Cell)` +/// } +/// ``` +/// Note, that `st = MyStorage.fromSlice(s)` does NOT deep-load any refs; `st.extra` is `Cell`, not `T`; +/// you should manually call `st.extra.load()` to get T (ExtraData in this example). +struct Cell { + tvmCell: cell; +} + +/// Parse data from already loaded cell reference. +/// Example: +/// ``` +/// struct MyStorage { ... extra: Cell; } +/// +/// var st = MyStorage.fromCell(contract.getData()); +/// // st.extra is cell; if we need to unpack it, we do +/// var extra = st.extra.load(); // it's ExtraData, unpacked from loaded ref +/// ``` +@pure +fun Cell.load(self, options: UnpackOptions = {}): T + builtin; + +/// Converts a typed cell into a slice. +@pure +fun Cell.beginParse(self): slice + asm "CTOS"; + +/// RemainingBitsAndRefs is a special built-in type to get "all the rest" slice tail on reading. +/// Example: +/// ``` +/// struct JettonMessage { +/// ... some fields +/// forwardPayload: RemainingBitsAndRefs; +/// } +/// ``` +/// When you deserialize JettonMessage, forwardPayload contains "everything left after reading fields above". +type RemainingBitsAndRefs = slice; + +/// Creates a cell with zero bits and references. +/// Equivalent to `beginCell().endCell()` but cheaper. +@pure +fun createEmptyCell(): cell + asm " PUSHREF"; + +/// Creates a slice with zero remaining bits and references. +/// Equivalent to `beginCell().endCell().beginParse()` but cheaper. +@pure +fun createEmptySlice(): slice + asm "x{} PUSHSLICE"; + + /** Signature checks, hashing, cryptography. */ @@ -378,6 +556,14 @@ fun slice.depth(self): int fun builder.depth(self): int asm "BDEPTH"; +/// Returns the number of stack slots anyVariable occupies (works at compile-time). +/// Example: sizeof(nullableInt) = 1, because `int?` is 1 TVM slot holding either NULL or a value. +/// Example: sizeof(somePoint) = 2 for `struct Point { x:int, y: int }`: two fields one slot per each. +/// Useful for debugging or when preparing stack contents for RUNVM. +@pure +fun sizeof(anyVariable: T): int + builtin; + /** Debug primitives. @@ -407,15 +593,6 @@ fun debug.dumpStack(): void When you _preload_ some data, you just get the result without mutating the slice. */ -/// Compile-time function that converts a constant string to TL-encoded MsgAddressInt (TVM slice). -/// Example: stringAddressToSlice("EQCRDM9h4k3UJdOePPuyX40mCgA4vxge5Dc5vjBR8djbEKC5") -/// Example: stringAddressToSlice("0:527964d55cfa6eb731f4bfc07e9d025098097ef8505519e853986279bd8400d8") -/// Note: it's a compile-time function, `stringAddressToSlice(slice_var)` does not work. -/// Use `parseStandardAddress` to decode a slice at runtime into workchain and hash. -@pure -fun stringAddressToSlice(constStringAddress: slice): slice - builtin; - /// Compile-time function that converts a constant hex-encoded string to N/2 bytes. /// Example: `const v = stringHexToSlice("abcdef")` = slice with 3 bytes `[ 0xAB, 0xCD, 0xEF ]` /// Note: stringHexToSlice(slice_var) does not work! It accepts a constant string and works at compile-time. @@ -430,7 +607,7 @@ fun stringHexToSlice(constStringBytesHex: slice): slice fun cell.beginParse(self): slice asm "CTOS"; -/// Checks if slice is empty. If not, throws an exception. +/// Checks if slice is empty. If not, throws an exception with code 9. fun slice.assertEnd(self): void asm "ENDS"; @@ -573,6 +750,11 @@ fun builder.storeUint(mutate self, x: int, len: int): self fun builder.storeSlice(mutate self, s: slice): self asm "STSLICER"; +/// Stores an address into a builder. +@pure +fun builder.storeAddress(mutate self, addr: address): self + asm "STSLICER"; + /// Stores amount of Toncoins into a builder. @pure fun builder.storeCoins(mutate self, x: coins): self @@ -694,37 +876,64 @@ fun builder.bitsCount(self): int 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. +/// Compile-time function that parses a valid contract address. +/// Example: address("EQCRDM9h4k3UJdOePPuyX40mCgA4vxge5Dc5vjBR8djbEKC5") +/// Example: address("0:527964d55cfa6eb731f4bfc07e9d025098097ef8505519e853986279bd8400d8") +/// Returns `address`, which can be stored in a builder, compared with `==`, etc. @pure -fun slice.loadAddress(mutate self): slice - asm( -> 1 0) "LDMSGADDR"; +fun address(stdAddress: slice): address + builtin; + +/// Creates a slice representing TL addr_none$00 (two `0` bits). +@pure +fun createAddressNone(): address + asm "b{00} PUSHSLICE"; + +/// Returns if it's an empty address. +/// Don't confuse it with null! Empty address is a slice with two `0` bits. +/// In TL/B, it's addr_none$00. +@pure +fun address.isNone(self): bool + asm "b{00} SDBEGINSQ" "NIP"; -/// 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. +/// Returns if it's a standard (internal) address. Such addresses contain workchain (8 bits) and hash (256 bits). +/// All contract addresses are internal, so it's the most practical use case. +/// In TL/B it's addr_std$10. +/// For internal addresses, you can call [address.getWorkchain] and [address.getWorkchainAndHash]. @pure -fun parseAddress(s: slice): tuple - asm "PARSEMSGADDR"; +fun address.isInternal(self): bool + asm "b{10} SDBEGINSQ" "NIP"; -/// 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. +/// Returns if it's an external address, used to communication with the outside world. +/// In TL/B it's addr_extern$01. @pure -fun parseStandardAddress(s: slice): (int, int) +fun address.isExternal(self): bool + asm "b{01} SDBEGINSQ" "NIP"; + +/// Extracts workchain and hash from a standard (internal) address. +/// If the address is not internal, throws a cell deserialization exception. +@pure +fun address.getWorkchainAndHash(self): (int8, uint256) asm "REWRITESTDADDR"; -/// Creates a slice representing TL addr_none$00 (two `0` bits). +/// Extracts workchain from a standard (internal) address. +/// If the address is not internal, throws a cell deserialization exception. @pure -fun createAddressNone(): slice - asm "b{00} PUSHSLICE"; +fun address.getWorkchain(self): int8 + asm "REWRITESTDADDR" "DROP"; -/// Returns if a slice pointer contains an empty address. -/// In other words, a slice starts with two `0` bits (TL addr_none$00). +/// Checks whether two addresses are equal. Equivalent to `a == b`. +/// Deprecated! Left for smoother transition from FunC, where you used `slice` everywhere. +/// Use just `a == b` and `a != b` to compare addresses, don't use bitsEqual. @pure -fun addressIsNone(s: slice): bool - asm "2 PLDU" "0 EQINT"; +@deprecated("use `senderAddress == ownerAddress`, not `senderAddress.bitsEqual(ownerAddress)`") +fun address.bitsEqual(self, b: address): bool + asm "SDEQ"; + +/// Loads from slice [s] a valid `MsgAddress` (none/internal/external). +@pure +fun slice.loadAddress(mutate self): address + asm( -> 1 0) "LDMSGADDR"; /** diff --git a/crypto/smartcont/tolk-stdlib/gas-payments.tolk b/crypto/smartcont/tolk-stdlib/gas-payments.tolk index 2c35faf930..8fde5f3c32 100644 --- a/crypto/smartcont/tolk-stdlib/gas-payments.tolk +++ b/crypto/smartcont/tolk-stdlib/gas-payments.tolk @@ -1,12 +1,12 @@ // A part of standard library for Tolk -tolk 0.12 +tolk 0.13 /** Gas and payment related primitives. */ /// Returns amount of gas (in gas units) consumed in current Computation Phase. -fun getGasConsumedAtTheMoment(): coins +fun getGasConsumedAtTheMoment(): int asm "GASCONSUMED"; /// This function is required to be called when you process an external message (from an outer world) diff --git a/crypto/smartcont/tolk-stdlib/lisp-lists.tolk b/crypto/smartcont/tolk-stdlib/lisp-lists.tolk index ada7a72bc7..14d8f835b8 100644 --- a/crypto/smartcont/tolk-stdlib/lisp-lists.tolk +++ b/crypto/smartcont/tolk-stdlib/lisp-lists.tolk @@ -1,5 +1,5 @@ // A part of standard library for Tolk -tolk 0.12 +tolk 0.13 /** Lisp-style lists are nested 2-elements tuples: `[1, [2, [3, null]]]` represents list `[1, 2, 3]`. diff --git a/crypto/smartcont/tolk-stdlib/tvm-dicts.tolk b/crypto/smartcont/tolk-stdlib/tvm-dicts.tolk index 90b392f615..9d678e83d8 100644 --- a/crypto/smartcont/tolk-stdlib/tvm-dicts.tolk +++ b/crypto/smartcont/tolk-stdlib/tvm-dicts.tolk @@ -1,5 +1,5 @@ // A part of standard library for Tolk -tolk 0.12 +tolk 0.13 /** Dictionaries are represented as `cell` data type (cells can store anything, dicts in particular). diff --git a/crypto/smartcont/tolk-stdlib/tvm-lowlevel.tolk b/crypto/smartcont/tolk-stdlib/tvm-lowlevel.tolk index 47b4a300ee..7281038d64 100644 --- a/crypto/smartcont/tolk-stdlib/tvm-lowlevel.tolk +++ b/crypto/smartcont/tolk-stdlib/tvm-lowlevel.tolk @@ -1,5 +1,5 @@ // A part of standard library for Tolk -tolk 0.12 +tolk 0.13 /// Usually `c3` has a continuation initialized by the whole code of the contract. It is used for function calls. /// The primitive returns the current value of `c3`. diff --git a/tolk-tester/tests/annotations-tests.tolk b/tolk-tester/tests/annotations-tests.tolk new file mode 100644 index 0000000000..730b0ee0bd --- /dev/null +++ b/tolk-tester/tests/annotations-tests.tolk @@ -0,0 +1,31 @@ +@deprecated("anything") +@custom("anything") +global a: int; + +@deprecated +@custom +const ASDF = 1; + +@custom("props", {allowed: false, ids: [1,2,3]}) +@deprecated +fun f() {} + +@custom({ + `type`: 123, + value: 19 +}) +@custom("another", 12, "annotation") +struct TTT {} + +@deprecated +type MyMsg = int; + +@custom(1,2,3,4) +@custom({function: "main", contract: self}) +fun main() { + return 0; +} + +/** +@testcase | 0 | | 0 + */ diff --git a/tolk-tester/tests/asm-arg-order.tolk b/tolk-tester/tests/asm-arg-order.tolk index 6d55b2da3f..cb7b6f1e30 100644 --- a/tolk-tester/tests/asm-arg-order.tolk +++ b/tolk-tester/tests/asm-arg-order.tolk @@ -180,6 +180,12 @@ fun test33(x: int) { return ((x += 10).plus1TimesB(2), (x += 20).plus1TimesB(x), ((x /= (g2=2)).plus1TimesB(x*g2)), setG2(7).plus1TimesB(g2)); } +@method_id(34) +fun test34() { + var cs = stringHexToSlice("020a"); + return asmAPlus1TimesB(cs.loadUint(8), cs.loadUint(8)); +} + fun main() { } @@ -205,6 +211,7 @@ fun main() { @testcase | 31 | | 70 @testcase | 32 | | 30 @testcase | 33 | 0 | 22 930 480 56 +@testcase | 34 | | 30 @fif_codegen """ @@ -253,5 +260,17 @@ fun main() { }> """ -@code_hash 78671986831403867804966279036762472603849672357801214378328975900111280733054 +@fif_codegen +""" + test34 PROC:<{ + x{020a} PUSHSLICE + 8 LDU + 8 LDU + DROP + SWAP + 1 ADDCONST MUL + }> +""" + +@code_hash 93297578247504549901423325446957614083213743835412767087757830691482809332788 */ diff --git a/tolk-tester/tests/assignment-tests.tolk b/tolk-tester/tests/assignment-tests.tolk index a52b3ca50e..e53020d050 100644 --- a/tolk-tester/tests/assignment-tests.tolk +++ b/tolk-tester/tests/assignment-tests.tolk @@ -212,6 +212,43 @@ fun test117() { return c; } +struct Storage { + owner: slice; +} + +@method_id(118) +fun test118(x: int) { + var i1: int; + if (10 > 3) { + i1 = 10; + } else { { (_, i1) = (5, 20); } } + + var i2: int10; + match (x) { + 1 => i2 = 1, + 2 => i2 = 2, + 3 => i2 = 3, + else => i2 = 4, + } + + var st: Storage?; + if (x > 100) { + throw 123; + } else { + if (x < -100) { throw 456; } + else { st = { owner: "" }} + } + + var i3: (int, int) | builder; + match (i1) { + int => i3 = (1, 2), + } + + var unused: int8; + + return (i1, i2, st.owner.remainingBitsCount(), i3.0); +} + fun main(value: int, ) { @@ -241,6 +278,7 @@ fun main(value: int, ) { @testcase | 115 | | [ 101 111 ] 9 9 @testcase | 116 | | 1 2 3 4 [ 1 2 3 4 ] @testcase | 117 | | [ 20 ] +@testcase | 118 | 3 | 10 3 0 1 @fif_codegen diff --git a/tolk-tester/tests/calls-tests.tolk b/tolk-tester/tests/calls-tests.tolk new file mode 100644 index 0000000000..de508a2d5d --- /dev/null +++ b/tolk-tester/tests/calls-tests.tolk @@ -0,0 +1,164 @@ +const ZERO = 0; + +fun sumABDef0(a: int, b: int = 0) { + return a + b; +} + +fun sumADef1BDef2(a: int = ((1)), b: int = 2+ZERO) { + return a + b; +} + +fun getAOrB(a: T, b: T, getA: bool = false): T { + return getA ? a : b; +} + +fun pushToTuple(mutate t: tuple, v: T? = null) { + t.push(v); +} + +struct Point { + x: int; + y: int; +} + +fun Point.create(x: int = 0, y: int = 0): Point { + return {x,y}; +} + +fun Point.incrementX(mutate self, deltaX: int = 1) { + self.x += deltaX; +} + +fun makeCost(cost: coins = ton("0.05")) { + return cost + ton("0.05"); +} + +fun makeUnion(v: int | slice = 0) { + return v; +} + +struct MyOptions { + negate: bool = false, + mulBy: int = 0, +} + +global t105: tuple; + +fun log105(a: int, options: MyOptions = {}) { + if (options.negate) { + a = -a; + } + if (options.mulBy) { + a *= options.mulBy; + } + t105.push(a); +} + +struct AnotherOptions { + c1: int; + leaveSign: bool = true; +} + +fun helper106(a: int, options: AnotherOptions = { c1: 1 }) { + a *= options.c1; + return options.leaveSign ? +a : -a; +} + + +@method_id(101) +fun test1(a: int) { + return ( + sumABDef0(a), + sumABDef0(a, 5), + sumABDef0(100), + sumADef1BDef2(a), + sumADef1BDef2(), + sumADef1BDef2(a, 100), + sumADef1BDef2(200, 100), + ) +} + +@method_id(102) +fun test2(a: int) { + var t = createEmptyTuple(); + pushToTuple(mutate t, getAOrB(sumADef1BDef2(a), sumADef1BDef2(a, a))); + pushToTuple(mutate t, true); + pushToTuple(mutate t, null); + pushToTuple(mutate t, null); + pushToTuple(mutate t, null); + return t; +} + +@method_id(103) +fun test3() { + var p = Point.create(); + p.incrementX(); + var p2 = Point.create(8); + p2.incrementX(p.x += 1); + return (p, p2); +} + +@method_id(104) +fun test4() { + return ( + makeCost(), + makeCost(ton("0.1")), + makeUnion() is int, + makeUnion(), + makeUnion(makeCost()), + ) +} + +@method_id(105) +fun test5() { + t105 = createEmptyTuple(); + log105(1); + log105(1, { negate: true }); + log105(1, { mulBy: 100 }); + log105(1, { mulBy: 100, negate: true }); + log105(1, {}); + return t105; +} + +@method_id(106) +fun test6(l4: bool) { + return ( + helper106(5), + helper106(5, {leaveSign: false, c1: 1}), + helper106(5, {c1: 10}), + helper106(5, {leaveSign: l4, c1: 10}), + helper106(5, {c1: 0}), + ); +} + +fun int.plus(self, v: int = 1) { + return self + v; +} + +@method_id(107) +fun test7() { + return (10.plus(5.plus()), int.plus(4)); +} + +fun createTFrom(v: (int, (U, V)) = (1, (2, 3))) { + return [v.0, v.1.0, v.1.1]; +} + +@method_id(108) +fun test8() { + __expect_type(createTFrom, "((int, (coins, int8))) -> [int, coins, int8]"); + return (createTFrom(), createTFrom((5, (8, createTFrom().0)))); +} + +fun main() {} + +/** +@testcase | 101 | 10 | 10 15 100 12 3 110 300 +@testcase | 102 | 10 | [ 20 -1 (null) (null) (null) ] +@testcase | 103 | | 2 0 10 0 +@testcase | 104 | | 100000000 150000000 -1 0 1 100000000 1 +@testcase | 105 | | [ 1 -1 100 -100 1 ] +@testcase | 106 | 0 | 5 -5 50 -50 0 +@testcase | 107 | | 16 5 +@testcase | 108 | | [ 1 2 3 ] [ 5 8 1 ] + */ diff --git a/tolk-tester/tests/cells-slices.tolk b/tolk-tester/tests/cells-slices.tolk index 70c40b531e..253ebfff67 100644 --- a/tolk-tester/tests/cells-slices.tolk +++ b/tolk-tester/tests/cells-slices.tolk @@ -183,7 +183,7 @@ fun test111() { .endCell().beginParse(); var op1 = s.loadUint(32); var q1 = s.loadUint(64); - if (addressIsNone(s)) { + if ((s as address).isNone()) { s.skipBits(2); } if (s.loadBool() == false) { @@ -254,13 +254,15 @@ Note, that since 'compute-asm-ltr' became on be default, chaining methods codege @fif_codegen """ test6 PROC:<{ - 3 PUSHINT // '0=3 - 2 PUSHINT // '0=3 '1=2 - 1 PUSHINT // '0=3 '1=2 '2=1 - NEWC // '0=3 '1=2 '2=1 '3 - 32 STU // '0=3 '1=2 '3 - 32 STU // '0=3 '3 - 32 STU // '3 + 1 PUSHINT // '0=1 + NEWC // '0=1 '1 + 32 STU // '1 + 2 PUSHINT // '1 '4=2 + SWAP // '4=2 '1 + 32 STU // '1 + 3 PUSHINT // '1 '7=3 + SWAP // '7=3 '1 + 32 STU // '1 }> """ */ diff --git a/tolk-tester/tests/constants-tests.tolk b/tolk-tester/tests/constants-tests.tolk index ed510c13c6..838a7dae33 100644 --- a/tolk-tester/tests/constants-tests.tolk +++ b/tolk-tester/tests/constants-tests.tolk @@ -22,6 +22,8 @@ const nibbles: int = 4; const strange_zero = (!10 as int); const strange_minus_1: MInt = (!0 as int); +const addr1 = address("Ef8zMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzM0vF"); + const true1 = true; const true2 = !!true; const true3 = true1 && true2; @@ -88,6 +90,12 @@ fun test5() { return (intOrN == null, int32Or64 is int32, int32Or64); } +@method_id(106) +fun test6() { + __expect_type(addr1, "address"); + return (addr1 == addr1, addr1 != addr1, addr1 == createAddressNone(), addr1.getWorkchain()); +} + fun main() { var i1: int = iget1(); var i2: int = iget2(); @@ -117,6 +125,7 @@ fun main() { @testcase | 103 | | 0 0 @testcase | 104 | | 1 1 2 @testcase | 105 | | -1 0 7 48 +@testcase | 106 | | -1 0 0 -1 -@code_hash 49556957179018386976033482229516007597784982050169632168468608374010225644988 +@code_hash 85012002134196298607946339234948178079079623823648671175958399073436861460061 */ diff --git a/tolk-tester/tests/generics-4.tolk b/tolk-tester/tests/generics-4.tolk index 30ce19e352..cfe5714ff1 100644 --- a/tolk-tester/tests/generics-4.tolk +++ b/tolk-tester/tests/generics-4.tolk @@ -146,15 +146,36 @@ fun test7() { return (v1, v2, v3, v4); } +struct FakeGeneric8 { + alwaysInt: int; +} + +struct Snake8 { + next: FakeGeneric8; // it's not a recursive struct, it's okay + next2: FakeGeneric8?; + next3: FakeGeneric8>; +} + +@method_id(108) +fun test8() { + var sn: Snake8 = { + next: { alwaysInt: 10 }, + next2: null, + next3: { alwaysInt: 20 } + }; + return sn; +} + fun main() { } /** @testcase | 103 | | 10 20 30 777 40 40 @testcase | 104 | | (null) (null) (null) -@testcase | 105 | | -1 (null) 0 (null) 133 777 0 123 0 456 132 +@testcase | 105 | | -1 (null) 0 (null) 134 777 0 123 0 456 133 @testcase | 106 | | -1 0 -1 -1 @testcase | 107 | | 1 10 110 117 +@testcase | 108 | | 10 (null) 20 @fif_codegen DECLPROC eqUnusedT @fif_codegen DECLPROC eqUnusedU diff --git a/tolk-tester/tests/indexed-access.tolk b/tolk-tester/tests/indexed-access.tolk index f84d2d93ed..113124dd54 100644 --- a/tolk-tester/tests/indexed-access.tolk +++ b/tolk-tester/tests/indexed-access.tolk @@ -239,7 +239,7 @@ fun getT() { return (1, 2); } @method_id(120) fun test120() { - return (getT().0 = 3, getT().0 = 4, [getT().0 = 5, getT().0 = 6]); + return (getT().0 = 3, getT().0 = sizeof(getT()) * 2, [getT().0 = 5, getT().0 = 6]); } @method_id(121) diff --git a/tolk-tester/tests/invalid-declaration/err-1055.tolk b/tolk-tester/tests/invalid-declaration/err-1055.tolk new file mode 100644 index 0000000000..3b89f53e0b --- /dev/null +++ b/tolk-tester/tests/invalid-declaration/err-1055.tolk @@ -0,0 +1,19 @@ + +struct Options { + o1: bool; + o2: bool; +} + +fun getBool() { return true; } + +fun f(x: Options = { + o1: true, + o2: getBool(), +}) { +} + +/** +@compilation_should_fail +@stderr not a constant expression +@stderr o2: getBool() + */ diff --git a/tolk-tester/tests/invalid-declaration/err-1190.tolk b/tolk-tester/tests/invalid-declaration/err-1190.tolk new file mode 100644 index 0000000000..510e1d1cc0 --- /dev/null +++ b/tolk-tester/tests/invalid-declaration/err-1190.tolk @@ -0,0 +1,9 @@ +fun main() { + var i; +} + +/** +@compilation_should_fail +@stderr provide a type for a variable, because its default value is omitted: +@stderr var i: ; + */ diff --git a/tolk-tester/tests/invalid-declaration/err-1585.tolk b/tolk-tester/tests/invalid-declaration/err-1585.tolk index 0c1fef3328..5abf000178 100644 --- a/tolk-tester/tests/invalid-declaration/err-1585.tolk +++ b/tolk-tester/tests/invalid-declaration/err-1585.tolk @@ -3,8 +3,13 @@ struct A { b: BAlias } type BAlias = B; +fun test(a: A) { + a.b; + a.b.a; +} + /** @compilation_should_fail -@stderr struct `B` size is infinity due to recursive fields -@stderr struct B { +(error message not stable, sometimes about A, sometimes B, it's okay, they are in a hashmap) +@stderr size is infinity due to recursive fields */ diff --git a/tolk-tester/tests/invalid-declaration/err-1617.tolk b/tolk-tester/tests/invalid-declaration/err-1617.tolk new file mode 100644 index 0000000000..dbfe897194 --- /dev/null +++ b/tolk-tester/tests/invalid-declaration/err-1617.tolk @@ -0,0 +1,8 @@ +fun main() { + val (s, i); +} + +/** +@compilation_should_fail +@stderr variables declaration must be followed by assignment + */ diff --git a/tolk-tester/tests/invalid-declaration/err-1665.tolk b/tolk-tester/tests/invalid-declaration/err-1665.tolk new file mode 100644 index 0000000000..4ba5297b9a --- /dev/null +++ b/tolk-tester/tests/invalid-declaration/err-1665.tolk @@ -0,0 +1,8 @@ +fun f(a: int, b: int = a) { + +} + +/** +@compilation_should_fail +@stderr symbol `a` is not a constant + */ diff --git a/tolk-tester/tests/invalid-declaration/err-1764.tolk b/tolk-tester/tests/invalid-declaration/err-1764.tolk new file mode 100644 index 0000000000..6afaa18498 --- /dev/null +++ b/tolk-tester/tests/invalid-declaration/err-1764.tolk @@ -0,0 +1,9 @@ + +fun increment(mutate x: int = 0) { + x += 1; +} + +/** +@compilation_should_fail +@stderr `mutate` parameter can't have a default value + */ diff --git a/tolk-tester/tests/invalid-declaration/err-1795.tolk b/tolk-tester/tests/invalid-declaration/err-1795.tolk new file mode 100644 index 0000000000..67b062cbe7 --- /dev/null +++ b/tolk-tester/tests/invalid-declaration/err-1795.tolk @@ -0,0 +1,13 @@ +@overflow1023_policy("ignorje") +struct A { + d1: bits256; + d2: bits256; + d3: bits256; + d4: bits256; + d5: bits256; +} + +/** +@compilation_should_fail +@stderr incorrect value for @overflow1023_policy + */ diff --git a/tolk-tester/tests/invalid-semantics/err-4208.tolk b/tolk-tester/tests/invalid-semantics/err-4208.tolk new file mode 100644 index 0000000000..c40dda98cb --- /dev/null +++ b/tolk-tester/tests/invalid-semantics/err-4208.tolk @@ -0,0 +1,6 @@ +const flyp = 9999999.77777777777777; + +/** +@compilation_should_fail +@stderr invalid numeric index + */ diff --git a/tolk-tester/tests/invalid-semantics/err-4301.tolk b/tolk-tester/tests/invalid-semantics/err-4301.tolk new file mode 100644 index 0000000000..800495da3b --- /dev/null +++ b/tolk-tester/tests/invalid-semantics/err-4301.tolk @@ -0,0 +1,15 @@ +fun main() { + var i: int; + match (random.uint256()) { + 0 => { i = 0; debug.print(i) }, + 1 => { if (true) { i = 1 } }, + 2 => throw i = 2, + } + if (i == 0) {} +} + +/** +@compilation_should_fail +@stderr using variable `i` before it's definitely assigned +@stderr if (i == 0) + */ diff --git a/tolk-tester/tests/invalid-semantics/err-4309.tolk b/tolk-tester/tests/invalid-semantics/err-4309.tolk new file mode 100644 index 0000000000..3fa24df913 --- /dev/null +++ b/tolk-tester/tests/invalid-semantics/err-4309.tolk @@ -0,0 +1,32 @@ +fun increment(mutate x: int) { + x += 1; +} + +fun main() { + var i: int; + if (true) { + i = 10; + } + increment(mutate i); // ok + + var j: int8 | int16; + if (random.uint256()) { + (j, _) = (10 as int8, i); + } else { + return; + } + match (j) { // ok + int8 => increment(mutate i), + int16 => increment(mutate j), + } + + var k: int; + { + increment(mutate k); + } +} + +/** +@compilation_should_fail +@stderr using variable `k` before it's definitely assigned + */ diff --git a/tolk-tester/tests/invalid-semantics/err-4320.tolk b/tolk-tester/tests/invalid-semantics/err-4320.tolk new file mode 100644 index 0000000000..f57df590e2 --- /dev/null +++ b/tolk-tester/tests/invalid-semantics/err-4320.tolk @@ -0,0 +1,13 @@ +struct Point { + x: int; + y: int; +} + +fun main() { + return sizeof(Point); +} + +/** +@compilation_should_fail +@stderr `Point` only refers to a type, but is being used as a value here + */ diff --git a/tolk-tester/tests/invalid-semantics/err-4509.tolk b/tolk-tester/tests/invalid-semantics/err-4509.tolk new file mode 100644 index 0000000000..d168ddb968 --- /dev/null +++ b/tolk-tester/tests/invalid-semantics/err-4509.tolk @@ -0,0 +1,14 @@ +fun f(a: int = 0, b: int) { + +} + +fun main() { + f(1, 2); // ok + return f(); +} + +/** +@compilation_should_fail +@stderr too few arguments in call to `f`, expected 2, have 0 +@stderr f(); + */ diff --git a/tolk-tester/tests/invalid-semantics/err-4520.tolk b/tolk-tester/tests/invalid-semantics/err-4520.tolk index 994440163b..47b1d2d558 100644 --- a/tolk-tester/tests/invalid-semantics/err-4520.tolk +++ b/tolk-tester/tests/invalid-semantics/err-4520.tolk @@ -6,5 +6,5 @@ fun main() { /** @compilation_should_fail -@stderr method `3` not found for type `int` +@stderr method `3` not found */ diff --git a/tolk-tester/tests/invalid-serialization/err-7008.tolk b/tolk-tester/tests/invalid-serialization/err-7008.tolk new file mode 100644 index 0000000000..9203ea228e --- /dev/null +++ b/tolk-tester/tests/invalid-serialization/err-7008.tolk @@ -0,0 +1,16 @@ +struct Point { + x: int; + y: int; +} + +fun main(p: Point) { + p.toCell(); +} + +/** +@compilation_should_fail +@stderr auto-serialization via toCell() is not available for type `Point` +@stderr because field `Point.x` of type `int` can't be serialized +@stderr because type `int` is not serializable, it doesn't define binary width +@stderr hint: replace `int` with `int32` / `uint64` / `coins` / etc. + */ diff --git a/tolk-tester/tests/invalid-serialization/err-7114.tolk b/tolk-tester/tests/invalid-serialization/err-7114.tolk new file mode 100644 index 0000000000..fe228d6019 --- /dev/null +++ b/tolk-tester/tests/invalid-serialization/err-7114.tolk @@ -0,0 +1,22 @@ +type NotSerializableTensor = (int8, slice); + +struct Demo { + a: int8; + b: int8 | int16; + c: NotSerializableTensor; +} + +fun main(p: Demo?) { + p.toCell(); +} + +/** +@compilation_should_fail +@stderr auto-serialization via toCell() is not available for type `Demo?` +@stderr because field `Demo.c` of type `NotSerializableTensor` can't be serialized +@stderr because alias `NotSerializableTensor` expands to `(int8, slice)` +@stderr because element `tensor.1` of type `slice` can't be serialized +@stderr because type `slice` is not serializable, it doesn't define binary width +@stderr hint: replace `slice` with `address` if it's an address, actually +@stderr hint: replace `slice` with `bits128` and similar if it represents fixed-width data without refs + */ diff --git a/tolk-tester/tests/invalid-serialization/err-7215.tolk b/tolk-tester/tests/invalid-serialization/err-7215.tolk new file mode 100644 index 0000000000..63b46affc6 --- /dev/null +++ b/tolk-tester/tests/invalid-serialization/err-7215.tolk @@ -0,0 +1,23 @@ +type MInt = int; + +struct CantBe { + a: int8; + b: MInt?; +} + +struct Container { + item: T; +} + +fun main(s: slice) { + Container.fromSlice(s); +} + +/** +@compilation_should_fail +@stderr auto-serialization via fromSlice() is not available for type `Container` +@stderr because field `Container.item` of type `CantBe` can't be serialized +@stderr because field `CantBe.b` of type `MInt?` can't be serialized +@stderr because alias `MInt` expands to `int` +@stderr because type `int` is not serializable + */ diff --git a/tolk-tester/tests/invalid-serialization/err-7419.tolk b/tolk-tester/tests/invalid-serialization/err-7419.tolk new file mode 100644 index 0000000000..4133c80a05 --- /dev/null +++ b/tolk-tester/tests/invalid-serialization/err-7419.tolk @@ -0,0 +1,24 @@ +struct(0x1) Ok1 {} +struct(0x2) Ok2 {} +struct(0x3) Ok3 {} +struct NoPrefix {} + +type MsgOk1 = Ok1 | Ok2 | Ok3; + +type MsgCantBe = Ok1 | Ok2 | NoPrefix | Ok3; + +fun main() { + MsgOk1.fromCell(beginCell().endCell()); + + MsgCantBe.fromCell(beginCell().endCell()); +} + +/** +@compilation_should_fail +@stderr auto-serialization via fromCell() is not available for type `MsgCantBe` +@stderr because alias `MsgCantBe` expands to `Ok1 | Ok2 | NoPrefix | Ok3` +@stderr because could not automatically generate serialization prefixes for a union +@stderr because struct `Ok3` has opcode, but `NoPrefix` does not +@stderr hint: manually specify opcodes to all structures +@stderr err-7419.tolk:13 + */ diff --git a/tolk-tester/tests/invalid-serialization/err-7514.tolk b/tolk-tester/tests/invalid-serialization/err-7514.tolk new file mode 100644 index 0000000000..11728a6c7b --- /dev/null +++ b/tolk-tester/tests/invalid-serialization/err-7514.tolk @@ -0,0 +1,47 @@ +struct In4 { + b: builder; +} + +struct In3 { + i: In4; +} + +struct In2 { + i: In3?; +} + +struct In1 { + i: (In2, slice); +} + +struct MaybeNothing {} +struct MaybeJust { value: T } +type Maybe = MaybeNothing | MaybeJust; + +struct CantBe { + a: address; + b: address?; + i: Maybe; +} + +fun main(c: cell) { + var d: CantBe = CantBe.fromCell(c); +} + +/** +@compilation_should_fail +@stderr auto-serialization via fromCell() is not available for type `CantBe` +@stderr because field `CantBe.i` of type `Maybe` can't be serialized +@stderr because alias `Maybe` expands to `MaybeNothing | MaybeJust` +@stderr because variant #2 of type `MaybeJust` can't be serialized +@stderr because field `MaybeJust.value` of type `In1` can't be serialized +@stderr because field `In1.i` of type `(In2, slice)` can't be serialized +@stderr because element `tensor.0` of type `In2` can't be serialized +@stderr because field `In2.i` of type `In3?` can't be serialized +@stderr because field `In3.i` of type `In4` can't be serialized +@stderr because field `In4.b` of type `builder` can't be serialized +@stderr because type `builder` can not be used for reading, only for writing +@stderr hint: use `bitsN` or `RemainingBitsAndRefs` for reading +@stderr hint: using generics, you can substitute `builder` for writing and something other for reading +@stderr CantBe.fromCell(c) + */ diff --git a/tolk-tester/tests/invalid-serialization/err-7563.tolk b/tolk-tester/tests/invalid-serialization/err-7563.tolk new file mode 100644 index 0000000000..cc83a0cd59 --- /dev/null +++ b/tolk-tester/tests/invalid-serialization/err-7563.tolk @@ -0,0 +1,20 @@ +struct A { + d1: bits256; + d2: bits256; + d3: bits256; + d4: bits256; + d5: bits256; +} + +struct B { + a: Cell; +} + +fun main(b: B) { + b.toCell(); +} + +/** +@compilation_should_fail +@stderr struct `A` can exceed 1023 bits in serialization (estimated size: 1280..1280 bits) + */ diff --git a/tolk-tester/tests/invalid-serialization/err-7564.tolk b/tolk-tester/tests/invalid-serialization/err-7564.tolk new file mode 100644 index 0000000000..36eb63d4fa --- /dev/null +++ b/tolk-tester/tests/invalid-serialization/err-7564.tolk @@ -0,0 +1,16 @@ +struct A { + d1: bits256; + d2: bits256; + d3: bits256; + d4: bits256; + d5: bits256; +} + +fun main(o: A | int32) { + o.toCell(); +} + +/** +@compilation_should_fail +@stderr struct `A` can exceed 1023 bits in serialization (estimated size: 1280..1280 bits) + */ diff --git a/tolk-tester/tests/invalid-serialization/err-7810.tolk b/tolk-tester/tests/invalid-serialization/err-7810.tolk new file mode 100644 index 0000000000..2cf0d32e54 --- /dev/null +++ b/tolk-tester/tests/invalid-serialization/err-7810.tolk @@ -0,0 +1,20 @@ +struct(0b01) B {} +struct(0b00) C {} + +struct A { + multiple: B | C | int32; +} + +fun main() { + var a: A = { multiple: B{} }; + return a.toCell(); +} + +/** +@compilation_should_fail +@stderr auto-serialization via toCell() is not available for type `A` +@stderr because field `A.multiple` of type `B | C | int32` can't be serialized +@stderr because could not automatically generate serialization prefixes for a union +@stderr because of mixing primitives and struct `C` with serialization prefix +@stderr hint: extract primitives to single-field structs and provide prefixes + */ diff --git a/tolk-tester/tests/invalid-serialization/err-7811.tolk b/tolk-tester/tests/invalid-serialization/err-7811.tolk new file mode 100644 index 0000000000..5dbcab7e6b --- /dev/null +++ b/tolk-tester/tests/invalid-serialization/err-7811.tolk @@ -0,0 +1,19 @@ +struct(0b01) B {} +struct(0b00) C {} + +struct A { + multiple: B | C | null; +} + +fun main() { + var a: A = { multiple: B{} }; + return a.toCell(); +} + +/** +@compilation_should_fail +@stderr error: auto-serialization via toCell() is not available for type `A` +@stderr because field `A.multiple` of type `B | C | null` can't be serialized +@stderr because could not automatically generate serialization prefixes for a union +@stderr because of mixing primitives and struct `C` with serialization prefix + */ diff --git a/tolk-tester/tests/invalid-serialization/err-7812.tolk b/tolk-tester/tests/invalid-serialization/err-7812.tolk new file mode 100644 index 0000000000..0b69956330 --- /dev/null +++ b/tolk-tester/tests/invalid-serialization/err-7812.tolk @@ -0,0 +1,20 @@ +struct(0b000) B {} +struct C {} + +struct A { + multiple: (int32, B | C); +} + +fun main() { + var a: A = { multiple: (5, B{}) }; + return a.toCell(); +} + +/** +@compilation_should_fail +@stderr error: auto-serialization via toCell() is not available for type `A` +@stderr because field `A.multiple` of type `(int32, B | C)` can't be serialized +@stderr because element `tensor.1` of type `B | C` can't be serialized +@stderr because could not automatically generate serialization prefixes for a union +@stderr because struct `B` has opcode, but `C` does not + */ diff --git a/tolk-tester/tests/invalid-serialization/err-7813.tolk b/tolk-tester/tests/invalid-serialization/err-7813.tolk new file mode 100644 index 0000000000..720678cffb --- /dev/null +++ b/tolk-tester/tests/invalid-serialization/err-7813.tolk @@ -0,0 +1,13 @@ +struct (0x0F) A {} + +fun f(x: int32 | A) { + x.toCell(); +} + +/** +@compilation_should_fail +@stderr auto-serialization via toCell() is not available for type `int32 | A` +@stderr because could not automatically generate serialization prefixes for a union +@stderr because of mixing primitives and struct `A` with serialization prefix +@stderr hint: extract primitives to single-field structs and provide prefixes + */ diff --git a/tolk-tester/tests/invalid-serialization/err-7911.tolk b/tolk-tester/tests/invalid-serialization/err-7911.tolk new file mode 100644 index 0000000000..ab045dd400 --- /dev/null +++ b/tolk-tester/tests/invalid-serialization/err-7911.tolk @@ -0,0 +1,22 @@ +struct ExtraData { + owner: address; + lastTime: int; +} + +struct Storage { + more: Cell; +} + +fun main() { + Storage.fromSlice(""); +} + +/** +@compilation_should_fail +@stderr error: auto-serialization via fromSlice() is not available for type `Storage` +@stderr because field `Storage.more` of type `Cell` can't be serialized +@stderr because type `ExtraData` can't be serialized +@stderr because field `ExtraData.lastTime` of type `int` can't be serialized +@stderr because type `int` is not serializable, it doesn't define binary width +@stderr hint: replace `int` with `int32` / `uint64` / `coins` / etc. + */ diff --git a/tolk-tester/tests/invalid-symbol/err-2188.tolk b/tolk-tester/tests/invalid-symbol/err-2188.tolk index 6511fd40d2..4d0c66d3db 100644 --- a/tolk-tester/tests/invalid-symbol/err-2188.tolk +++ b/tolk-tester/tests/invalid-symbol/err-2188.tolk @@ -9,6 +9,6 @@ fun main() { /** @compilation_should_fail -@stderr method `storeUnexisting` not found for type `builder` +@stderr method `storeUnexisting` not found @stderr .storeUnexisting() */ diff --git a/tolk-tester/tests/invalid-symbol/err-2218.tolk b/tolk-tester/tests/invalid-symbol/err-2218.tolk new file mode 100644 index 0000000000..a9a1e78874 --- /dev/null +++ b/tolk-tester/tests/invalid-symbol/err-2218.tolk @@ -0,0 +1,13 @@ +fun demo(x: int) { + +} + +fun main() { + 10.demo(); +} + +/** +@compilation_should_fail +@stderr method `demo` not found, but there is a global function named `demo` +@stderr (a function should be called `foo(arg)`, not `arg.foo()`) + */ diff --git a/tolk-tester/tests/invalid-symbol/err-2770.tolk b/tolk-tester/tests/invalid-symbol/err-2770.tolk new file mode 100644 index 0000000000..06935c93d8 --- /dev/null +++ b/tolk-tester/tests/invalid-symbol/err-2770.tolk @@ -0,0 +1,14 @@ + +fun int.demo(self) { + +} + +fun main() { + (10 as int?).demo(); +} + +/** +@compilation_should_fail +@stderr method `demo` not found for type `int?` +@stderr (but it exists for type `int`) + */ diff --git a/tolk-tester/tests/invalid-symbol/err-2833.tolk b/tolk-tester/tests/invalid-symbol/err-2833.tolk new file mode 100644 index 0000000000..8a7d249ff7 --- /dev/null +++ b/tolk-tester/tests/invalid-symbol/err-2833.tolk @@ -0,0 +1,13 @@ +struct Point { + x: int8; + y: int8; +} + +fun main() { + return Point.getDeclaredPackPrefix(); +} + +/** +@compilation_should_fail +@stderr type `Point` does not have a serialization prefix + */ diff --git a/tolk-tester/tests/invalid-syntax/err-3208.tolk b/tolk-tester/tests/invalid-syntax/err-3208.tolk new file mode 100644 index 0000000000..75292b2529 --- /dev/null +++ b/tolk-tester/tests/invalid-syntax/err-3208.tolk @@ -0,0 +1,8 @@ +fun main() { + return address("0:gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg"); +} + +/** +@compilation_should_fail +@stderr invalid standard address + */ diff --git a/tolk-tester/tests/invalid-syntax/err-3357.tolk b/tolk-tester/tests/invalid-syntax/err-3357.tolk new file mode 100644 index 0000000000..de5569da32 --- /dev/null +++ b/tolk-tester/tests/invalid-syntax/err-3357.tolk @@ -0,0 +1,8 @@ +fun main() { + return ton("1000000000"); +} + +/** +@compilation_should_fail +@stderr argument is too big and leads to overflow + */ diff --git a/tolk-tester/tests/invalid-typing/err-6179.tolk b/tolk-tester/tests/invalid-typing/err-6179.tolk new file mode 100644 index 0000000000..73148590b2 --- /dev/null +++ b/tolk-tester/tests/invalid-typing/err-6179.tolk @@ -0,0 +1,7 @@ +fun f(x: slice = ton("0.04")) { +} + +/** +@compilation_should_fail +@stderr can not assign `coins` to `slice` + */ diff --git a/tolk-tester/tests/invalid-typing/err-6710.tolk b/tolk-tester/tests/invalid-typing/err-6710.tolk new file mode 100644 index 0000000000..d0fae1d56d --- /dev/null +++ b/tolk-tester/tests/invalid-typing/err-6710.tolk @@ -0,0 +1,15 @@ +fun f(s: slice) { + +} + +fun testCantPassAddressToSlice() { + var a = createAddressNone(); + f(a as slice); // ok + f(a); +} + +/** +@compilation_should_fail +@stderr can not pass `address` to `slice` +@stderr f(a); + */ diff --git a/tolk-tester/tests/invalid-typing/err-6928.tolk b/tolk-tester/tests/invalid-typing/err-6928.tolk new file mode 100644 index 0000000000..a335647ace --- /dev/null +++ b/tolk-tester/tests/invalid-typing/err-6928.tolk @@ -0,0 +1,7 @@ +fun int.f(self, v: (int, int) = (0, 1+"")) { +} + +/** +@compilation_should_fail +@stderr can not apply operator `+` to `int` and `slice` + */ diff --git a/tolk-tester/tests/pack-unpack-1.tolk b/tolk-tester/tests/pack-unpack-1.tolk new file mode 100644 index 0000000000..002df87ddc --- /dev/null +++ b/tolk-tester/tests/pack-unpack-1.tolk @@ -0,0 +1,130 @@ +struct JustInt32 { + value: int32; +} + +struct Point { + x: int32; + y: int32; +} + +@method_id(101) +fun test1(value: int) { + var t: JustInt32 = { value }; + var c = t.toCell(); + var t2 = c.load(); + __expect_type(c, "Cell"); + __expect_type(t2, "JustInt32"); + if (0) { + var rawCell: cell = c; // Cell is assignable to raw cell + contract.setData(t.toCell()); // (for this purpose, since we don't have overloads) + } + + var t3 = Cell.load(c); + var t4 = JustInt32.fromCell(c); + var t5 = JustInt32.fromSlice(c.beginParse()); + return (t2.value, t3.value, t4.value, t5.value); +} + +@method_id(102) +fun test2() { + var t: JustInt32 = { value: 10 }; + var c = t.toCell(); + var s = c.tvmCell.beginParse(); + __expect_type(s.skipAny(), "slice"); + s.assertEnd(); + return true; +} + +@method_id(103) +fun test3() { + var yy = 20; + var p: Point = { x: 10, y: yy }; + var c = p.toCell(); // p is constant, storing its fields are joined into a single STI + var s = c.beginParse(); + s.skipAny(); // skipping is also merged to skip 64 bits + return s.remainingBitsCount(); +} + +@method_id(104) +fun test4() { + var s = stringHexToSlice("000000010000000200000003"); + var p = Point.fromSlice(s, {assertEndAfterReading: false}); // does not mutate s + return (p, s.remainingBitsCount()); +} + +@method_id(105) +fun test5() { + var s = stringHexToSlice("000000010000000200000003"); + var p: Point = s.loadAny(); // mutates s + return (p, s.remainingBitsCount()); +} + +@method_id(106) +fun test6() { + var b = beginCell().storeInt(1, 32); + var p: Point = { x: 10, y: 20 }; + b.storeAny(p); + var s = b.endCell().beginParse(); + return (s.remainingBitsCount(), b.bitsCount(), Point.fromSlice(s.getLastBits(64)), s.remainingBitsCount()); +} + +// this function is used in serialization codegen, it's not exposed to stdlib; +// it constructs "x{...}" slices for SDBEGINSQ, used for opcode matching; +// here we write some unit tests and fif codegen tests for it +fun slice.tryStripPrefix(mutate self, prefix: int, prefixLen: int): bool + builtin; + +@method_id(107) +fun test7(s: slice) { + if (s.tryStripPrefix(0x10, 32)) { return (1, s.remainingBitsCount()); } + if (s.tryStripPrefix(0x40, 31)) { return (2, s.remainingBitsCount()); } + if (s.tryStripPrefix(0xFF18, 16)) { return (3, s.remainingBitsCount()); } + if (s.tryStripPrefix(0b1001, 8)) { return (4, s.remainingBitsCount()); } + if (s.tryStripPrefix(0b01, 3)) { return (5, s.remainingBitsCount()); } + return (6, s.remainingBitsCount()); +} + +fun main(c: cell) { + c as Cell; + (c as Cell) as cell; + (c as Cell) as cell?; + __expect_type((c as Cell?)!, "Cell"); + c = c as Cell; // Cell implicitly converts to cell +} + +/** +@testcase | 101 | 10 | 10 10 10 10 +@testcase | 102 | | -1 +@testcase | 103 | | 0 +@testcase | 104 | | 1 2 96 +@testcase | 105 | | 1 2 32 +@testcase | 106 | | 96 96 10 20 96 +@testcase | 107 | x{00000080} | 2 1 +@testcase | 107 | x{09332} | 4 12 +@testcase | 107 | x{2} | 5 1 +@testcase | 107 | x{0234} | 6 16 + +@fif_codegen +""" + test3 PROC:<{ + 20 PUSHINT // yy=20 + 10 PUSHINT // p.y=20 p.x=10 + NEWC // p.y=20 p.x=10 b + 32 STI // p.y=20 b + 32 STI // b + ENDC // c + CTOS // s + 32 PUSHINT // s '15=32 + SDSKIPFIRST // s + 32 PUSHINT // s '16=32 + SDSKIPFIRST // s + SBITS // '17 + }> +""" + +@fif_codegen x{00000010} SDBEGINSQ +@fif_codegen b{0000000000000000000000001000000} SDBEGINSQ +@fif_codegen x{ff18} SDBEGINSQ +@fif_codegen x{09} SDBEGINSQ +@fif_codegen b{001} SDBEGINSQ + */ diff --git a/tolk-tester/tests/pack-unpack-2.tolk b/tolk-tester/tests/pack-unpack-2.tolk new file mode 100644 index 0000000000..75f20a5fdd --- /dev/null +++ b/tolk-tester/tests/pack-unpack-2.tolk @@ -0,0 +1,682 @@ + +struct MaybeNothing {} +struct MaybeJust { value: T; } +type Maybe = MaybeNothing | MaybeJust; + +struct EitherLeft { value: T } +struct EitherRight { value: T } +type Either = EitherLeft | EitherRight; + +@inline +fun makeExternalAddress(hash: int, len: int): address { + return beginCell().storeUint(0b01, 2).storeUint(len, 9).storeUint(hash, len).endCell().beginParse() as address; +} + + +fun slice.assertEqDeeply(self, rhs: slice): slice { + var lhs = self; + assert(lhs.bitsEqual(rhs), 400); + assert(lhs.remainingRefsCount() == rhs.remainingRefsCount(), 400); + while (lhs.remainingRefsCount()) { + lhs.loadRef().beginParse().assertEqDeeply(rhs.loadRef().beginParse()); + } + return self; +} + +@inline +fun generateSlice_44_with_ref45(): slice { + return generateCell_44_with_ref45().beginParse(); +} + +@inline +fun generateCell_44_with_ref45(): cell { + return beginCell().storeInt(44, 32).storeRef(beginCell().storeInt(45, 32).endCell()).endCell(); +} + +fun assert_slice_is_44_and_ref45(s: slice) { + assert(s.loadInt(32) == 44, 400); + var ref = s.loadRef().beginParse(); + assert(ref.loadInt(32) == 45, 400); + ref.assertEnd(); + s.assertEnd(); +} + +@inline +fun slice.appendRef(self, refSlice: slice): slice { + return beginCell().storeSlice(self).storeRef(beginCell().storeSlice(refSlice).endCell()).endCell().beginParse(); +} + +fun run(input: TInputStruct, ans: slice) { + repeat (2) { + var s = input.toCell().beginParse(); + input = TInputStruct.fromSlice(s.assertEqDeeply(ans)); + } + input.toCell().beginParse().skipAny().assertEnd(); +} + + +/* + value:int32 + = JustInt32; +*/ + +struct JustInt32 { + value: int32; +} + +/* + value:(Maybe int32) + = JustMaybeInt32; + */ + +struct JustMaybeInt32 { + value: int32?; +} + +/* + op:int32 + amount:Grams + = TwoInts32AndCoins; +*/ + +struct TwoInts32AndCoins { + op: int32; + amount: coins; +} + +/* + op:int32 + query_id:uint64 + = TwoInts32And64; +*/ + +struct TwoInts32And64 { + op: int32; + query_id: uint64; +} + +/* + op:int32 + query_id_ref: ^[uint64] + = TwoInts32AndRef64; +*/ + +struct TwoInts32AndRef64 { + op: int32; + query_id_ref: Cell; +} + +/* + op:int32 + query_id:(Maybe uint64) + demo_bool_field:Bool + = TwoInts32AndMaybe64; +*/ + +struct TwoInts32AndMaybe64 { + op: int32; + query_id: uint64?; + demo_bool_field: bool; +} + +/* + addr:MsgAddressInt + = JustAddress; +*/ + +struct JustAddress { + addr: address; +} + +/* + op:int32 + addr:MsgAddressExt + query_id:uint64 + = TwoInts32And64SepByAddress; +*/ + +struct TwoInts32And64SepByAddress { + op: int32; + addr_e: address; + query_id: uint64; +} + +/* + op:int32 + i8or256:(Either int8 int256) + = IntAndEitherInt8Or256; +*/ + +struct IntAndEitherInt8Or256 { + op: int32; + i8or256: int8 | int256; +} + +/* + query_id_ref:uint64 = Inner1; + i64_in_ref:int64 = Inner2; + + op:int32 + i32orRef:(Either int32 ^Inner2) + query_id_maybe_ref: (Maybe ^Inner1) + = IntAndEither32OrRef64; +*/ + +struct Inner1 { + query_id_ref: uint64; +} +struct Inner2 { + i64_in_ref: int64; +} + +struct IntAndEither32OrRef64 { + op: int32; + i32orRef: int32 | Cell; + query_id_maybe_ref: Cell?; +} + +/* + value:(Either int8 (Maybe int256)) + op:int32 + = IntAndEither8OrMaybe256; +*/ + +struct IntAndEither8OrMaybe256 { + value: Either; + op: int32; +} + +/* + value:(Either (Maybe int8) int256) + op:int32 + = IntAndEitherMaybe8Or256; +*/ + +struct IntAndEitherMaybe8Or256 { + value: Either; + op: int32; +} + +/* + value:(Maybe (Maybe int8)) + op:int32 + = IntAndMaybeMaybe8; +*/ + +struct IntAndMaybeMaybe8 { + value: Maybe>; + op: int32; +} + +/* + f1:bits8 + f2:bits3 + f3:(Maybe bits20) + f4:(Either bits100 bits200) + = SomeBytesFields; +*/ + +struct SomeBytesFields { + f1: bytes1; + f2: bits3; + f3: bits20?; + f4: bits100 | bits200; +} + +/* + op:int32 + rest:Cell + = IntAndRestInlineCell; +*/ + +struct IntAndRestInlineCell { + op: int32; + rest: RemainingBitsAndRefs; +} + +/* + op:int32 + rest:^Cell + = IntAndRestRefCell; +*/ + +struct IntAndRestRefCell { + op: int32; + rest: cell; +} + +/* + op:int32 + rest:(Either Cell ^Cell) + = IntAndRestEitherCellOrRefCell; +*/ + +struct IntAndRestEitherCellOrRefCell { + op: int32; + rest: RemainingBitsAndRefs | cell; +} + +/* + op:int32 + ref1m:(Maybe ^Cell) + ref2m:(Maybe ^Cell) + ref3:^Cell + ref4m32:(Maybe ^JustInt32) + query_id:int64 + = DifferentMaybeRefs; + */ + +struct DifferentMaybeRefs { + op: int32; + ref1m: cell?; + ref2m: dict; + ref3: cell; + ref4m32: Cell?; + query_id: int64; +} + +/* + ji:JustInt32 + jmi:JustMaybeInt32 + jiMaybe:(Maybe JustInt32) + jmiMaybe:(Maybe JustMaybeInt32) + = DifferentIntsWithMaybe; +*/ + +struct DifferentIntsWithMaybe { + ji: JustInt32; + jmi: JustMaybeInt32; + jiMaybe: JustInt32?; + jmiMaybe: JustMaybeInt32?; +} + +/* + ja1:JustAddress + ja2m:(Maybe JustAddress) + imm:IntAndMaybeMaybe8 + tis:TwoInts32And64SepByAddress + = DifferentMix1; +*/ + +@overflow1023_policy("suppress") +struct DifferentMix1 { + ja1: JustAddress; + ja2m: JustAddress?; + ext_nn: address; + imm: IntAndMaybeMaybe8; + tis: TwoInts32And64SepByAddress; +} + +/* + iae:^IntAndEither32OrRef64 + tic:TwoInts32AndCoins + rest:Cell + = DifferentMix2; +*/ + +struct DifferentMix2 { + iae: Cell; + tic: TwoInts32AndCoins; + rest: RemainingBitsAndRefs; +} + +/* + bod:(Either ^TwoInts32AndCoins ^JustInt32) + tim:(Maybe TwoInts32AndCoins) + pairm:(Maybe (Both int32 int64)) + = DifferentMix3; + */ + +struct DifferentMix3 { + bod: Cell | Cell; + tim: TwoInts32AndCoins?; + pairm: (int32, int64)?; +} + +/* + Test: write with builder, read with other struct + (type `builder` is available for writing, but not for reading) + */ + +struct WriteWithBuilder { + f1: int32; + rest: builder; +} + +struct ReadWrittenWithBuilder { + f1: int32; + someInt: uint32; + someCell: cell?; +} + +struct ReadWriteRest { + f1: int32; + f2: coins; + rest: T; +} + +struct Tail224 { + ji: JustInt32, + addr: address, + ref1: Cell?, + ref2: Cell, +} + +type ReadRest_Remaining = ReadWriteRest; + +struct ReadWriteMid { + f1: int32; + mid: T; + f3: coins; +} + + + +// --------------------------------------------- + + +@method_id(200) +fun test_JustInt32_1() { + var b = beginCell(); + b.storeAny(JustInt32{ value: 123 }); + b.storeAny(JustInt32{ value: 456 }); + return b; +} + +@method_id(201) +fun test_JustInt32() { + run({ value: 255}, stringHexToSlice("000000FF")); + var s = stringHexToSlice("0000007b000001c8"); + return (s.loadAny(), s.loadAny()); +} + +@method_id(202) +fun test_JustMaybeInt32() { + run({ value: 255 }, stringHexToSlice("8000007FC_")); + run({ value: null }, stringHexToSlice("4_")); + return true; +} + +@method_id(203) +fun test_TwoInts32AndCoins() { + run({ op: 123, amount: 0 }, stringHexToSlice("0000007B0")); + run({ op: 123, amount: 1000000000 }, stringHexToSlice("0000007B43B9ACA00")); + return true; +} + +@method_id(204) +fun test_TwoInts32And64() { + run({ op: 123, query_id: 255 }, stringHexToSlice("0000007B00000000000000FF")); + return true; +} + +@method_id(205) +fun test_TwoInts32AndRef64() { + run({ op: 123, query_id_ref: { tvmCell: beginCell().storeUint(255,64).endCell() } }, stringHexToSlice("0000007B").appendRef(stringHexToSlice("00000000000000FF"))); + return true; +} + +@method_id(206) +fun test_TwoInts32AndMaybe64() { + run({ op: 123, query_id: 255, demo_bool_field: true }, stringHexToSlice("0000007B800000000000007FE_")); + run({ op: 123, query_id: null, demo_bool_field: true }, stringHexToSlice("0000007B6_")); + run({ op: 123, query_id: null, demo_bool_field: false }, stringHexToSlice("0000007B2_")); + return true; +} + +@method_id(207) +fun test_JustAddress() { + run({ addr: address("0:ca6e321c7cce9ecedf0a8ca2492ec8592494aa5fb5ce0387dff96ef6af982a3e") }, stringHexToSlice("80194DC6438F99D3D9DBE151944925D90B2492954BF6B9C070FBFF2DDED5F30547D_")); + return true; +} + +@method_id(208) +fun test_TwoInts32And64SepByAddress() { + run({ op: 123, addr_e: makeExternalAddress(70, 10), query_id: 255 } , stringHexToSlice("0000007B41423000000000000007FC_")); + run({ op: 123, addr_e: makeExternalAddress(666, 20), query_id: 254 } , stringHexToSlice("0000007B4280053400000000000001FD_")); + run({ op: 123, addr_e: createAddressNone(), query_id: 253 } , stringHexToSlice("0000007B000000000000003F6_")); + return true; +} + +@method_id(209) +fun test_IntAndEitherInt8Or256() { + run({ op: 123, i8or256: 80 as int8 }, stringHexToSlice("0000007B284_")); + run({ op: 123, i8or256: 65535 as int256 }, stringHexToSlice("0000007B8000000000000000000000000000000000000000000000000000000000007FFFC_")); + return true; +} + +@method_id(210) +fun test_IntAndEither32OrRef64() { + run({ op: 123, i32orRef: Inner2{ i64_in_ref: 555 }.toCell(), query_id_maybe_ref: Inner1{ query_id_ref: 888 }.toCell() }, stringHexToSlice("0000007BE_").appendRef(stringHexToSlice("000000000000022B")).appendRef(stringHexToSlice("0000000000000378"))); + run({ op: 123, i32orRef: Inner2{ i64_in_ref: 555 }.toCell(), query_id_maybe_ref: null }, stringHexToSlice("0000007BA_").appendRef(stringHexToSlice("000000000000022B"))); + run({ op: 123, i32orRef: 555, query_id_maybe_ref: Inner1{ query_id_ref: 888 }.toCell() }, stringHexToSlice("0000007B00000115E_").appendRef(stringHexToSlice("0000000000000378"))); + run({ op: 123, i32orRef: 555, query_id_maybe_ref: null }, stringHexToSlice("0000007B00000115A_")); + return IntAndEither32OrRef64.fromSlice(stringHexToSlice("0000007B00000115A_")); +} + +@method_id(211) +fun test_IntAndEither8OrMaybe256() { + run({ value: EitherLeft { value: 100 }, op: 123 }, stringHexToSlice("320000003DC_")); + run({ value: EitherRight { value: 10000 }, op: 123 }, stringHexToSlice("C0000000000000000000000000000000000000000000000000000000000009C40000001EE_")); + run({ value: EitherRight { value: null }, op: 123 }, stringHexToSlice("8000001EE_")); + return IntAndEither8OrMaybe256.fromSlice(stringHexToSlice("8000001EE_")); +} + +@method_id(212) +fun test_IntAndEitherMaybe8Or256() { + run({ value: EitherLeft { value: 100 }, op: 123 }, stringHexToSlice("590000001EE_")); + run({ value: EitherRight { value: 10000 }, op: 123 }, stringHexToSlice("80000000000000000000000000000000000000000000000000000000000013880000003DC_")); + run({ value: EitherLeft { value: null }, op: 123 }, stringHexToSlice("0000001EE_")); + return IntAndEitherMaybe8Or256.fromSlice(stringHexToSlice("0000001EE_")); +} + +@method_id(213) +fun test_SomeBytesFields() { + run({ f1: stringHexToSlice("A4") as bytes1, f2: stringHexToSlice("7_") as bits3, f3: null, f4: stringHexToSlice("BBA87684B3DAA58C0FCC75230C4302C9D156102139D631FF56") as bits200 }, stringHexToSlice("A46DDD43B4259ED52C607E63A9186218164E8AB08109CEB18FFAB4_")); + run({ f1: stringHexToSlice("E6") as bytes1, f2: stringHexToSlice("D_") as bits3, f3: stringHexToSlice("2531C") as bits20, f4: stringHexToSlice("927E88FAB2D327D9468547217") as bits100 }, stringHexToSlice("E6D2531C493F447D596993ECA342A390BC_")); + return SomeBytesFields.fromSlice(stringHexToSlice("E6D2531C493F447D596993ECA342A390BC_")).f4 is bits100; +} + +@method_id(214) +fun test_IntAndMaybeMaybe8() { + run({ value: MaybeJust { value: MaybeJust { value: 88 } }, op: 123 }, stringHexToSlice("D60000001EE_")); + run({ value: MaybeJust { value: MaybeNothing{} }, op: 123 }, stringHexToSlice("8000001EE_")); + + val t1 = IntAndMaybeMaybe8.fromSlice(stringHexToSlice("D60000001EE_")); // (88) 123 + val v1 = t1.value; + __expect_type(v1, "Maybe>"); + assert(v1 is MaybeJust && v1.value is MaybeJust && v1.value.value == 88 && t1.op == 123, 400); + __expect_type(v1, "MaybeJust>"); + __expect_type(v1.value, "MaybeJust"); + __expect_type(v1.value.value, "int8"); + val t2 = IntAndMaybeMaybe8.fromSlice(stringHexToSlice("8000001EE_")); // (()) 123 + val v2 = t2.value; + assert(v2 is MaybeJust && v2.value is MaybeNothing && t2.op == 123, 400); + __expect_type(v2, "MaybeJust>"); + __expect_type(v2.value, "MaybeNothing"); + + return true; +} + +@method_id(215) +fun test_IntAndRestInlineCell() { + run({ op: 123, rest: generateSlice_44_with_ref45() }, stringHexToSlice("0000007B0000002C").appendRef(stringHexToSlice("0000002D"))); + return true; +} + +@method_id(216) +fun test_IntAndRestRefCell() { + run({ op: 123, rest: generateCell_44_with_ref45() }, stringHexToSlice("0000007B").appendRef(stringHexToSlice("0000002C").appendRef(stringHexToSlice("0000002D")))); + return true; +} + +@method_id(217) +fun test_IntAndRestEitherCellOrRefCell() { + val input1: IntAndRestEitherCellOrRefCell = { op: 123, rest: generateCell_44_with_ref45() }; + var s1 = input1.toCell().beginParse(); + s1.assertEqDeeply(stringHexToSlice("0000007BC_").appendRef(stringHexToSlice("0000002C").appendRef(stringHexToSlice("0000002D")))); + match (val rest = IntAndRestEitherCellOrRefCell.fromSlice(s1).rest) { + cell => assert_slice_is_44_and_ref45(rest.beginParse()), + RemainingBitsAndRefs => assert_slice_is_44_and_ref45(rest) + } + + val input2: IntAndRestEitherCellOrRefCell = { op: 123, rest: generateSlice_44_with_ref45() }; + var s2 = input2.toCell().beginParse(); + s2.assertEqDeeply(stringHexToSlice("0000007B000000164_").appendRef(stringHexToSlice("0000002D"))); + match (val rest = IntAndRestEitherCellOrRefCell.fromSlice(s2).rest) { + cell => assert_slice_is_44_and_ref45(rest.beginParse()), + RemainingBitsAndRefs => assert_slice_is_44_and_ref45(rest) + } + + return true; +} + +@method_id(218) +fun test_DifferentMaybeRefs() { + run({ op: 123, ref1m: null, ref2m: null, ref3: generateCell_44_with_ref45(), ref4m32: null, query_id: 456}, stringHexToSlice("0000007B00000000000000391_").appendRef(stringHexToSlice("0000002C").appendRef(stringHexToSlice("0000002D")))); + run({ op: 123, ref1m: generateCell_44_with_ref45(), ref2m: null, ref3: generateCell_44_with_ref45(), ref4m32: JustInt32{value: 999}.toCell(), query_id: 456}, stringHexToSlice("0000007BA0000000000000391_").appendRef(stringHexToSlice("0000002C").appendRef(stringHexToSlice("0000002D"))).appendRef(stringHexToSlice("0000002C").appendRef(stringHexToSlice("0000002D"))).appendRef(stringHexToSlice("000003E7"))); + run({ op: 123, ref1m: null, ref2m: beginCell().endCell(), ref3: generateCell_44_with_ref45(), ref4m32: JustInt32{value: 998}.toCell(), query_id: 456}, stringHexToSlice("0000007B60000000000000391_").appendRef("").appendRef(stringHexToSlice("0000002C").appendRef(stringHexToSlice("0000002D"))).appendRef(stringHexToSlice("000003E6"))); + run({ op: 123, ref1m: generateCell_44_with_ref45(), ref2m: generateCell_44_with_ref45(), ref3: beginCell().endCell(), ref4m32: null, query_id: 456}, stringHexToSlice("0000007BC0000000000000391_").appendRef(stringHexToSlice("0000002C").appendRef(stringHexToSlice("0000002D"))).appendRef(stringHexToSlice("0000002C").appendRef(stringHexToSlice("0000002D"))).appendRef("")); + run({ op: 123, ref1m: generateCell_44_with_ref45(), ref2m: beginCell().endCell(), ref3: generateCell_44_with_ref45(), ref4m32: JustInt32{value: 997}.toCell(), query_id: 456}, stringHexToSlice("0000007BE0000000000000391_").appendRef(stringHexToSlice("0000002C").appendRef(stringHexToSlice("0000002D"))).appendRef("").appendRef(stringHexToSlice("0000002C").appendRef(stringHexToSlice("0000002D"))).appendRef(stringHexToSlice("000003E5"))); + return true; +} + +@method_id(219) +fun test_DifferentIntsWithMaybe() { + run({ ji: { value: 44 }, jmi: { value: 45 }, jiMaybe: null, jmiMaybe: null }, stringHexToSlice("0000002C800000169_")); + run({ ji: { value: 44 }, jmi: { value: null }, jiMaybe: { value: 45 }, jmiMaybe: { value: null } }, stringHexToSlice("0000002C4000000B6")); + run({ ji: { value: 44 }, jmi: { value: null }, jiMaybe: null, jmiMaybe: { value: 46 } }, stringHexToSlice("0000002C30000002E")); + return DifferentIntsWithMaybe.fromSlice(stringHexToSlice("0000002C30000002E")); +} + +@method_id(220) +fun test_DifferentMix1() { + run({ ja1: { addr: address("EQCRDM9h4k3UJdOePPuyX40mCgA4vxge5Dc5vjBR8djbEKC5") }, ja2m: { addr: address("EQCRDM9h4k3UJdOePPuyX40mCgA4vxge5Dc5vjBR8djbEKC5") }, ext_nn: makeExternalAddress(1234,30), imm: { op: 78, value: MaybeJust { value: MaybeJust { value: 78 } } }, tis: { op: 123, query_id: 889128, addr_e: makeExternalAddress(1234, 80) } }, + stringHexToSlice("80122199EC3C49BA84BA73C79F764BF1A4C1400717E303DC86E737C60A3E3B1B62180122199EC3C49BA84BA73C79F764BF1A4C1400717E303DC86E737C60A3E3B1B62087800004D2D3800000138000001ED2800000000000000000269000000000006C8944_")); + run({ ja1: { addr: address("EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c") }, ja2m: null, ext_nn: makeExternalAddress(0,3), imm: { op: 99, value: MaybeJust { value: MaybeJust { value: 99 } } }, tis: { op: 1234, query_id: 889129, addr_e: createAddressNone() } }, + stringHexToSlice("800000000000000000000000000000000000000000000000000000000000000000040636300000063000004D2000000000003644A6_")); + + val o = DifferentMix1.fromSlice(stringHexToSlice("800000000000000000000000000000000000000000000000000000000000000000040636300000063000004D2000000000003644A6_")); + return (o.ja1.addr.getWorkchainAndHash(), o.ja2m, o.imm, o.tis.op, o.tis.addr_e.isNone() ? null : 0, o.tis.query_id, (o.ext_nn as slice).remainingBitsCount()); +} + +@method_id(221) +fun test_DifferentMix2() { + run({ iae: IntAndEither32OrRef64{ op: 777, i32orRef: 2983, query_id_maybe_ref: null }.toCell(), tic: { op: 123, amount: 829290000 }, rest: generateSlice_44_with_ref45() }, + stringHexToSlice("0000007B4316DF6100000002C").appendRef(stringHexToSlice("00000309000005D3A_")).appendRef(stringHexToSlice("0000002D"))); + val r1 = DifferentMix2.fromSlice(stringHexToSlice("0000007B4316DF6100000002C").appendRef(stringHexToSlice("00000309000005D3A_")).appendRef(stringHexToSlice("0000002D"))); + val iae1 = r1.iae.load(); + assert(iae1.i32orRef is int32, 400); + assert(iae1.i32orRef == 2983, 400); + assert(iae1.query_id_maybe_ref == null, 400); + assert_slice_is_44_and_ref45(r1.rest); + + run({ iae: { tvmCell: IntAndEither32OrRef64{ op: 778, i32orRef: { tvmCell: Inner2{ i64_in_ref: 9919992 }.toCell() }, query_id_maybe_ref: Inner1{ query_id_ref: 889477 }.toCell() }.toCell() }, tic: { op: 123, amount: 500000 }, rest: beginCell().endCell().beginParse() }, + stringHexToSlice("0000007B307A120").appendRef(stringHexToSlice("0000030AE_").appendRef(stringHexToSlice("0000000000975DF8")).appendRef(stringHexToSlice("00000000000D9285")))); + var r2 = DifferentMix2.fromSlice(stringHexToSlice("0000007B307A120").appendRef(stringHexToSlice("0000030AE_").appendRef(stringHexToSlice("0000000000975DF8")).appendRef(stringHexToSlice("00000000000D9285")))); + val iae2 = r2.iae.load(); + assert(iae2.i32orRef is Cell, 400); + __expect_type(iae2.i32orRef.load(), "Inner2"); + assert(iae2.i32orRef.load().i64_in_ref == 9919992, 400); + assert(iae2.query_id_maybe_ref != null, 400); + __expect_type(iae2.query_id_maybe_ref.load(), "Inner1"); + assert(iae2.query_id_maybe_ref.load().query_id_ref == 889477, 400); + assert(r2.tic.amount == 500000, 400); + r2.rest.assertEnd(); + + return true; +} + +@method_id(222) +fun test_DifferentMix3() { + run({ bod: TwoInts32AndCoins{op:123, amount:80000}.toCell(), tim: {op: 456, amount:0}, pairm: null }, stringHexToSlice("4000007201_").appendRef(stringHexToSlice("0000007B3013880"))); + run({ bod: TwoInts32AndCoins{op:124, amount:10}.toCell(), tim: null, pairm: (100000,100000) }, stringHexToSlice("200030D400000000000030D41_").appendRef(stringHexToSlice("0000007C10A"))); + run({ bod: JustInt32{value:255}.toCell(), tim: null, pairm: (90,90) }, stringHexToSlice("A000000B400000000000000B5_").appendRef(stringHexToSlice("000000FF"))); + run({ bod: JustInt32{value:510}.toCell(), tim: {op: 567, amount:9392843922}, pairm: (81923,81923) }, stringHexToSlice("C000008DD408BF6DB24A000280060000000000028007_").appendRef(stringHexToSlice("000001FE"))); + + val o4 = DifferentMix3.fromSlice(stringHexToSlice("C000008DD408BF6DB24A000280060000000000028007_").appendRef(stringHexToSlice("000001FE"))); + val o2 = DifferentMix3.fromSlice(stringHexToSlice("200030D400000000000030D41_").appendRef(stringHexToSlice("0000007C10A"))); + return ( + (o4.bod is Cell) ? o4.bod.load().value as int : -1, o4.tim, o4.pairm, + 777, + o2.bod is Cell, o2.bod is Cell, o2.tim, o2.pairm, + ); +} + +@method_id(223) +fun test_WriteWithBuilderReadWithOther() { + var b = beginCell().storeUint(55, 32).storeMaybeRef(null); + var w: WriteWithBuilder = { f1: 10, rest: b }; + return ReadWrittenWithBuilder.fromCell(w.toCell()); +} + +@method_id(224) +fun test_RestIsBuilderOrRemaining() { + var remainingB = beginCell() + .storeUint(5, 32) + .storeAddress(address("9:527964d55cfa6eb731f4bfc07e9d025098097ef8505519e853986279bd8400d8")) + .storeMaybeRef(JustInt32 { value: 123 }.toCell()) + .storeRef(JustAddress { addr: createAddressNone() }.toCell()); + var w: ReadWriteRest = { + f1: 60, + f2: ton("0.05"), + rest: remainingB + }; + var r: ReadRest_Remaining = w.toCell().beginParse().loadAny(); + __expect_type(r.rest, "RemainingBitsAndRefs"); + var rest = Tail224.fromSlice(r.rest); + return (r.f1, r.f2, rest.ji.value, rest.addr.getWorkchain(), rest.ref1!.load(), rest.ref2.load().addr.isNone()); +} + +@method_id(225) +fun test_MidIsBuilderOrBitsN() { + var bits40_b = beginCell().storeSlice(stringHexToSlice("0000FFFF00")); + var typedCell = ReadWriteMid { + f1: 5, + mid: bits40_b, + f3: ton("0.05") + }.toCell(); + __expect_type(typedCell, "Cell>"); + var r = ReadWriteMid.fromCell(typedCell); + var mid = r.mid as slice; + return (r.f1, mid.remainingBitsCount(), mid.loadAny(), mid.remainingBitsCount(), r.f3); +} + + +fun main() { + var t: JustInt32 = { value: 10 }; + var c = t.toCell(); + var t2 = c.load(); + var t3 = JustInt32.fromSlice(c.beginParse()); + return (t2.value, t3.value); +} + +/** +@testcase | 0 | | 10 10 +@testcase | 200 | | BC{00100000007b000001c8} +@testcase | 201 | | 123 456 +@testcase | 202 | | -1 +@testcase | 203 | | -1 +@testcase | 204 | | -1 +@testcase | 205 | | -1 +@testcase | 206 | | -1 +@testcase | 207 | | -1 +@testcase | 208 | | -1 +@testcase | 209 | | -1 +@testcase | 210 | | 123 555 46 (null) +@testcase | 211 | | (null) 132 123 +@testcase | 212 | | (null) 133 123 +@testcase | 213 | | -1 +@testcase | 214 | | -1 +@testcase | 215 | | -1 +@testcase | 216 | | -1 +@testcase | 217 | | -1 +@testcase | 218 | | -1 +@testcase | 219 | | 44 (null) (null) 46 143 +@testcase | 220 | | 0 0 (null) 99 136 137 99 1234 (null) 889129 14 +@testcase | 221 | | -1 +@testcase | 222 | | 510 567 9392843922 146 81923 81923 147 777 0 -1 (null) (null) 0 100000 100000 147 +@testcase | 223 | | 10 55 (null) +@testcase | 224 | | 60 50000000 5 9 123 -1 +@testcase | 225 | | 5 40 65535 8 50000000 + */ diff --git a/tolk-tester/tests/pack-unpack-3.tolk b/tolk-tester/tests/pack-unpack-3.tolk new file mode 100644 index 0000000000..74fa1b8e92 --- /dev/null +++ b/tolk-tester/tests/pack-unpack-3.tolk @@ -0,0 +1,284 @@ + +struct EitherLeft { value: T } +struct EitherRight { value: T } +type Either = EitherLeft | EitherRight; + +@inline +fun makeExternalAddress(hash: int, len: int): address { + return beginCell().storeUint(0b01, 2).storeUint(len, 9).storeUint(hash, len).endCell().beginParse() as address; +} + + +fun slice.assertEqDeeply(self, rhs: slice): slice { + var lhs = self; + assert(lhs.bitsEqual(rhs), 400); + assert(lhs.remainingRefsCount() == rhs.remainingRefsCount(), 400); + while (lhs.remainingRefsCount()) { + lhs.loadRef().beginParse().assertEqDeeply(rhs.loadRef().beginParse()); + } + return self; +} + +@inline +fun slice.appendRef(self, refSlice: slice): slice { + return beginCell().storeSlice(self).storeRef(beginCell().storeSlice(refSlice).endCell()).endCell().beginParse(); +} + +@inline +fun generateSlice_44_with_ref45(): slice { + return generateCell_44_with_ref45().beginParse(); +} + +@inline +fun generateCell_44_with_ref45(): cell { + return beginCell().storeInt(44, 32).storeRef(beginCell().storeInt(45, 32).endCell()).endCell(); +} + + +fun run(input: TInputStruct, ans: slice) { + repeat (2) { + var s = input.toCell().beginParse(); + input = TInputStruct.fromSlice(s.assertEqDeeply(ans)); + } + input.toCell().beginParse().skipAny().assertEnd(); +} + + +/* +single_prefix32#87654321 + amount1:Grams + amount2:Grams + = MsgSinglePrefix32; +*/ + +struct(0x87654321) MsgSinglePrefix32 { + amount1: coins; + amount2: coins; +} + +/* +single_prefix48#876543211234 + amount:(Either Grams uint64) + = MsgSinglePrefix48; +*/ + +struct(0x876543211234) MsgSinglePrefix48 { + amount: coins | uint64; +} + +/* +counterIncrement#12345678 + counter_id:int8 + inc_by:int32 + = MsgCounter1; +*/ + +struct(0x12345678) CounterIncrement { + counter_id: int8; + inc_by: int32; +} + +/* +counterDecrement#23456789 + counter_id:int8 + dec_by:int32 + = MsgCounter1; +*/ + +struct(0x23456789) CounterDecrement { + counter_id: int8; + dec_by: int32; +} + +/* +counterReset0#34567890 + counter_id:int8 + = MsgCounter1; +*/ + +struct(0x34567890) CounterReset0 { + counter_id: int8; +} + +/* +counterResetTo#00184300 + counter_id:int8 + initial_value:int64 + = MsgCounter1; +*/ + +struct(0x00184300) CounterResetTo { + counter_id: int8; + initial_value: int64; +} + +type MsgCounter1 = + CounterIncrement | + CounterDecrement | + CounterReset0 | + CounterResetTo | +; + +/* +bodyPayload1$001 + should_forward:Bool + n_times:int32 + content:Cell + = BodyPayload; +*/ + +struct(0b001) BodyPayload1 { + should_forward: bool; + n_times: int32; + content: RemainingBitsAndRefs; +} + +/* +bodyPayload2$01 + master_id:int8 + owner_address:MsgAddressInt + = BodyPayload; +*/ + +struct(0b01) BodyPayload2 { + master_id: int8; + owner_address: address; +} + +type BodyPayload = BodyPayload1 | BodyPayload2; + +/* +sayHiAndGoodbye#89 + dest_addr:(Maybe MsgAddressInt) + body:BodyPayload + = MsgExternal1; +*/ + +struct(0x89) SayHiAndGoodbye { + dest_addr: address?; + body: BodyPayload; +} + +/* +sayStoreInChain#0013 + in_masterchain:Bool + contents:^BodyPayload + = MsgExternal1; + */ + +struct(0x0013) SayStoreInChain { + in_masterchain: bool; + contents: Cell; +} + +type MsgExternal1 = SayHiAndGoodbye | SayStoreInChain; + +/* +transferParams1#794 + dest_int:MsgAddressInt + amount:Grams + dest_ext:MsgAddressExt + = TransferParams; +*/ + +struct(0x794) TransferParams1 { + dest_int: address; + amount: coins; + dest_ext: address; +} + +/* +transferParams2#9 + intVector:(Both int32 (Both (Maybe Grams) uint64)) + needs_more:^Bit + = TransferParams; + */ + +struct(0x9) TransferParams2 { + intVector: (int32, coins?, uint64); + needs_more: Cell; +} + +type TransferParams = TransferParams1 | TransferParams2; + +/* +_#FB3701FF + params:(Either TransferParams ^TransferParams) + = MsgTransfer; + */ + +struct(0xFB3701FF) MsgTransfer { + params: Either>; +} + + + +// --------------------------------------------- + + +@method_id(201) +fun test_MsgSinglePrefix32() { + run({ amount1: 80, amount2: 800000000 }, stringHexToSlice("8765432115042FAF0800")); + + return MsgSinglePrefix32.fromSlice(stringHexToSlice("8765432115042FAF0800")); +} + +@method_id(202) +fun test_MsgSinglePrefix48() { + run({ amount: 80 as uint64 }, stringHexToSlice("87654321123480000000000000284_")); + run({ amount: 800000000 as coins }, stringHexToSlice("876543211234217D784004_")); + + var o = MsgSinglePrefix48.fromSlice(stringHexToSlice("876543211234217D784004_")); + return (o, 777, o.amount is coins, o.amount is uint64); +} + +@method_id(203) +fun test_MsgCounter1() { + run(CounterIncrement{ counter_id: 123, inc_by: 78 }, stringHexToSlice("123456787B0000004E")); + run(CounterDecrement{ counter_id: 0, dec_by: -38 }, stringHexToSlice("2345678900FFFFFFDA")); + run(CounterReset0{ counter_id: 0 }, stringHexToSlice("3456789000")); + run(CounterResetTo{ counter_id: 0, initial_value: 29874329774732 }, stringHexToSlice("001843000000001B2BA8D06A8C")); + + val o = MsgCounter1.fromSlice(stringHexToSlice("3456789000")); + return (o, 777, o is CounterReset0, o is CounterIncrement); +} + +@method_id(204) +fun test_MsgExternal1() { + run(SayHiAndGoodbye{ dest_addr: null, body: BodyPayload2 { master_id: 10, owner_address: address("EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c") } }, stringHexToSlice("892150000000000000000000000000000000000000000000000000000000000000000002_")); + run(SayHiAndGoodbye{ dest_addr: address("EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c"), body: BodyPayload1 { should_forward: false, n_times: 85, content: generateSlice_44_with_ref45() } }, stringHexToSlice("89C0000000000000000000000000000000000000000000000000000000000000000002000000550000002C").appendRef(stringHexToSlice("0000002D"))); + run(SayHiAndGoodbye{ dest_addr: address("Ef8o6AM9sUZ8rOqLFY8PYeaC3gbopZR1BMkE8fcD0r5NnmCi"), body: BodyPayload2 { master_id: -5, owner_address: address("Ef8o6AM9sUZ8rOqLFY8PYeaC3gbopZR1BMkE8fcD0r5NnmCi") } }, stringHexToSlice("89CFF28E8033DB1467CACEA8B158F0F61E682DE06E8A5947504C904F1F703D2BE4D9E7EE7F9474019ED8A33E5675458AC787B0F3416F037452CA3A82648278FB81E95F26CF4_")); + run(SayStoreInChain{ in_masterchain: true, contents: { tvmCell: BodyPayload1{ should_forward: true, n_times: 20, content: generateSlice_44_with_ref45() }.toCell()} }, stringHexToSlice("0013C_").appendRef(stringHexToSlice("3000000140000002C").appendRef(stringHexToSlice("0000002D")))); + run(SayStoreInChain{ in_masterchain: false, contents: { tvmCell: BodyPayload2{ master_id: 37, owner_address: address(("EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c")) }.toCell()} }, stringHexToSlice("00134_").appendRef(stringHexToSlice("4960000000000000000000000000000000000000000000000000000000000000000004_"))); + + val o = MsgExternal1.fromSlice(stringHexToSlice("00134_").appendRef(stringHexToSlice("4960000000000000000000000000000000000000000000000000000000000000000004_"))); + assert(o is SayStoreInChain, 400); + val contents = o.contents.load(); + return (contents is BodyPayload2 && contents.owner_address == address("EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c"), contents is BodyPayload1, contents is BodyPayload2); +} + +@method_id(205) +fun test_MsgTransfer() { + run({ params: EitherLeft { value: TransferParams1 { dest_int: address("Ef8o6AM9sUZ8rOqLFY8PYeaC3gbopZR1BMkE8fcD0r5NnmCi"), amount: 80000000, dest_ext: makeExternalAddress(1234,80) } } }, stringHexToSlice("FB3701FF3CA4FF28E8033DB1467CACEA8B158F0F61E682DE06E8A5947504C904F1F703D2BE4D9E404C4B4004A0000000000000000009A5_")); + run({ params: EitherLeft { value: TransferParams2 { intVector: (123, 1234567890123456, 1234567890123456), needs_more: {tvmCell: beginCell().storeBool(true).endCell()} } } }, stringHexToSlice("FB3701FF48000003DDC118B54F22AEB0000118B54F22AEB02_").appendRef(stringHexToSlice("C_"))); + run({ params: EitherRight { value: { tvmCell: TransferParams1{ dest_int: address("EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c"), amount: 80000000, dest_ext: makeExternalAddress(1234,70) }.toCell()} } }, stringHexToSlice("FB3701FFC_").appendRef(stringHexToSlice("7948000000000000000000000000000000000000000000000000000000000000000000809896800918000000000000004D2"))); + run({ params: EitherRight { value: { tvmCell: TransferParams2{ intVector: (123, null, 0), needs_more: {tvmCell: beginCell().storeBool(false).endCell()} }.toCell()} } }, stringHexToSlice("FB3701FFC_").appendRef(stringHexToSlice("90000007B00000000000000004_").appendRef(stringHexToSlice("4_")))); + + val o = MsgTransfer.fromSlice(stringHexToSlice("FB3701FFC_").appendRef(stringHexToSlice("90000007B00000000000000004_").appendRef(stringHexToSlice("4_")))); + return ( + o.params is EitherLeft, o.params is EitherRight, + o.params is EitherRight && TransferParams.fromCell(o.params.value.tvmCell) is TransferParams1, + o.params is EitherRight && TransferParams.fromCell(o.params.value.tvmCell) is TransferParams2, + ); +} + + +fun main() {} + +/** +@testcase | 201 | | 80 800000000 +@testcase | 202 | | 800000000 17 777 -1 0 +@testcase | 203 | | (null) 0 131 777 -1 0 +@testcase | 204 | | -1 0 -1 +@testcase | 205 | | 0 -1 0 -1 + */ diff --git a/tolk-tester/tests/pack-unpack-4.tolk b/tolk-tester/tests/pack-unpack-4.tolk new file mode 100644 index 0000000000..992a0664aa --- /dev/null +++ b/tolk-tester/tests/pack-unpack-4.tolk @@ -0,0 +1,162 @@ + +fun slice.assertEqDeeply(self, rhs: slice): slice { + var lhs = self; + assert(lhs.bitsEqual(rhs), 400); + assert(lhs.remainingRefsCount() == rhs.remainingRefsCount(), 400); + while (lhs.remainingRefsCount()) { + lhs.loadRef().beginParse().assertEqDeeply(rhs.loadRef().beginParse()); + } + return self; +} + + +fun run(input: TInputStruct, ans: slice) { + repeat (2) { + var s = input.toCell().beginParse(); + input = TInputStruct.fromSlice(s.assertEqDeeply(ans)); + } + input.toCell().beginParse().skipAny().assertEnd(); +} + + +type Union_8_16_32 = int8 | int16 | int32; + +fun ans101_8(): slice asm "b{0000001111} PUSHSLICE"; +fun ans101_16(): slice asm "b{010000000000001111} PUSHSLICE"; +fun ans101_32(): slice asm "b{1000000000000000000000000000001111} PUSHSLICE"; + +@method_id(101) +fun test1() { + run(15 as int8, ans101_8()); + run(15 as int16, ans101_16()); + run(15 as int32, ans101_32()); + + return 0; +} + + +type Union_8_16_32_n = int8 | null | int16 | int32; + +fun ans102n_8(): slice asm "b{0100001111} PUSHSLICE"; +fun ans102n_16(): slice asm "b{100000000000001111} PUSHSLICE"; +fun ans102n_32(): slice asm "b{1100000000000000000000000000001111} PUSHSLICE"; +fun ans102n_null(): slice asm "b{00} PUSHSLICE"; + +@method_id(102) +fun test2() { + run(15 as int8, ans102n_8()); + run(15 as int16, ans102n_16()); + run(15 as int32, ans102n_32()); + run(null, ans102n_null()); + + return 0; +} + + +struct Test4_8 { a: int8 } +struct Test4_16 { a: int16 } +struct Test4_32 { a: int32 } + +type UnionStructs_8_16_32 = int8 | Test4_16 | Test4_32; +type UnionStructs_8_16_32_n = Test4_8 | null | int16 | int32; + +fun ans104_8(): slice asm "b{0000001111} PUSHSLICE"; +fun ans104_16(): slice asm "b{010000000000001111} PUSHSLICE"; +fun ans104_32(): slice asm "b{1000000000000000000000000000001111} PUSHSLICE"; + +fun ans104n_8(): slice asm "b{0100001111} PUSHSLICE"; +fun ans104n_16(): slice asm "b{100000000000001111} PUSHSLICE"; +fun ans104n_32(): slice asm "b{1100000000000000000000000000001111} PUSHSLICE"; +fun ans104n_null(): slice asm "b{00} PUSHSLICE"; + +@method_id(104) +fun test4() { + // when mixing primitives and structs with no opcode, it's like mixing primitives + run(15, ans104_8()); + run(Test4_16{a:15}, ans104_16()); + run(Test4_32{a:15}, ans104_32()); + // with null — the same behavior + run({a:15}, ans104n_8()); + run(15 as int16, ans104n_16()); + run(15 as int32, ans104n_32()); + run(null, ans104n_null()); + + return 0; +} + + +type U105 = int8 | int32 | int64; // auto-prefixes 0b00 0b01 0b10 + +fun invalid105_slice(): slice asm "b{1100} PUSHSLICE"; // prefix 0b11 doesn't exist + +@method_id(105) +fun test5() { + try { + var u = U105.fromSlice(invalid105_slice(), {throwIfOpcodeDoesNotMatch: 9}); + return (u is int8) ? 8 : -8; + } catch (excode) { + return excode; + } +} + + +type U106 = int8 | int16 | int32 | int64; // exhaustive prefixes, checked via codegen + +fun s106_int16(): slice asm "b{010000000000001111} PUSHSLICE"; + +@method_id(106) +fun test6(): int16 { + var u = U106.fromSlice(s106_int16()); + if (u is int16) { return u; } + else { return -1; } +} + + +fun main() {} + +/** +@testcase | 101 | | 0 +@testcase | 102 | | 0 +@testcase | 104 | | 0 +@testcase | 105 | | 9 +@testcase | 106 | | 15 + +@fif_codegen +""" + test6 PROC:<{ // + b{010000000000001111} PUSHSLICE // s + b{00} SDBEGINSQ // s '8 + IF:<{ // s + 8 LDI // '12 s + 42 PUSHINT // 'USlot1 s 'UTag=42 + }>ELSE<{ // s + b{01} SDBEGINSQ // s '8 + IF:<{ // s + 16 LDI // '17 s + 44 PUSHINT // 'USlot1 s 'UTag=44 + }>ELSE<{ // s + b{10} SDBEGINSQ // s '8 + IF:<{ // s + 32 LDI // '22 s + 46 PUSHINT // 'USlot1 s 'UTag=46 + }>ELSE<{ // s + b{11} SDBEGINSQ // s '8 + IFNOTJMP:<{ // s + 63 THROW + }> + 64 LDI // '27 s + 48 PUSHINT // 'USlot1 s 'UTag=48 + }> + }> + }> + SWAP // 'USlot1 'UTag s + ENDS // u.USlot1 u.UTag + 44 EQINT // u.USlot1 '29 + IFJMP:<{ // u.USlot1 + }> // u.USlot1 + DROP // + -1 PUSHINT // '31=-1 + }> +""" + */ + diff --git a/tolk-tester/tests/pack-unpack-5.tolk b/tolk-tester/tests/pack-unpack-5.tolk new file mode 100644 index 0000000000..9644d7331f --- /dev/null +++ b/tolk-tester/tests/pack-unpack-5.tolk @@ -0,0 +1,203 @@ +struct JustInt32 { + value: int32; +} + +type MaybeInt32 = int32?; +type MaybeCell = cell?; +type Int16Or32 = int16 | int32; + +struct JustMaybeInt32 { + value: MaybeInt32; +} + + +@method_id(101) +fun test1() { + return ( + int32.estimatePackSize(), + uint64.estimatePackSize(), + int1.estimatePackSize(), + bool.estimatePackSize(), + RemainingBitsAndRefs.estimatePackSize(), + coins.estimatePackSize(), + bits6.estimatePackSize(), + bytes8.estimatePackSize(), + ); +} + +@method_id(102) +fun test2() { + return ( + JustInt32.estimatePackSize(), + JustMaybeInt32.estimatePackSize(), + MaybeInt32.estimatePackSize(), + Int16Or32.estimatePackSize(), + ); +} + +struct Test3_1 { + f1: JustInt32, + f2: JustMaybeInt32, + f3: JustMaybeInt32, + f4: JustInt32 | uint100, +} + +struct Test3_2 { + f1: Test3_1; + f2: Test3_1?; + f3: null | Test3_1 | null; +} + +@method_id(103) +fun test3() { + return (Test3_1.estimatePackSize(), Test3_2.estimatePackSize()); +} + +struct Test4_1 { + f1: address; + f2: address; +} + +struct Test4_2 { + f1: address?; + f2: address?; +} + +@overflow1023_policy("suppress") +struct Test4_3 { + f: (Test4_1, Test4_2); +} + +@method_id(104) +fun test4() { + return (Test4_1.estimatePackSize(), Test4_2.estimatePackSize(), Test4_3.estimatePackSize()); +} + +struct Test5_1 { + f1: cell; + f2: cell?; + f3: Cell; + f4: Cell?; +} + +struct Test5_2 { + f1: Cell?; + f2: Cell?; + f3: Cell>?; + f4: Cell?; + f5: Cell?; + rest: RemainingBitsAndRefs; +} + +struct Test5_3 { + f1: int8 | Cell; + f2: bytes2 | Cell; + f3: Cell | cell; + f4: Cell | coins; +} + +@method_id(105) +fun test5() { + return (Test5_1.estimatePackSize(), Test5_2.estimatePackSize(), Test5_3.estimatePackSize()); +} + +struct(0x00112233) Test6_1 { + f1: int32; +} + +struct(0b0010) Test6_2 { + f1: int32?; + f2: Cell; +} + +type Test6_or = Test6_1 | Test6_2; + +@method_id(106) +fun test6() { + return (Test6_1.estimatePackSize(), Test6_2.estimatePackSize(), Test6_or.estimatePackSize()); +} + +struct(0x1020) Test7_1 { } +struct(0x1030) Test7_2 { } +struct(0x1040) Test7_3 { } + +type Test7_or = Test7_1 | Test7_2 | Test7_3 |; + +@method_id(107) +fun test7() { + assert((Test7_1{} as Test7_or).toCell().beginParse().remainingBitsCount() == Test7_or.estimatePackSize().0, 400); + return (Test7_1.estimatePackSize(), Test7_or.estimatePackSize()); +} + +struct(0x10) Inner8_1 { + ea: address; +} +struct(0b1) CellInner8_1 { + ref: Cell; +} +struct Inner8_2 { + t: (bits32, int1, coins?); +} + +@overflow1023_policy("suppress") +struct Test8 { + f1: Inner8_1; + f2: Inner8_2; + f3: Inner8_1?; + f4: Inner8_2?; + f5: Inner8_1 | CellInner8_1; +} + +@method_id(108) +fun test8() { + return (Inner8_1.estimatePackSize(), Inner8_2.estimatePackSize(), Test8.estimatePackSize()); +} + +struct Test9_bits2 { f: bits2; } +struct Test9_bits4 { f: bits4; } + +type Test9_f1 = int32 | int64 | int128; // auto-generated 2-bit prefix +type Test9_f2 = int32 | Inner8_2; // auto-generated 1-bit prefix (Either) +type Test9_f3 = bits1 | Test9_bits2 | bits3 | bits4 | bits5; // auto-generated 3-bit prefix +type Test9_f4 = bits1 | Test9_bits2 | bits3 | Test9_bits4?; // auto-generated 3-bit prefix + +@method_id(109) +fun test9() { + return (Test9_f1.estimatePackSize(), Test9_f2.estimatePackSize(), Test9_f3.estimatePackSize(), Test9_f4.estimatePackSize()); +} + +struct Test10_1 { + a: int32; + b: builder; // unpredictable +} + +type Test10_2 = (Test10_1, bool?, RemainingBitsAndRefs); + +@method_id(110) +fun test10() { + return (Test10_1.estimatePackSize(), Test10_2.estimatePackSize()); +} + +@method_id(120) +fun test20() { + return (Test7_1 .getDeclaredPackPrefixLen(), Test7_1 .getDeclaredPackPrefix(), + CellInner8_1.getDeclaredPackPrefixLen(), CellInner8_1.getDeclaredPackPrefix()); +} + +fun main() { + __expect_type(int8.estimatePackSize(), "[int, int, int, int]"); +} + +/** +@testcase | 101 | | [ 32 32 0 0 ] [ 64 64 0 0 ] [ 1 1 0 0 ] [ 1 1 0 0 ] [ 0 9999 0 4 ] [ 4 124 0 0 ] [ 6 6 0 0 ] [ 64 64 0 0 ] +@testcase | 102 | | [ 32 32 0 0 ] [ 1 33 0 0 ] [ 1 33 0 0 ] [ 17 33 0 0 ] +@testcase | 103 | | [ 67 199 0 0 ] [ 69 599 0 0 ] +@testcase | 104 | | [ 4 534 0 0 ] [ 2 536 0 0 ] [ 6 1070 0 0 ] +@testcase | 105 | | [ 2 2 2 4 ] [ 5 9999 0 9 ] [ 4 152 1 4 ] +@testcase | 106 | | [ 64 64 0 0 ] [ 5 37 1 1 ] [ 5 65 0 1 ] +@testcase | 107 | | [ 16 16 0 0 ] [ 16 16 0 0 ] +@testcase | 108 | | [ 10 275 0 0 ] [ 34 158 0 0 ] [ 47 1143 0 1 ] +@testcase | 109 | | [ 34 130 0 0 ] [ 33 159 0 0 ] [ 4 8 0 0 ] [ 3 7 0 0 ] +@testcase | 110 | | [ 32 9999 0 4 ] [ 33 9999 0 8 ] +@testcase | 120 | | 16 4128 1 1 + */ diff --git a/tolk-tester/tests/pack-unpack-6.tolk b/tolk-tester/tests/pack-unpack-6.tolk new file mode 100644 index 0000000000..049109ad78 --- /dev/null +++ b/tolk-tester/tests/pack-unpack-6.tolk @@ -0,0 +1,223 @@ +struct (0x01) CounterIncrement { byValue: int8; } +struct (0x03) CounterDecrementBy1 {} +type CounterMsg = CounterIncrement | CounterDecrementBy1; + +struct SomeBytesFields { + f1: bytes1; +} + +@method_id(101) +fun test1() { + try { + return CounterIncrement.fromSlice(stringHexToSlice("880f"), {throwIfOpcodeDoesNotMatch: 101}).byValue as int; + } catch (excno) { + return excno; + } +} + +@method_id(102) +fun test2() { + try { + return CounterIncrement.fromSlice(stringHexToSlice("890f")).byValue as int; + } catch (excno) { + return excno; + } +} + +@method_id(103) +fun test3() { + var cc: Cell = { + tvmCell: beginCell().storeSlice(stringHexToSlice("0109ab")).endCell() + }; + return cc.load({assertEndAfterReading: false}); +} + +@method_id(104) +fun test4() { + try { + var msg = CounterMsg.fromSlice(stringHexToSlice("88"), {throwIfOpcodeDoesNotMatch: 104, assertEndAfterReading: false}); + __expect_type(msg, "CounterMsg"); + return (-1, msg is CounterIncrement); + } catch (excno) { + return (excno, null); + } +} + +@method_id(105) +fun test5() { + return SomeBytesFields { f1: stringHexToSlice("11") as bytes1 }.toCell().hash() & 0xFFFF; +} + +@method_id(106) +fun test6() { + return SomeBytesFields { f1: stringHexToSlice("11") as bytes1 }.toCell({skipBitsNFieldsValidation: true}).hash() & 0xFFFF; +} + +@method_id(107) +fun test7(believe: bool) { + try { + return SomeBytesFields { f1: stringHexToSlice("ffff") as bytes1 }.toCell({skipBitsNFieldsValidation: believe}).hash() & 0xFFFF; + } catch (excno) { + return excno; + } +} + +@method_id(108) +fun test8() { + return CounterIncrement.fromSlice(stringHexToSlice("010f"), { + throwIfOpcodeDoesNotMatch: 0xFFFF + }).byValue; +} + +@method_id(109) +fun test9() { + return CounterMsg.fromSlice(stringHexToSlice("010f"), { + throwIfOpcodeDoesNotMatch: 0xFFFF, + assertEndAfterReading: false, + }) is CounterIncrement; +} + +@method_id(110) +fun test10() { + var c = beginCell().storeUint(123, 64).endCell() as Cell; + try { + var b = c.load(); + return -(b.f1 as slice).remainingBitsCount(); + } catch (excno) { + return excno; + } +} + +@method_id(111) +fun test11() { + var c = beginCell().storeUint(123, 64).endCell() as Cell; + var b = c.load({assertEndAfterReading: false}); + return -(b.f1 as slice).remainingBitsCount(); +} + +fun main(){} + +/** +@testcase | 101 | | 101 +@testcase | 102 | | 63 +@testcase | 103 | | 9 +@testcase | 104 | | 104 (null) +@testcase | 105 | | 36896 +@testcase | 106 | | 36896 +@testcase | 107 | -1 | 2142 +@testcase | 107 | 0 | 9 +@testcase | 108 | | 15 +@testcase | 109 | | -1 +@testcase | 110 | | 9 +@testcase | 111 | | -8 + +@fif_codegen +""" +x{880f} PUSHSLICE +x{01} SDBEGINSQ +101 THROWIFNOT +""" + +@fif_codegen +""" +x{890f} PUSHSLICE +x{01} SDBEGINSQ +63 THROWIFNOT +8 LDI +ENDS +RETALT +""" + +@fif_codegen +""" + test3 PROC:<{ + NEWC + x{0109ab} PUSHSLICE + STSLICER + ENDC + CTOS + x{01} SDBEGINSQ + 63 THROWIFNOT + 8 LDI + DROP + }> +""" + +@fif_codegen +""" +IF:<{ + DROP + 129 PUSHINT +}>ELSE<{ + x{03} SDBEGINSQ + NIP + IFNOTJMP:<{ + 104 THROW + }> + 130 PUSHINT +}> +""" + +@fif_codegen +""" + test5 PROC:<{ + x{11} PUSHSLICE + NEWC + OVER + SBITREFS + 9 THROWIF + 8 EQINT + 9 THROWIFNOT + SWAP + STSLICER +""" + +@fif_codegen +""" + test6 PROC:<{ + x{11} PUSHSLICE + NEWC + SWAP + STSLICER + ENDC + HASHCU +""" + +@fif_codegen +""" + test8 PROC:<{ // + x{010f} PUSHSLICE // '0 + 16 PUSHPOW2DEC // s '2=65535 + SWAP // '2=65535 s + x{01} SDBEGINSQ // '2=65535 s '4 + s1 s2 XCHG // s '2=65535 '4 + THROWANYIFNOT // s + 8 LDI // '9 s + ENDS // '9 + }> +""" + +@fif_codegen +""" + test9 PROC:<{ // + x{010f} PUSHSLICE // s + x{01} SDBEGINSQ // s '6 + IF:<{ // s + DROP // + 129 PUSHINT // 'UTag=129 + }>ELSE<{ // s + x{03} SDBEGINSQ // s '6 + NIP // '6 + IFNOTJMP:<{ // + 16 PUSHPOW2DEC + THROWANY + }> + 130 PUSHINT // 'UTag=130 + }> + 129 PUSHINT // 'UTag '17=129 + SWAP // '17=129 'UTag + EQUAL // '16 + }> +""" + + */ diff --git a/tolk-tester/tests/parse-address.tolk b/tolk-tester/tests/parse-address.tolk index 912fb84c28..cf74af3f10 100644 --- a/tolk-tester/tests/parse-address.tolk +++ b/tolk-tester/tests/parse-address.tolk @@ -1,113 +1,171 @@ -const cc1 = stringAddressToSlice("0:ca6e321c7cce9ecedf0a8ca2492ec8592494aa5fb5ce0387dff96ef6af982a3e"); -const cc2 = stringAddressToSlice("EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPrHF"); +const cc1 = address("0:ca6e321c7cce9ecedf0a8ca2492ec8592494aa5fb5ce0387dff96ef6af982a3e"); +const cc2 = address("EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPrHF"); -fun verifyAddr(addr: slice, workchain: int, number: int) { - assert (addr.remainingBitsCount() == 3 + 8 + 256) throw 112; - addr.skipBits(3); - assert (addr.loadUint(8) == workchain) throw 111; - assert (addr.loadUint(256) == number) throw 111; +fun verifyAddr(addr: address, workchain: int, number: int) { + assert (addr == addr) throw 111; + assert (!(addr != addr)) throw 111; + assert (addr.isInternal(), 111); + assert (!addr.isExternal(), 111); + assert (!addr.isNone(), 111); + assert (addr.bitsEqual(addr), 111); + + var (wc, h) = addr.getWorkchainAndHash(); + assert (wc == workchain) throw 111; + assert (h == number) throw 111; + assert (addr.getWorkchain() == workchain) throw 111; + + var s = addr as slice; + assert (s.remainingBitsCount() == 3 + 8 + 256) throw 112; + s.skipBits(3); + assert (s.loadInt(8) == workchain) throw 111; + assert (s.loadUint(256) == number) throw 111; +} + +fun check0(addr: address) { + assert (addr.getWorkchain() == 0) throw 111; +} + +fun codegenAddrEq(a: address, b: address) { + if (a == b) { return 1; } + if (a != b) { return 2; } + return 3; } fun main() { - verifyAddr(stringAddressToSlice("Ef8zMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzM0vF"), 255, 23158417847463239084714197001737581570653996933128112807891516801582625927987); - verifyAddr(stringAddressToSlice("EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c"), 0, 0); - verifyAddr(stringAddressToSlice("EQCRDM9h4k3UJdOePPuyX40mCgA4vxge5Dc5vjBR8djbEKC5"), 0, 65607996509792174074532427555986248720836864382484024657400295821210434460432); - verifyAddr(stringAddressToSlice("UQCOgxbCOjOLH_cEuQdGgS23zBM5SrQQepMFedjK-oixYbis"), 0, 64460038539088394980732229180523693489682583665805557562964506609821558550881); - verifyAddr(stringAddressToSlice("EQDa4VOnTYlLvDJ0gZjNYm5PXfSmmtL6Vs6A_CZEtXCNICq_"), 0, 99002318936150612861744867526221033858534811876886359650897405270877291973920); - verifyAddr(stringAddressToSlice("Ef8BtXO9bcTMXjg9bgivKh4lhJmZWQPP6_rb9vfjlTP5FJtM"), 255, 772910975127952880303441415761050161913031788763061162001556772893733681428); - verifyAddr(stringAddressToSlice("Ef89xh-uy860-mCcvS8zcAUs8bApmxLGygDLEKjUk5RL-311"), 255, 27941138149036269893630478666581900122707382189183906805784676408403709676539); - verifyAddr(stringAddressToSlice("Ef_vA6yRfmt2P4UHnxlrQUZFcBnKux8mL2eMqBgpeMFPorr4"), 255, 108109262375472472702582493362335418330829651067377177643099076957184687427490); - verifyAddr(stringAddressToSlice("Ef8o6AM9sUZ8rOqLFY8PYeaC3gbopZR1BMkE8fcD0r5NnmCi"), 255, 18502444830824300068094395885436326119386947594392869497312068745716154912158); - verifyAddr(stringAddressToSlice("Ef_fvrd0hBoVJUxoi7wH173Zk8NPiyVvxh5IoYSjEYZbOhsu"), 255, 101202732337223525952216789200341489000836292542250083765062769181728788863802); - verifyAddr(stringAddressToSlice("Ef9nzj6RBc4mQ6p3ng7mGJ7tp7MbzERhe7obkM9A0wnCCEcf"), 255, 46952625717497919357580310066854892621799390294920450816077086267929711460872); - verifyAddr(stringAddressToSlice("Ef9rU-_AAnBkHB71TIC3QvUf5LcAsvj0B4IoYzAXLpEFd5CA"), 255, 48545777798729612074233611768739897492467685225150339217043102685589809464695); - verifyAddr(stringAddressToSlice("Ef9LynHHKgBxY6-l-W_dWN-CtGT2_ji5rN3EzOI-p9zWEfq6"), 255, 34281152017620085319078796986198022632548048219136747083019177301186013091345); - verifyAddr(stringAddressToSlice("Ef9hMd78gzSiVsK0zz0AHtEja8x1UoB_NDZMjn-l86NQK_2Y"), 255, 43962460814164090767878334494257755557842170134382045184921495822637115592747); - verifyAddr(stringAddressToSlice("Ef80FNJ5NJO4-0QwlVAWckUZXdk-PfYDexDZ1-ju9SxhF0A6"), 255, 23557057702048801338698514499604413540742716310574705490458593067566768087319); - verifyAddr(stringAddressToSlice("Ef_fdIbThooPs4_r2DE_Z6ZsWycJdHLnsuKAJHTcbaZaipez"), 255, 101071650030310556115830521522496708686577365303530257137459798093298869361290); - verifyAddr(stringAddressToSlice("Ef_lva0qEiZhWrrZJl-IJxyCcTQmmTo71fIWyQ31HxJ8NurV"), 255, 103914771557158282349484109182290824591675204108148026180964788916630125182006); - verifyAddr(stringAddressToSlice("Ef8sMGKypw006AeRYqimLjmY2Ufp-SHk8C0ZJBNgVBlzw_Nr"), 255, 19987255184378161380023126214650814972824352533523055905552702178965809886147); - verifyAddr(stringAddressToSlice("EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff+W72r5gqPrHF"), 0, 91561894446285001782438967260723928368560331318344957259023550817453781559870); - verifyAddr(stringAddressToSlice("EQCaSCHVak-jIc9ANutTAfHpZNM3YdGky7yaDzsTrg0WhFlm"), 0, 69783625181781015447914682554083924798054947959007050695795761257887453484676); - verifyAddr(stringAddressToSlice("EQBS9U3AfD15fGmOtRMXQAxcPVBwNuItfLcDni9fkbTyyNX0"), 0, 37523067738561024305547433298623118197038688994386001017161816416175242146504); - verifyAddr(stringAddressToSlice("EQBiMNL9qNWMAkJHuM0BFneYcuHL17kzS4pswpaEO-NGWrFG"), 0, 44412924025649114419413541526870954696667907029239618728289150652715284776538); - verifyAddr(stringAddressToSlice("EQAUzE-Nef80O9dLZy91HfPiOb6EEQ8YqyWKyIU-KeaYLNUi"), 0, 9407242825041766837311851458322335726136775042891143504070507665010681354284); - verifyAddr(stringAddressToSlice("EQD-nhrinjv0B4LTgr0dRHTHwH1MOsgGhKBXJZd7vESMZUf1"), 0, 115166810931401616117484448645661180241548402534908005320733783571353775148133); - verifyAddr(stringAddressToSlice("EQAVD3Fni9I6j8XeSIl-wAGBEhqhame6OtAY0GScKT0D9X6f"), 0, 9525855215156855607080079714361451576383963668563135377495902959388099150837); - verifyAddr(stringAddressToSlice("EQC6ACq3VANZjqfRBy7JMHkpLwqQ9qyYJsCIGx1mYbQgxaKw"), 0, 84130484652351964071210477536969520113177637645401392541565606610268614566085); - verifyAddr(stringAddressToSlice("EQCIJLNFIko5CvpKn9oAkrDgLocDOoD4vwmHxNx_fsG_LkwW"), 0, 61579391178099797614367237687950512448308156724136883899001108680249616482094); - verifyAddr(stringAddressToSlice("EQCe4AYIBce1pAk2qJJPSs1OzyZRlKjkfq8zuC8D7erv6DUP"), 0, 71861245445432818728925844931259040612664802586395398157190478191760507596776); - verifyAddr(stringAddressToSlice("EQCtrtTXEAoSpoERmiqOnICe9LHxn2N89N4BH9qdHlrG-U0i"), 0, 78559023162479717496981724991265882229440558807791659796411897368395464230649); - verifyAddr(stringAddressToSlice("EQBBlraAps0OZaB9Q8ePQn2wVAaL1G411A-dNppyWe3X3GIT"), 0, 29666621803903557832193058147214384979915773445007872807927344851911086823388); - verifyAddr(stringAddressToSlice("EQBiASqUqaVizrozLRbszkWC2kETbkhpO2qniDVDPPg2_0W8"), 0, 44328719889509369519441680467651025944540360433148852643949783408843779749631); - verifyAddr(stringAddressToSlice("EQBu2Q1EO8gIoNA1qoGWnHUudKfmqlKEDTQE-DxN-_4sdg14"), 0, 50137910719490808065414827264266674858051167131188257457782826342827836714102); - verifyAddr(stringAddressToSlice("EQA5bvxWd5-q2vJUVqR9AlbEIfdFysLR0PXGgVlBf8x5hWuF"), 0, 25977927117604457079092522008276392864656238504700352770597256138254994667909); - verifyAddr(stringAddressToSlice("EQBguMSHjFv5bfoOdshr3ruS9ymSZzhRKMovoNrxGxZXvmee"), 0, 43748489720571123896506696370504498290006245978262404519821633796370658121662); - verifyAddr(stringAddressToSlice("EQAxL0oF1-zNgimPKthbDnYS4xj94rHtfNRN7_Pd1r2LNNv3"), 0, 22246882279393590648219842750911786376759362211171398419754426796438233910068); - verifyAddr(stringAddressToSlice("EQANX1uRKGZfyPIwEaIXrR0ZOqadct5q10dvKxWIxx7SQqzW"), 0, 6048549475100840191738010856156544571222758030966479209409932714701987172930); - verifyAddr(stringAddressToSlice("EQBitdFDoU5DWSjfKq7AsO29RIwAnBzzvcVVSn5ekQoB9Liv"), 0, 44647902768175374073183447303109856895983123510911038181495138821771906122228); - verifyAddr(stringAddressToSlice("EQBgbux7VSjqJHP7ByRK1q4QuVZbpSCesNgvz5qad3lfXX_B"), 0, 43618018778298854282398238948198420936771670943015013768514626213000552996701); - verifyAddr(stringAddressToSlice("EQDisBd8U7M3CEOZ8gcWCdetdmJi3AI31zIT5qBwOdmUbsxY"), 0, 102533830955233207294921564956803510155400341370448300698800842506363763004526); - verifyAddr(stringAddressToSlice("EQAZpn_eynVlf7Ii2d6jP_p1URPrdF9F3S7DiudQyelkjzwE"), 0, 11602000355550451044739442929923326898313570892134000961608306166632391730319); - verifyAddr(stringAddressToSlice("EQDE0HBgfkOiqHezLtExBGTvOs8eitthHQosBjW3BmDy1y2K"), 0, 89021598108837008984355105304701054698583123510131754065320641619941010764503); - verifyAddr(stringAddressToSlice("EQDyT36zktBN9PVWvZ1joRxhIfEUgCPt4F2isa-enUA_d6CP"), 0, 109600164736599393471831241268953938618560132398692391517933933264745646800759); - verifyAddr(stringAddressToSlice("EQDSMUGwt25IQd3_yHjI03F71G8Kp2GMaMEv2TiWoTKbsyRH"), 0, 95072727086440754059372943502908629555499501854161516009430039520728770059187); - verifyAddr(stringAddressToSlice("EQAgK1EcrvEuL9sCtoj3cNhVNOuf3lo5GIPE2gn1fwZZYB3j"), 0, 14550545393206146289454646242321274637527057595221202748348667645886114191712); - verifyAddr(stringAddressToSlice("EQCDKqL5w_6MD-Z7AOButu-uR-ZJTsgNU1fu464hn9grY81U"), 0, 59328315557704100696483472039557119625141880163887490602190749720459366378339); - verifyAddr(stringAddressToSlice("EQB1aVMyFBhnlYXmQjsma0S63kvxKU7ccZKFNCFTwX7ASPv4"), 0, 53106696421104300082516512931084483581353095629408473618166869610568148238408); - verifyAddr(stringAddressToSlice("EQBbjrXHoxDyh1ZYGBdBoQgLaScxW6pZR1hEhJC8BqF-5Kgq"), 0, 41412616102566803060532874463898939692666425753852274254609049615175463829220); - verifyAddr(stringAddressToSlice("EQC-QeZ13QP0lszxNKt380fCWuaV94vwC/bfuqmrlg1/fJPA"), 0, 86055876869280374285292827775555707420719385459150221433115419095878595346300); - verifyAddr(stringAddressToSlice("EQAiUwpF27vXCngqNhf_TQ5E_06ah0G4zuSrnfU7CLLaht5H"), 0, 15525356059048115813946213102829493539706126913595626308144289257869196581510); - verifyAddr(stringAddressToSlice("EQBqiVjmhe2iVGmgOSDO1FGjSiz_AMtb1w7lLEiP4XIF_SFy"), 0, 48187833566271418625754761625661652107159264793429628379411792200127405491709); - verifyAddr(stringAddressToSlice("EQDmwvaK2d_SbaPdpOM60effPWeKsksgDVwFPEyxuftM396K"), 0, 104376425077737068747642645125299653296942252727305637929378053253273342397663); - verifyAddr(stringAddressToSlice("EQDWtPZZgF7wvIMUHZQojuD3utiuivsW7WslRJ33dgv-5yc8"), 0, 97114682311034709685427168495629428400170984047839002197324103884924936519399); - verifyAddr(stringAddressToSlice("EQAA7z0JI0JKqbN-1uENKz9JrxIO5ZRY-ehMeg9fPncx50Ck"), 0, 422697701361909095759185681783393186844038628935759044330165207027374567911); - verifyAddr(stringAddressToSlice("EQBVUHRoCq6coQYUwOAhGSoAmQ6Mpm7dFlDYon6HMgWV8Ftr"), 0, 38588743302295548905191533977469452945717219128199196974980570837505276220912); - verifyAddr(stringAddressToSlice("EQCTdvDCf0bA5dOPI1-44tB2ZfNcMGiklzvg27TovgDEqM6E"), 0, 66700138358140658950710678965721715920748906761125730971082529064117803730088); - verifyAddr(stringAddressToSlice("EQBDBKE5WGKIlnoi3OOzw7vkKKIX55eWjPvgxJWwek8AyL2J"), 0, 30313140970524770883308749215942283658935592719811899513010665548955593408712); - verifyAddr(stringAddressToSlice("EQAvCSyLCo21GrqLAifdov4WkOxuGQCjMRxgF1cXSaNzLHZe"), 0, 21274912932379789207153885262858116665851037273450532982121514600400844714796); - verifyAddr(stringAddressToSlice("EQCsLpDeHB2qpRbmsCb_0xmsYVNx1NgeYrvHGT1TDrHkDgL4"), 0, 77880084760844670718511036557364450189323549135231036747480176919181282894862); - verifyAddr(stringAddressToSlice("EQCTQ8kPwyX92r48gCIL_pLN_RcQT9ghZygnmDTYkOkuW_j5"), 0, 66609755171046741472463433430016629628609840960137226492652665879987546041947); - verifyAddr(stringAddressToSlice("EQCTrFRSHt-tfk7WxK9ZHQmqLcgxXxTK7wGfCEbqgY2W9Mcx"), 0, 66794468397542182731534559853537484892417154018190804733043974345563210356468); - verifyAddr(stringAddressToSlice("EQCv28y49GdaLncoclv0ISdDlMUY_cxDPGNWFCPT8t4GuqUJ"), 0, 79543100951881731989812212377176715376834409392116644269458867858071577560762); - verifyAddr(stringAddressToSlice("EQCVL-k6deDR56Z8pcb0Btg0lGfaivOGfdDCD1vvyRsyL9vS"), 0, 67479265933941008511790471646564661743401752930295407567346938670637286896175); - verifyAddr(stringAddressToSlice("EQD6t2dXDjZxF1DqunKF-8dEWivJdliY_0FYiCXnthuqnDCa"), 0, 113402258385556889021060606279033166272577193563727959698596277924908309916316); - verifyAddr(stringAddressToSlice("EQDE98XNzXiPq7VnbJJ2M4-Ht3tX_OWR0xUTTnDC8NObLmyU"), 0, 89091094739778473356272490822716056624384395256388481953562551087642791090990); - verifyAddr(stringAddressToSlice("EQDfeRDE1TDhwt478CDR0Q7MDwqcTUhfjqyTT59mgoAaF6f7"), 0, 101079669463449311486034260688909914923832300293253430457119371423825321269783); - verifyAddr(stringAddressToSlice("EQDijcEyUKa-QgCbeGlggQk1uBtt2ZRHyW4Y4gB4R6MN6RLW"), 0, 102473162609487797404330889623966425536887610061087715571345738626121871855081); - verifyAddr(stringAddressToSlice("EQDOtFOt41skbjBkZF89oYXpoDECjlxIzD-ShWAOYyzuxqLA"), 0, 93495056812773926196963707371665512785268729004579280701087533371037976424134); - verifyAddr(stringAddressToSlice("EQDuJKSFWU7AYqH6KLFfAbYvMuz346eWmJvG6_2NYE42_B4T"), 0, 107715199938628393100813870735031557263256555616273999363057194279168054802172); - verifyAddr(stringAddressToSlice("EQDwGu4vFv1e3wn8min_iy7OPJXegOYTFQ5bZFZ5a5ZPiBpX"), 0, 108602665568837301744601989570019709742180613578164394799026726718721456754568); - verifyAddr(stringAddressToSlice("EQC4G2ph6AS_mD_-cIv4aIYm1z5jAgCW_TTDEr72ygXOP2X-"), 0, 83274003234732023403481554420859495155084746906198543572711543697320249249343); - verifyAddr(stringAddressToSlice("EQDpUkyAa6lZ12P3ZB2PL_rmWwI1I55BU4kxw_rssFL5dswA"), 0, 105534303174146507629736518862713754948570412188900908177600861330298381728118); - verifyAddr(stringAddressToSlice("EQDoIA20MF1qEcSPtROdCu5ukGx9dVjgMeJh1oQ4A4cf_Jif"), 0, 104993214557977037193613824776415934089204193426692473563548548423424814817276); - verifyAddr(stringAddressToSlice("EQDpUkyAa6lZ12P3ZB2PL_rmWwI1I55BU4kxw_rssFL5dswA"), 0, 105534303174146507629736518862713754948570412188900908177600861330298381728118); - verifyAddr(stringAddressToSlice("EQClLO4EnZ_rTyV1GVpWy53pLgWJRki5c4ZzuM_1O_ClBkO9"), 0, 74711004027159342540251007601464500186374346239921204216319145006974068892934); - verifyAddr(stringAddressToSlice("EQDmkj65Ab_m0aZaW8IpKw4kYqIgITw_HRstYEkVQ6NIYCyW"), 0, 104290347741656803921830951060768893809692975574470790497562993373950614128736); - verifyAddr(stringAddressToSlice("EQCqNTwAYUNhPFS0RgqZoTLGJcQQxbAJ7csUo4YO3_TONLab"), 0, 76987241268612358571638783428744566580605181728938801022059780105627411729972); - verifyAddr(stringAddressToSlice("EQCL3DmCynaRK7-vsfeNmd4Jj-UxAIHPvA4qS2xwaL6UpLbF"), 0, 63260589232981964910240894899061676480139492286430589202252472895352724165796); - verifyAddr(stringAddressToSlice("EQDbU1SVEjBE73oUqgAoM9gDcShUkM5EC2PgoCjuwVUKo-Ee"), 0, 99203745911752606845646497420891218522647962685916739950275357890977532807843); - verifyAddr(stringAddressToSlice("EQD02VdcF4TDbCKLLhZJ39NQTu6aWq2LnLjp0oXqbNu_BANK"), 0, 110748343802097970709980079967961144373090790244250392237586606542170934198020); - verifyAddr(stringAddressToSlice("EQBynBO23ywHy_CgarY9NK9FTz0yDsG82PtcbSTQgGoXwiuA"), 0, 51839428943991432793039248316067731096592274748149794482308513726460953499586); - verifyAddr(stringAddressToSlice("UQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPuwA"), 0, 91561894446285001782438967260723928368560331318344957259023550817453781559870); - verifyAddr(stringAddressToSlice("EQAUTbQiM522Y_XJ_T98QPhPhTmb4nV--VSPiha8kC6kRfPO"), 0, 9183547432069678364603018431103042146626948674383548774683927217595824907333); - verifyAddr(stringAddressToSlice("EQBlqsm144Dq6SjbPI4jjZvA1hqTIP3CvHovbIfW_t-SCALE"), 0, 45985353862647206060987594732861817093328871106941773337270673759241903247880); - verifyAddr(stringAddressToSlice("UQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPuwA"), 0, 91561894446285001782438967260723928368560331318344957259023550817453781559870); - verifyAddr(stringAddressToSlice("kQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPgpP"), 0, 91561894446285001782438967260723928368560331318344957259023550817453781559870); - verifyAddr(stringAddressToSlice("kf-Dfdg-YQXaR2Q97gZJ4fGBtmV1DHOU1y1RPyyZZtRy_Ikh"), 255, 59475331506450494976393625198911249698879029820580340449086829444312920781564); - verifyAddr(stringAddressToSlice("0:527964d55cfa6eb731f4bfc07e9d025098097ef8505519e853986279bd8400d8"), 0, 37304138005561100291416421295333982606153966175434134130332440738068913455320); - verifyAddr(stringAddressToSlice("0:0000000000000000000000000000000000000000000000000000000000000000"), 0, 0); - verifyAddr(stringAddressToSlice("0:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFfffffffffffffffffffffffffffff"), 0, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - verifyAddr(stringAddressToSlice("0:zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"), 0, 23158417847463239084714197001737581570653996933128112807891516801582625927987); - verifyAddr(stringAddressToSlice("0:0000000000000000000000000000000000000000000000000000000000000000"), 0, 0); - verifyAddr(stringAddressToSlice("1:527964d55cfa6eb731f4bfc07e9d025098097ef8505519e853986279bd8400d8"), 1, 37304138005561100291416421295333982606153966175434134130332440738068913455320); - verifyAddr(stringAddressToSlice("9:527964d55cfa6eb731f4bfc07e9d025098097ef8505519e853986279bd8400d8"), 9, 37304138005561100291416421295333982606153966175434134130332440738068913455320); - verifyAddr(stringAddressToSlice("99:527964d55cfa6eb731f4bfc07e9d025098097ef8505519e853986279bd8400d8"), 99, 37304138005561100291416421295333982606153966175434134130332440738068913455320); - verifyAddr(stringAddressToSlice("-1:527964d55cfa6eb731f4bfc07e9d025098097ef8505519e853986279bd8400d8"), 255, 37304138005561100291416421295333982606153966175434134130332440738068913455320); + __expect_type(cc1, "address"); + + verifyAddr(address("Ef8zMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzM0vF"), -1, 23158417847463239084714197001737581570653996933128112807891516801582625927987); + verifyAddr(address("EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c"), 0, 0); + verifyAddr(address("EQCRDM9h4k3UJdOePPuyX40mCgA4vxge5Dc5vjBR8djbEKC5"), 0, 65607996509792174074532427555986248720836864382484024657400295821210434460432); + verifyAddr(address("UQCOgxbCOjOLH_cEuQdGgS23zBM5SrQQepMFedjK-oixYbis"), 0, 64460038539088394980732229180523693489682583665805557562964506609821558550881); + verifyAddr(address("EQDa4VOnTYlLvDJ0gZjNYm5PXfSmmtL6Vs6A_CZEtXCNICq_"), 0, 99002318936150612861744867526221033858534811876886359650897405270877291973920); + verifyAddr(address("Ef8BtXO9bcTMXjg9bgivKh4lhJmZWQPP6_rb9vfjlTP5FJtM"), -1, 772910975127952880303441415761050161913031788763061162001556772893733681428); + verifyAddr(address("Ef89xh-uy860-mCcvS8zcAUs8bApmxLGygDLEKjUk5RL-311"), -1, 27941138149036269893630478666581900122707382189183906805784676408403709676539); + verifyAddr(address("Ef_vA6yRfmt2P4UHnxlrQUZFcBnKux8mL2eMqBgpeMFPorr4"), -1, 108109262375472472702582493362335418330829651067377177643099076957184687427490); + verifyAddr(address("Ef8o6AM9sUZ8rOqLFY8PYeaC3gbopZR1BMkE8fcD0r5NnmCi"), -1, 18502444830824300068094395885436326119386947594392869497312068745716154912158); + verifyAddr(address("Ef_fvrd0hBoVJUxoi7wH173Zk8NPiyVvxh5IoYSjEYZbOhsu"), -1, 101202732337223525952216789200341489000836292542250083765062769181728788863802); + verifyAddr(address("Ef9nzj6RBc4mQ6p3ng7mGJ7tp7MbzERhe7obkM9A0wnCCEcf"), -1, 46952625717497919357580310066854892621799390294920450816077086267929711460872); + verifyAddr(address("Ef9rU-_AAnBkHB71TIC3QvUf5LcAsvj0B4IoYzAXLpEFd5CA"), -1, 48545777798729612074233611768739897492467685225150339217043102685589809464695); + verifyAddr(address("Ef9LynHHKgBxY6-l-W_dWN-CtGT2_ji5rN3EzOI-p9zWEfq6"), -1, 34281152017620085319078796986198022632548048219136747083019177301186013091345); + verifyAddr(address("Ef9hMd78gzSiVsK0zz0AHtEja8x1UoB_NDZMjn-l86NQK_2Y"), -1, 43962460814164090767878334494257755557842170134382045184921495822637115592747); + verifyAddr(address("Ef80FNJ5NJO4-0QwlVAWckUZXdk-PfYDexDZ1-ju9SxhF0A6"), -1, 23557057702048801338698514499604413540742716310574705490458593067566768087319); + verifyAddr(address("Ef_fdIbThooPs4_r2DE_Z6ZsWycJdHLnsuKAJHTcbaZaipez"), -1, 101071650030310556115830521522496708686577365303530257137459798093298869361290); + verifyAddr(address("Ef_lva0qEiZhWrrZJl-IJxyCcTQmmTo71fIWyQ31HxJ8NurV"), -1, 103914771557158282349484109182290824591675204108148026180964788916630125182006); + verifyAddr(address("Ef8sMGKypw006AeRYqimLjmY2Ufp-SHk8C0ZJBNgVBlzw_Nr"), -1, 19987255184378161380023126214650814972824352533523055905552702178965809886147); + verifyAddr(address("EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff+W72r5gqPrHF"), 0, 91561894446285001782438967260723928368560331318344957259023550817453781559870); + verifyAddr(address("EQCaSCHVak-jIc9ANutTAfHpZNM3YdGky7yaDzsTrg0WhFlm"), 0, 69783625181781015447914682554083924798054947959007050695795761257887453484676); + verifyAddr(address("EQBS9U3AfD15fGmOtRMXQAxcPVBwNuItfLcDni9fkbTyyNX0"), 0, 37523067738561024305547433298623118197038688994386001017161816416175242146504); + verifyAddr(address("EQBiMNL9qNWMAkJHuM0BFneYcuHL17kzS4pswpaEO-NGWrFG"), 0, 44412924025649114419413541526870954696667907029239618728289150652715284776538); + verifyAddr(address("EQAUzE-Nef80O9dLZy91HfPiOb6EEQ8YqyWKyIU-KeaYLNUi"), 0, 9407242825041766837311851458322335726136775042891143504070507665010681354284); + verifyAddr(address("EQD-nhrinjv0B4LTgr0dRHTHwH1MOsgGhKBXJZd7vESMZUf1"), 0, 115166810931401616117484448645661180241548402534908005320733783571353775148133); + verifyAddr(address("EQAVD3Fni9I6j8XeSIl-wAGBEhqhame6OtAY0GScKT0D9X6f"), 0, 9525855215156855607080079714361451576383963668563135377495902959388099150837); + verifyAddr(address("EQC6ACq3VANZjqfRBy7JMHkpLwqQ9qyYJsCIGx1mYbQgxaKw"), 0, 84130484652351964071210477536969520113177637645401392541565606610268614566085); + verifyAddr(address("EQCIJLNFIko5CvpKn9oAkrDgLocDOoD4vwmHxNx_fsG_LkwW"), 0, 61579391178099797614367237687950512448308156724136883899001108680249616482094); + verifyAddr(address("EQCe4AYIBce1pAk2qJJPSs1OzyZRlKjkfq8zuC8D7erv6DUP"), 0, 71861245445432818728925844931259040612664802586395398157190478191760507596776); + verifyAddr(address("EQCtrtTXEAoSpoERmiqOnICe9LHxn2N89N4BH9qdHlrG-U0i"), 0, 78559023162479717496981724991265882229440558807791659796411897368395464230649); + verifyAddr(address("EQBBlraAps0OZaB9Q8ePQn2wVAaL1G411A-dNppyWe3X3GIT"), 0, 29666621803903557832193058147214384979915773445007872807927344851911086823388); + verifyAddr(address("EQBiASqUqaVizrozLRbszkWC2kETbkhpO2qniDVDPPg2_0W8"), 0, 44328719889509369519441680467651025944540360433148852643949783408843779749631); + verifyAddr(address("EQBu2Q1EO8gIoNA1qoGWnHUudKfmqlKEDTQE-DxN-_4sdg14"), 0, 50137910719490808065414827264266674858051167131188257457782826342827836714102); + verifyAddr(address("EQA5bvxWd5-q2vJUVqR9AlbEIfdFysLR0PXGgVlBf8x5hWuF"), 0, 25977927117604457079092522008276392864656238504700352770597256138254994667909); + verifyAddr(address("EQBguMSHjFv5bfoOdshr3ruS9ymSZzhRKMovoNrxGxZXvmee"), 0, 43748489720571123896506696370504498290006245978262404519821633796370658121662); + verifyAddr(address("EQAxL0oF1-zNgimPKthbDnYS4xj94rHtfNRN7_Pd1r2LNNv3"), 0, 22246882279393590648219842750911786376759362211171398419754426796438233910068); + verifyAddr(address("EQANX1uRKGZfyPIwEaIXrR0ZOqadct5q10dvKxWIxx7SQqzW"), 0, 6048549475100840191738010856156544571222758030966479209409932714701987172930); + verifyAddr(address("EQBitdFDoU5DWSjfKq7AsO29RIwAnBzzvcVVSn5ekQoB9Liv"), 0, 44647902768175374073183447303109856895983123510911038181495138821771906122228); + verifyAddr(address("EQBgbux7VSjqJHP7ByRK1q4QuVZbpSCesNgvz5qad3lfXX_B"), 0, 43618018778298854282398238948198420936771670943015013768514626213000552996701); + verifyAddr(address("EQDisBd8U7M3CEOZ8gcWCdetdmJi3AI31zIT5qBwOdmUbsxY"), 0, 102533830955233207294921564956803510155400341370448300698800842506363763004526); + verifyAddr(address("EQAZpn_eynVlf7Ii2d6jP_p1URPrdF9F3S7DiudQyelkjzwE"), 0, 11602000355550451044739442929923326898313570892134000961608306166632391730319); + verifyAddr(address("EQDE0HBgfkOiqHezLtExBGTvOs8eitthHQosBjW3BmDy1y2K"), 0, 89021598108837008984355105304701054698583123510131754065320641619941010764503); + verifyAddr(address("EQDyT36zktBN9PVWvZ1joRxhIfEUgCPt4F2isa-enUA_d6CP"), 0, 109600164736599393471831241268953938618560132398692391517933933264745646800759); + verifyAddr(address("EQDSMUGwt25IQd3_yHjI03F71G8Kp2GMaMEv2TiWoTKbsyRH"), 0, 95072727086440754059372943502908629555499501854161516009430039520728770059187); + verifyAddr(address("EQAgK1EcrvEuL9sCtoj3cNhVNOuf3lo5GIPE2gn1fwZZYB3j"), 0, 14550545393206146289454646242321274637527057595221202748348667645886114191712); + verifyAddr(address("EQCDKqL5w_6MD-Z7AOButu-uR-ZJTsgNU1fu464hn9grY81U"), 0, 59328315557704100696483472039557119625141880163887490602190749720459366378339); + verifyAddr(address("EQB1aVMyFBhnlYXmQjsma0S63kvxKU7ccZKFNCFTwX7ASPv4"), 0, 53106696421104300082516512931084483581353095629408473618166869610568148238408); + verifyAddr(address("EQBbjrXHoxDyh1ZYGBdBoQgLaScxW6pZR1hEhJC8BqF-5Kgq"), 0, 41412616102566803060532874463898939692666425753852274254609049615175463829220); + verifyAddr(address("EQC-QeZ13QP0lszxNKt380fCWuaV94vwC/bfuqmrlg1/fJPA"), 0, 86055876869280374285292827775555707420719385459150221433115419095878595346300); + verifyAddr(address("EQAiUwpF27vXCngqNhf_TQ5E_06ah0G4zuSrnfU7CLLaht5H"), 0, 15525356059048115813946213102829493539706126913595626308144289257869196581510); + verifyAddr(address("EQBqiVjmhe2iVGmgOSDO1FGjSiz_AMtb1w7lLEiP4XIF_SFy"), 0, 48187833566271418625754761625661652107159264793429628379411792200127405491709); + verifyAddr(address("EQDmwvaK2d_SbaPdpOM60effPWeKsksgDVwFPEyxuftM396K"), 0, 104376425077737068747642645125299653296942252727305637929378053253273342397663); + verifyAddr(address("EQDWtPZZgF7wvIMUHZQojuD3utiuivsW7WslRJ33dgv-5yc8"), 0, 97114682311034709685427168495629428400170984047839002197324103884924936519399); + verifyAddr(address("EQAA7z0JI0JKqbN-1uENKz9JrxIO5ZRY-ehMeg9fPncx50Ck"), 0, 422697701361909095759185681783393186844038628935759044330165207027374567911); + verifyAddr(address("EQBVUHRoCq6coQYUwOAhGSoAmQ6Mpm7dFlDYon6HMgWV8Ftr"), 0, 38588743302295548905191533977469452945717219128199196974980570837505276220912); + verifyAddr(address("EQCTdvDCf0bA5dOPI1-44tB2ZfNcMGiklzvg27TovgDEqM6E"), 0, 66700138358140658950710678965721715920748906761125730971082529064117803730088); + verifyAddr(address("EQBDBKE5WGKIlnoi3OOzw7vkKKIX55eWjPvgxJWwek8AyL2J"), 0, 30313140970524770883308749215942283658935592719811899513010665548955593408712); + verifyAddr(address("EQAvCSyLCo21GrqLAifdov4WkOxuGQCjMRxgF1cXSaNzLHZe"), 0, 21274912932379789207153885262858116665851037273450532982121514600400844714796); + verifyAddr(address("EQCsLpDeHB2qpRbmsCb_0xmsYVNx1NgeYrvHGT1TDrHkDgL4"), 0, 77880084760844670718511036557364450189323549135231036747480176919181282894862); + verifyAddr(address("EQCTQ8kPwyX92r48gCIL_pLN_RcQT9ghZygnmDTYkOkuW_j5"), 0, 66609755171046741472463433430016629628609840960137226492652665879987546041947); + verifyAddr(address("EQCTrFRSHt-tfk7WxK9ZHQmqLcgxXxTK7wGfCEbqgY2W9Mcx"), 0, 66794468397542182731534559853537484892417154018190804733043974345563210356468); + verifyAddr(address("EQCv28y49GdaLncoclv0ISdDlMUY_cxDPGNWFCPT8t4GuqUJ"), 0, 79543100951881731989812212377176715376834409392116644269458867858071577560762); + verifyAddr(address("EQCVL-k6deDR56Z8pcb0Btg0lGfaivOGfdDCD1vvyRsyL9vS"), 0, 67479265933941008511790471646564661743401752930295407567346938670637286896175); + verifyAddr(address("EQD6t2dXDjZxF1DqunKF-8dEWivJdliY_0FYiCXnthuqnDCa"), 0, 113402258385556889021060606279033166272577193563727959698596277924908309916316); + verifyAddr(address("EQDE98XNzXiPq7VnbJJ2M4-Ht3tX_OWR0xUTTnDC8NObLmyU"), 0, 89091094739778473356272490822716056624384395256388481953562551087642791090990); + verifyAddr(address("EQDfeRDE1TDhwt478CDR0Q7MDwqcTUhfjqyTT59mgoAaF6f7"), 0, 101079669463449311486034260688909914923832300293253430457119371423825321269783); + verifyAddr(address("EQDijcEyUKa-QgCbeGlggQk1uBtt2ZRHyW4Y4gB4R6MN6RLW"), 0, 102473162609487797404330889623966425536887610061087715571345738626121871855081); + verifyAddr(address("EQDOtFOt41skbjBkZF89oYXpoDECjlxIzD-ShWAOYyzuxqLA"), 0, 93495056812773926196963707371665512785268729004579280701087533371037976424134); + verifyAddr(address("EQDuJKSFWU7AYqH6KLFfAbYvMuz346eWmJvG6_2NYE42_B4T"), 0, 107715199938628393100813870735031557263256555616273999363057194279168054802172); + verifyAddr(address("EQDwGu4vFv1e3wn8min_iy7OPJXegOYTFQ5bZFZ5a5ZPiBpX"), 0, 108602665568837301744601989570019709742180613578164394799026726718721456754568); + verifyAddr(address("EQC4G2ph6AS_mD_-cIv4aIYm1z5jAgCW_TTDEr72ygXOP2X-"), 0, 83274003234732023403481554420859495155084746906198543572711543697320249249343); + verifyAddr(address("EQDpUkyAa6lZ12P3ZB2PL_rmWwI1I55BU4kxw_rssFL5dswA"), 0, 105534303174146507629736518862713754948570412188900908177600861330298381728118); + verifyAddr(address("EQDoIA20MF1qEcSPtROdCu5ukGx9dVjgMeJh1oQ4A4cf_Jif"), 0, 104993214557977037193613824776415934089204193426692473563548548423424814817276); + verifyAddr(address("EQDpUkyAa6lZ12P3ZB2PL_rmWwI1I55BU4kxw_rssFL5dswA"), 0, 105534303174146507629736518862713754948570412188900908177600861330298381728118); + verifyAddr(address("EQClLO4EnZ_rTyV1GVpWy53pLgWJRki5c4ZzuM_1O_ClBkO9"), 0, 74711004027159342540251007601464500186374346239921204216319145006974068892934); + verifyAddr(address("EQDmkj65Ab_m0aZaW8IpKw4kYqIgITw_HRstYEkVQ6NIYCyW"), 0, 104290347741656803921830951060768893809692975574470790497562993373950614128736); + verifyAddr(address("EQCqNTwAYUNhPFS0RgqZoTLGJcQQxbAJ7csUo4YO3_TONLab"), 0, 76987241268612358571638783428744566580605181728938801022059780105627411729972); + verifyAddr(address("EQCL3DmCynaRK7-vsfeNmd4Jj-UxAIHPvA4qS2xwaL6UpLbF"), 0, 63260589232981964910240894899061676480139492286430589202252472895352724165796); + verifyAddr(address("EQDbU1SVEjBE73oUqgAoM9gDcShUkM5EC2PgoCjuwVUKo-Ee"), 0, 99203745911752606845646497420891218522647962685916739950275357890977532807843); + verifyAddr(address("EQD02VdcF4TDbCKLLhZJ39NQTu6aWq2LnLjp0oXqbNu_BANK"), 0, 110748343802097970709980079967961144373090790244250392237586606542170934198020); + verifyAddr(address("EQBynBO23ywHy_CgarY9NK9FTz0yDsG82PtcbSTQgGoXwiuA"), 0, 51839428943991432793039248316067731096592274748149794482308513726460953499586); + verifyAddr(address("UQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPuwA"), 0, 91561894446285001782438967260723928368560331318344957259023550817453781559870); + verifyAddr(address("EQAUTbQiM522Y_XJ_T98QPhPhTmb4nV--VSPiha8kC6kRfPO"), 0, 9183547432069678364603018431103042146626948674383548774683927217595824907333); + verifyAddr(address("EQBlqsm144Dq6SjbPI4jjZvA1hqTIP3CvHovbIfW_t-SCALE"), 0, 45985353862647206060987594732861817093328871106941773337270673759241903247880); + verifyAddr(address("UQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPuwA"), 0, 91561894446285001782438967260723928368560331318344957259023550817453781559870); + verifyAddr(address("kQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPgpP"), 0, 91561894446285001782438967260723928368560331318344957259023550817453781559870); + verifyAddr(address("kf-Dfdg-YQXaR2Q97gZJ4fGBtmV1DHOU1y1RPyyZZtRy_Ikh"), -1, 59475331506450494976393625198911249698879029820580340449086829444312920781564); + verifyAddr(address("0:527964d55cfa6eb731f4bfc07e9d025098097ef8505519e853986279bd8400d8"), 0, 37304138005561100291416421295333982606153966175434134130332440738068913455320); + verifyAddr(address("0:0000000000000000000000000000000000000000000000000000000000000000"), 0, 0); + verifyAddr(address("0:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFfffffffffffffffffffffffffffff"), 0, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + verifyAddr(address("0:0000000000000000000000000000000000000000000000000000000000000000"), 0, 0); + verifyAddr(address("1:527964d55cfa6eb731f4bfc07e9d025098097ef8505519e853986279bd8400d8"), 1, 37304138005561100291416421295333982606153966175434134130332440738068913455320); + verifyAddr(address("9:527964d55cfa6eb731f4bfc07e9d025098097ef8505519e853986279bd8400d8"), 9, 37304138005561100291416421295333982606153966175434134130332440738068913455320); + verifyAddr(address("99:527964d55cfa6eb731f4bfc07e9d025098097ef8505519e853986279bd8400d8"), 99, 37304138005561100291416421295333982606153966175434134130332440738068913455320); + verifyAddr(address("-1:527964d55cfa6eb731f4bfc07e9d025098097ef8505519e853986279bd8400d8"), -1, 37304138005561100291416421295333982606153966175434134130332440738068913455320); - return cc1.bitsEqual(cc2); + return ( + cc1 == cc2, + cc2 != cc1, + (cc1 as slice).bitsEqual(((cc2 as slice) as address) as slice), + createAddressNone() == cc1, + createAddressNone() == createAddressNone(), + check0(address("0:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFfffffffffffffffffffffffffffff")) + ); } /** -@testcase | 0 | | -1 +@testcase | 0 | | -1 0 -1 0 -1 + +@fif_codegen +""" + check0 PROC:<{ // addr + REWRITESTDADDR + DROP // '2 + 0 EQINT // '4 + 111 THROWIFNOT // + }> +""" + +@fif_codegen +""" + codegenAddrEq PROC:<{ // a b + 2DUP // a b a b + SDEQ // a b '2 + IFJMP:<{ // a b + 2DROP // + 1 PUSHINT // '3=1 + }> // a b + SDEQ // '4 + IFNOTJMP:<{ // + 2 PUSHINT // '5=2 + }> // + 3 PUSHINT // '6=3 + }> +""" */ diff --git a/tolk-tester/tests/some-tests-2.tolk b/tolk-tester/tests/some-tests-2.tolk index b3b756b199..592cf96efc 100644 --- a/tolk-tester/tests/some-tests-2.tolk +++ b/tolk-tester/tests/some-tests-2.tolk @@ -89,6 +89,15 @@ fun test95() { return (cur, next); } +struct Point { + x: int, y: int +} + +@method_id(96) +fun test96(p: Point) { + debug.print(p); // works ok with non-1 stack width, checked via codegen +} + /** method_id | in | out @testcase | 0 | 101 15 | 100 1 @@ -153,4 +162,11 @@ fun test95() { next GETGLOB // g_cur g_next }> """ + +@fif_codegen +""" + test96 PROC:<{ + s1 DUMP s0 DUMP 2 BLKDROP + }> +""" */ diff --git a/tolk-tester/tests/strings-tests.tolk b/tolk-tester/tests/strings-tests.tolk index 9feed3a1c2..287dc0df3c 100644 --- a/tolk-tester/tests/strings-tests.tolk +++ b/tolk-tester/tests/strings-tests.tolk @@ -6,8 +6,8 @@ get raw_slice(): slice { return stringHexToSlice("abcdef"); } -get addr_slice(): slice { - return stringAddressToSlice("Ef8zMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzM0vF"); +get addr_slice(): address { + return address("Ef8zMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzM0vF"); } get string_hex(): int { @@ -51,7 +51,7 @@ fun calcSliceBytes(slice: slice): tuple { fun main() { var s_ascii: slice = ascii_slice(); var s_raw: slice = raw_slice(); - var s_addr: slice = addr_slice(); + var s_addr: address = addr_slice(); var i_hex: int = string_hex(); var i_mini: int = string_minihash(); var i_maxi: int = string_maxihash(); @@ -59,7 +59,7 @@ fun main() { var i_crc16: int = string_crc16(); 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) + assert(sdeq(s_addr as slice, newc().storeUint(4, 3).storeInt(-1, 8) .storeUint(0x3333333333333333333333333333333333333333333333333333333333333333, 256).endcs()), 103); assert(i_hex == 0x4142434445464748494A4B4C4D4E4F505152535455565758595A303132333435) throw 104; assert(i_mini == 0x7a62e8a8) throw 105; @@ -78,5 +78,5 @@ fun test1() { @testcase | 0 | | 0 @testcase | 101 | | [ 65 66 67 68 ] -@code_hash 55974318379341089957961227475446008591490555692181953973486962465702042912657 +@code_hash 58447050269190289721084012139099481162782788646785441106022886746601529758643 */ diff --git a/tolk-tester/tests/struct-tests.tolk b/tolk-tester/tests/struct-tests.tolk index a484475a21..46da0207c0 100644 --- a/tolk-tester/tests/struct-tests.tolk +++ b/tolk-tester/tests/struct-tests.tolk @@ -98,6 +98,7 @@ fun test3() { @method_id(104) fun test4() { var p: Point = { x: 10, y: 20 }; + assert(sizeof(p) == 2, 100); return (p == null, p, p = {x:30,y:40}, p != null); } @@ -487,6 +488,81 @@ fun test34() { return o1; } +@pure fun getXPure() { return 1; } +@pure fun getYPure() { return 2; } +global t_impure: tuple; +fun getXImpure() { t_impure.push(1); return 1; } +fun getYImpure() { t_impure.push(2); return 2; } + +@method_id(135) +fun test35() { + var p: Point = { + y: getYPure(), + x: getXPure(), + }; + return p; +} + +@method_id(136) +fun test36() { + t_impure = createEmptyTuple(); + var p: Point = { + y: getYImpure(), + x: getXImpure(), + }; + return (p, t_impure); +} + +@method_id(137) +fun test37() { + var num = 0; + var p: Point = { + y: num += 5, + x: (num *= 10) - 2, + }; + return (p, num); +} + +@method_id(138) +fun test38(num: int) { + var p: Point = { + y: num += 5, + x: (num *= 10) - 2, + }; + return (p, num); +} + +struct TwoPoints { + p1: Point; + p2: Point; +} + +@method_id(139) +fun test39(): (TwoPoints, int) { + var cs = stringHexToSlice("0102030405"); + return ({ + p2: { y: cs.loadUint(8), x: cs.loadUint(8) }, + p1: { x: cs.loadUint(8), y: cs.loadUint(8) }, + }, cs.remainingBitsCount()); +} + +@method_id(140) +fun test40() { + var cs = stringHexToSlice("0102030405"); + return TwoPoints { + p1: { y: cs.loadUint(8), x: cs.loadUint(8) }, + p2: { y: cs.loadUint(8), x: cs.loadUint(8) }, + } +} + +@method_id(141) +fun test41(rev: bool) { + var cs = stringHexToSlice("0102030405"); + return TwoPoints { + p2: rev ? { y: cs.loadUint(8), x: cs.loadUint(8) } : { x: cs.loadUint(8), y: cs.loadUint(8) }, + p1: rev ? { y: cs.loadUint(8), x: cs.loadUint(8) } : { x: cs.loadUint(8), y: cs.loadUint(8) }, + } +} fun main(x: int8, y: MInt) { __expect_type(PointAlias{x,y}, "Point"); @@ -539,6 +615,14 @@ type PointAlias = Point; @testcase | 132 | | 10 20 10 0 0 20 0 0 0 0 @testcase | 133 | | -1 0 5 (null) (null) (null) 0 0 46 @testcase | 134 | | 10 20 +@testcase | 135 | | 1 2 +@testcase | 136 | | 1 2 [ 2 1 ] +@testcase | 137 | | 48 5 50 +@testcase | 138 | 3 | 78 8 80 +@testcase | 139 | | 3 4 2 1 8 +@testcase | 140 | | 2 1 4 3 +@testcase | 141 | -1 | 4 3 2 1 +@testcase | 141 | 0 | 3 4 1 2 @fif_codegen """ @@ -571,4 +655,25 @@ type PointAlias = Point; }> """ +@fif_codegen +""" + test35 PROC:<{ // + getXPure CALLDICT // '2 + getYPure CALLDICT // p.x p.y + }> +""" + +@fif_codegen +""" + test36 PROC:<{ // + NIL // '0 + t_impure SETGLOB // + getYImpure CALLDICT // '4 + getXImpure CALLDICT // p.y p.x + t_impure GETGLOB // p.y p.x g_t_impure + s1 s2 XCHG // p.x p.y g_t_impure + }> +""" + + */ diff --git a/tolk-tester/tests/try-catch-tests.tolk b/tolk-tester/tests/try-catch-tests.tolk index b0a50aacb3..be0362e395 100644 --- a/tolk-tester/tests/try-catch-tests.tolk +++ b/tolk-tester/tests/try-catch-tests.tolk @@ -236,6 +236,16 @@ fun testCodegen3(numberId: int, paramVal: cell) { paramVal.beginParse(); } +@method_id(113) +fun testBigExcno() { + try { + throw 2048; + return 10; + } catch (excno) { + return excno; + } +} + fun main() { } @@ -264,8 +274,9 @@ fun main() { @testcase | 110 | 0 | 5 @testcase | 111 | -1 | 123 @testcase | 111 | 0 | 456 +@testcase | 113 | | 2048 -@code_hash 57361460846265694653029920796509802052573595128418810728101968091567195330515 +@code_hash 26411074751358281917876479601714698321674833208179417883888954015536273820035 @fif_codegen """ @@ -317,4 +328,12 @@ fun main() { DROP // }> """ + +@fif_codegen +""" +<{ + 11 PUSHPOW2 + THROWANY +}>CONT +""" */ diff --git a/tolk-tester/tests/type-aliases-tests.tolk b/tolk-tester/tests/type-aliases-tests.tolk index ed0cca11d2..5d725ecf5a 100644 --- a/tolk-tester/tests/type-aliases-tests.tolk +++ b/tolk-tester/tests/type-aliases-tests.tolk @@ -1,3 +1,5 @@ +import "@stdlib/tvm-dicts.tolk" + type MIntN = MInt?; type MInt = int; type MInt_v2 = int; @@ -175,6 +177,27 @@ fun test11() { __expect_type(analyzeTensor2(x3), "(MInt, MInt)?"); } +fun dict.getFakeDepth(self) { return 1; } +fun cell.getFakeDepth(self) { return 2; } + +@method_id(112) +fun test12(makeNotNull: bool) { + var d = createEmptyDict(); + if (makeNotNull) { + d.iDictSet(32, 123, ""); + } + var t = createEmptyTuple(); + if (d != null) { + __expect_type(d.getFakeDepth, "(cell) -> int"); + t.push(d.getFakeDepth()); + } else { + __expect_type(d.getFakeDepth, "(dict) -> int"); + t.push(d.getFakeDepth()); + } + t.push(d.getFakeDepth()); + return t; +} + fun main(x: MInt, y: MInt?) { return y == null ? x : x + y; @@ -184,6 +207,8 @@ fun main(x: MInt, y: MInt?) { @testcase | 0 | 3 4 | 7 @testcase | 104 | 1 2 | 3 @testcase | 110 | | 41 +@testcase | 112 | 0 | [ 1 1 ] +@testcase | 112 | -1 | [ 2 1 ] @fif_codegen """ diff --git a/tolk-tester/tests/var-apply-tests.tolk b/tolk-tester/tests/var-apply-tests.tolk index 1c0f164cba..825b48b96b 100644 --- a/tolk-tester/tests/var-apply-tests.tolk +++ b/tolk-tester/tests/var-apply-tests.tolk @@ -314,6 +314,17 @@ fun testMethodsOfGenericStruct() { return (eq(w1, w2), w2.value = w1.value as int, eq(w1, w2), creator3()); } +fun add3WithDefaults(a: int, b: int = 0, c: int = 0) { + return a + b + c; +} + +@method_id(119) +fun testSavingFunWithDefaults() { + var cc = add3WithDefaults; + __expect_type(cc, "(int, int, int) -> int"); + return cc(1, 2, 3); +} + fun main() {} @@ -343,4 +354,5 @@ fun main() {} @testcase | 116 | | 7 11 80 @testcase | 117 | | 6 8 @testcase | 118 | | 0 10 -1 (null) +@testcase | 119 | | 6 */ diff --git a/tolk/CMakeLists.txt b/tolk/CMakeLists.txt index c6e992c270..7aca836882 100644 --- a/tolk/CMakeLists.txt +++ b/tolk/CMakeLists.txt @@ -6,6 +6,8 @@ set(TOLK_SOURCE ast.cpp ast-from-tokens.cpp constant-evaluator.cpp + pack-unpack-api.cpp + pack-unpack-serializers.cpp pipe-discover-parse-sources.cpp pipe-register-symbols.cpp pipe-resolve-identifiers.cpp @@ -16,6 +18,7 @@ set(TOLK_SOURCE pipe-refine-lvalue-for-mutate.cpp pipe-check-rvalue-lvalue.cpp pipe-check-pure-impure.cpp + pipe-check-serialized-fields.cpp pipe-constant-folding.cpp pipe-optimize-boolean-expr.cpp pipe-ast-to-legacy.cpp diff --git a/tolk/abscode.cpp b/tolk/abscode.cpp index 02215d8847..462aad7b2d 100644 --- a/tolk/abscode.cpp +++ b/tolk/abscode.cpp @@ -436,4 +436,15 @@ std::vector CodeBlob::create_var(TypePtr var_type, SrcLocation loc, s return ir_idx; } +var_idx_t CodeBlob::create_int(SrcLocation loc, int64_t value, const char* desc) { + vars.emplace_back(var_cnt, TypeDataInt::create(), std::string{}, loc); +#ifdef TOLK_DEBUG + vars.back().desc = desc; +#endif + var_idx_t ir_int = var_cnt; + var_cnt++; + emplace_back(loc, Op::_IntConst, std::vector{ir_int}, td::make_refint(value)); + return ir_int; +} + } // namespace tolk diff --git a/tolk/ast-from-tokens.cpp b/tolk/ast-from-tokens.cpp index ff13ed386f..0ca8b3aebe 100644 --- a/tolk/ast-from-tokens.cpp +++ b/tolk/ast-from-tokens.cpp @@ -226,10 +226,6 @@ static AnyTypeV parse_type_nullable(Lexer& lex) { } static AnyTypeV parse_type_expression(Lexer& lex) { - if (lex.tok() == tok_bitwise_or) { // `type T = | T1 | T2 | ...` (each per line) (like in TypeScript) - lex.next(); - } - AnyTypeV result = parse_type_nullable(lex); if (lex.tok() == tok_bitwise_or) { // `int | slice`, `Pair2 | (Pair3 | null)` @@ -237,6 +233,9 @@ static AnyTypeV parse_type_expression(Lexer& lex) { items.emplace_back(result); while (lex.tok() == tok_bitwise_or) { lex.next(); + if (lex.tok() == tok_clpar || lex.tok() == tok_clbracket || lex.tok() == tok_semicolon) { + break; // allow trailing `|` (not leading, like in TypeScript, because of tree-sitter) + } items.emplace_back(parse_type_nullable(lex)); } result = createV(result->loc, std::move(items)); @@ -332,13 +331,20 @@ static AnyV parse_parameter(Lexer& lex, AnyTypeV self_type) { lex.error("`self` parameter should not have a type"); } - return createV(loc, param_name, param_type, declared_as_mutate); + // optional default value + AnyExprV default_value = nullptr; + if (lex.tok() == tok_assign && !is_self) { // `a: int = 0` + if (declared_as_mutate) { + lex.error("`mutate` parameter can't have a default value"); + } + lex.next(); + default_value = parse_expr(lex); + } + + return createV(loc, param_name, param_type, default_value, declared_as_mutate); } static AnyV parse_global_var_declaration(Lexer& lex, const std::vector>& annotations) { - if (!annotations.empty()) { - lex.error("@annotations are not applicable to global var declaration"); - } SrcLocation loc = lex.cur_location(); lex.expect(tok_global, "`global`"); lex.check(tok_identifier, "global variable name"); @@ -353,13 +359,21 @@ static AnyV parse_global_var_declaration(Lexer& lex, const std::vectorkind) { + case AnnotationKind::deprecated: + case AnnotationKind::custom: + break; + default: + v_annotation->error("this annotation is not applicable to global"); + } + } + return createV(loc, v_ident, declared_type); } static AnyV parse_constant_declaration(Lexer& lex, const std::vector>& annotations) { - if (!annotations.empty()) { - lex.error("@annotations are not applicable to global var declaration"); - } SrcLocation loc = lex.cur_location(); lex.expect(tok_const, "`const`"); lex.check(tok_identifier, "constant name"); @@ -376,13 +390,21 @@ static AnyV parse_constant_declaration(Lexer& lex, const std::vectorkind) { + case AnnotationKind::deprecated: + case AnnotationKind::custom: + break; + default: + v_annotation->error("this annotation is not applicable to constant"); + } + } + return createV(loc, v_ident, declared_type, init_value); } static AnyV parse_type_alias_declaration(Lexer& lex, const std::vector>& annotations) { - if (!annotations.empty()) { - lex.error("@annotations are not applicable to type alias declaration"); - } SrcLocation loc = lex.cur_location(); lex.expect(tok_type, "`type`"); lex.check(tok_identifier, "type name"); @@ -397,14 +419,25 @@ static AnyV parse_type_alias_declaration(Lexer& lex, const std::vectorkind) { + case AnnotationKind::deprecated: + case AnnotationKind::custom: + break; + default: + v_annotation->error("this annotation is not applicable to type alias"); + } + } + return createV(loc, v_ident, genericsT_list, underlying_type); } -static AnyExprV parse_var_declaration_lhs(Lexer& lex, bool is_immutable) { +static AnyExprV parse_var_declaration_lhs(Lexer& lex, bool is_immutable, bool allow_lateinit) { SrcLocation loc = lex.cur_location(); if (lex.tok() == tok_oppar) { lex.next(); - AnyExprV first = parse_var_declaration_lhs(lex, is_immutable); + AnyExprV first = parse_var_declaration_lhs(lex, is_immutable, false); if (lex.tok() == tok_clpar) { lex.next(); return first; @@ -412,17 +445,17 @@ static AnyExprV parse_var_declaration_lhs(Lexer& lex, bool is_immutable) { std::vector args(1, first); while (lex.tok() == tok_comma) { lex.next(); - args.push_back(parse_var_declaration_lhs(lex, is_immutable)); + args.push_back(parse_var_declaration_lhs(lex, is_immutable, false)); } lex.expect(tok_clpar, "`)`"); return createV(loc, std::move(args)); } if (lex.tok() == tok_opbracket) { lex.next(); - std::vector args(1, parse_var_declaration_lhs(lex, is_immutable)); + std::vector args(1, parse_var_declaration_lhs(lex, is_immutable, false)); while (lex.tok() == tok_comma) { lex.next(); - args.push_back(parse_var_declaration_lhs(lex, is_immutable)); + args.push_back(parse_var_declaration_lhs(lex, is_immutable, false)); } lex.expect(tok_clbracket, "`]`"); return createV(loc, std::move(args)); @@ -431,6 +464,7 @@ static AnyExprV parse_var_declaration_lhs(Lexer& lex, bool is_immutable) { auto v_ident = createV(loc, lex.cur_str()); AnyTypeV declared_type = nullptr; bool marked_as_redef = false; + bool is_lateinit = false; lex.next(); if (lex.tok() == tok_colon) { lex.next(); @@ -439,7 +473,13 @@ static AnyExprV parse_var_declaration_lhs(Lexer& lex, bool is_immutable) { lex.next(); marked_as_redef = true; } - return createV(loc, v_ident, declared_type, is_immutable, marked_as_redef); + if (lex.tok() == tok_semicolon && allow_lateinit) { + if (declared_type == nullptr) { + lex.error("provide a type for a variable, because its default value is omitted:\n> var " + static_cast(v_ident->name) + ": ;"); + } + is_lateinit = true; + } + return createV(loc, v_ident, declared_type, is_immutable, is_lateinit, marked_as_redef); } if (lex.tok() == tok_underscore) { AnyTypeV declared_type = nullptr; @@ -448,18 +488,21 @@ static AnyExprV parse_var_declaration_lhs(Lexer& lex, bool is_immutable) { lex.next(); declared_type = parse_type_from_tokens(lex); } - return createV(loc, createV(loc, ""), declared_type, true, false); + return createV(loc, createV(loc, ""), declared_type, true, false, false); } lex.unexpected("variable name"); } -static AnyExprV parse_local_vars_declaration_assignment(Lexer& lex) { +static AnyExprV parse_local_vars_declaration(Lexer& lex, bool allow_lateinit) { SrcLocation loc = lex.cur_location(); bool is_immutable = lex.tok() == tok_val; lex.next(); - AnyExprV lhs = createV(loc, parse_var_declaration_lhs(lex, is_immutable)); + AnyExprV lhs = parse_var_declaration_lhs(lex, is_immutable, allow_lateinit); if (lex.tok() != tok_assign) { + if (auto lhs_var = lhs->try_as(); lhs_var && lhs_var->is_lateinit) { + return lhs; // just ast_local_var_lhs inside AST tree + } lex.error("variables declaration must be followed by assignment: `var xxx = ...`"); } lex.next(); @@ -468,7 +511,7 @@ static AnyExprV parse_local_vars_declaration_assignment(Lexer& lex) { if (lex.tok() == tok_comma) { lex.error("multiple declarations are not allowed, split variables on separate lines"); } - return createV(loc, lhs, rhs); + return createV(loc, createV(loc, lhs), rhs); } // "parameters" are at function declaration: `fun f(param1: int, mutate param2: slice)` @@ -666,7 +709,7 @@ static V parse_match_expression(Lexer& lex) { lex.expect(tok_oppar, "`(`"); AnyExprV subject = lex.tok() == tok_var || lex.tok() == tok_val // `match (var x = rhs)` - ? parse_local_vars_declaration_assignment(lex) + ? parse_local_vars_declaration(lex, false) : parse_expr(lex); lex.expect(tok_clpar, "`)`"); @@ -1179,7 +1222,7 @@ AnyV parse_statement(Lexer& lex) { switch (lex.tok()) { case tok_var: // `var x = 0` is technically an expression, but can not appear in "any place", case tok_val: // only as a separate declaration - return parse_local_vars_declaration_assignment(lex); + return parse_local_vars_declaration(lex, true); case tok_opbrace: return parse_block_statement(lex); case tok_return: @@ -1293,6 +1336,7 @@ static V parse_annotation(Lexer& lex) { v_arg = createV(loc, {}); break; case AnnotationKind::deprecated: + case AnnotationKind::custom: // allowed with and without arguments; it's IDE-only, the compiler doesn't analyze @deprecated break; case AnnotationKind::method_id: @@ -1300,9 +1344,15 @@ static V parse_annotation(Lexer& lex) { throw ParseError(loc, "expecting `(number)` after " + static_cast(name)); } break; + case AnnotationKind::overflow1023_policy: { + if (!v_arg || v_arg->size() != 1 || v_arg->get_item(0)->kind != ast_string_const) { + throw ParseError(loc, "expecting `(\"policy_name\")` after " + static_cast(name)); + } + break; + } } - return createV(loc, kind, v_arg); + return createV(loc, name, kind, v_arg); } static AnyV parse_function_declaration(Lexer& lex, const std::vector>& annotations) { @@ -1426,7 +1476,7 @@ static AnyV parse_function_declaration(Lexer& lex, const std::vector(lex.cur_location()); } return createV(loc, v_ident, default_value, declared_type); @@ -1476,10 +1524,23 @@ static V parse_struct_body(Lexer& lex) { static AnyV parse_struct_declaration(Lexer& lex, const std::vector>& annotations) { SrcLocation loc = lex.cur_location(); - if (!annotations.empty()) { - lex.error("@annotations are not applicable to type alias declaration"); - } lex.expect(tok_struct, "`struct`"); + + AnyExprV opcode = nullptr; + if (lex.tok() == tok_oppar) { // struct(0x0012) CounterIncrement + lex.next(); + lex.check(tok_int_const, "opcode `0x...` or `0b...`"); + std::string_view opcode_str = lex.cur_str(); + if (!opcode_str.starts_with("0x") && !opcode_str.starts_with("0b")) { + lex.unexpected("opcode `0x...` or `0b...`"); + } + opcode = createV(lex.cur_location(), parse_tok_int_const(opcode_str), opcode_str); + lex.next(); + lex.expect(tok_clpar, "`)`"); + } else { + opcode = createV(lex.cur_location()); + } + lex.check(tok_identifier, "identifier"); auto v_ident = createV(lex.cur_location(), lex.cur_str()); lex.next(); @@ -1489,7 +1550,27 @@ static AnyV parse_struct_declaration(Lexer& lex, const std::vector(loc, v_ident, genericsT_list, parse_struct_body(lex)); + StructData::Overflow1023Policy overflow1023_policy = StructData::Overflow1023Policy::not_specified; + for (auto v_annotation : annotations) { + switch (v_annotation->kind) { + case AnnotationKind::deprecated: + case AnnotationKind::custom: + break; + case AnnotationKind::overflow1023_policy: { + std::string_view str = v_annotation->get_arg()->get_item(0)->as()->str_val; + if (str == "suppress") { + overflow1023_policy = StructData::Overflow1023Policy::suppress; + } else { + v_annotation->error("incorrect value for " + static_cast(v_annotation->name)); + } + break; + } + default: + v_annotation->error("this annotation is not applicable to struct"); + } + } + + return createV(loc, v_ident, genericsT_list, overflow1023_policy, opcode, parse_struct_body(lex)); } static AnyV parse_tolk_required_version(Lexer& lex) { diff --git a/tolk/ast-replacer.h b/tolk/ast-replacer.h index 382f2bd715..03e7d43206 100644 --- a/tolk/ast-replacer.h +++ b/tolk/ast-replacer.h @@ -200,7 +200,7 @@ class ASTReplacerInFunctionBody : public ASTReplacer { public: virtual bool should_visit_function(FunctionPtr fun_ref) = 0; - void start_replacing_in_function(FunctionPtr fun_ref, V v_function) { + virtual void start_replacing_in_function(FunctionPtr fun_ref, V v_function) { replace(v_function->get_body()); } }; diff --git a/tolk/ast-replicator.h b/tolk/ast-replicator.h index fc1dafa22b..03a9b0a53b 100644 --- a/tolk/ast-replicator.h +++ b/tolk/ast-replicator.h @@ -97,7 +97,7 @@ class ASTReplicator final { return createV(v->loc, clone(v->get_identifier()), v->has_instantiationTs() ? clone(v->get_instantiationTs()) : nullptr); } static V clone(V v) { - return createV(v->loc, clone(v->get_identifier()), clone(v->type_node), v->is_immutable, v->marked_as_redef); + return createV(v->loc, clone(v->get_identifier()), clone(v->type_node), v->is_immutable, v->is_lateinit, v->marked_as_redef); } static V clone(V v) { return createV(v->loc, clone(v->get_expr())); @@ -223,13 +223,13 @@ class ASTReplicator final { return createV(v->loc, clone(v->get_items())); } static V clone(V v) { - return createV(v->loc, v->param_name, clone(v->type_node), v->declared_as_mutate); + return createV(v->loc, v->param_name, clone(v->type_node), v->default_value ? clone(v->default_value) : nullptr, v->declared_as_mutate); } static V clone(V v) { return createV(v->loc, clone(v->get_params())); } static V clone(V v) { - return createV(v->loc, clone(v->get_identifier()), clone(v->get_default_value()), clone(v->type_node)); + return createV(v->loc, clone(v->get_identifier()), v->default_value ? clone(v->default_value) : nullptr, clone(v->type_node)); } static V clone(V v) { return createV(v->loc, clone(v->get_all_fields())); @@ -345,6 +345,8 @@ class ASTReplicator final { v_orig->loc, new_name_ident, clone(v_orig->genericsT_list), + v_orig->overflow1023_policy, + v_orig->has_opcode() ? static_cast(clone(v_orig->get_opcode())) : createV(v_orig->loc), clone(v_orig->get_struct_body()) ); } diff --git a/tolk/ast-stringifier.h b/tolk/ast-stringifier.h index 8ced76a0ba..395f956811 100644 --- a/tolk/ast-stringifier.h +++ b/tolk/ast-stringifier.h @@ -107,16 +107,6 @@ class ASTStringifier final : public ASTVisitor { static_assert(std::size(name_pairs) == ast_tolk_file + 1, "name_pairs needs to be updated"); - constexpr static std::pair annotation_kinds[] = { - {AnnotationKind::inline_simple, "@inline"}, - {AnnotationKind::inline_ref, "@inline_ref"}, - {AnnotationKind::method_id, "@method_id"}, - {AnnotationKind::pure, "@pure"}, - {AnnotationKind::deprecated, "@deprecated"}, - }; - - static_assert(std::size(annotation_kinds) == static_cast(AnnotationKind::unknown), "annotation_kinds needs to be updated"); - template constexpr static const char* ast_node_kind_to_string() { return name_pairs[node_kind].second; @@ -205,7 +195,7 @@ class ASTStringifier final : public ASTVisitor { case ast_if_statement: return v->as()->is_ifnot ? "ifnot" : ""; case ast_annotation: - return annotation_kinds[static_cast(v->as()->kind)].second; + return static_cast(v->as()->name); case ast_parameter: return static_cast(v->as()->param_name) + ": " + ast_type_node_to_string(v->as()->type_node); case ast_function_declaration: { diff --git a/tolk/ast.cpp b/tolk/ast.cpp index 6e725b7816..59a18a1631 100644 --- a/tolk/ast.cpp +++ b/tolk/ast.cpp @@ -66,6 +66,12 @@ AnnotationKind Vertex::parse_kind(std::string_view name) { if (name == "@deprecated") { return AnnotationKind::deprecated; } + if (name == "@custom") { + return AnnotationKind::custom; + } + if (name == "@overflow1023_policy") { + return AnnotationKind::overflow1023_policy; + } return AnnotationKind::unknown; } diff --git a/tolk/ast.h b/tolk/ast.h index ee550064c4..cf82a0a178 100644 --- a/tolk/ast.h +++ b/tolk/ast.h @@ -141,6 +141,8 @@ enum class AnnotationKind { method_id, pure, deprecated, + custom, + overflow1023_policy, unknown, }; @@ -584,6 +586,7 @@ struct Vertex final : ASTExprLeaf { LocalVarPtr var_ref = nullptr; // filled on resolve identifiers; for `redef` points to declared above; for underscore, name is empty AnyTypeV type_node; // exists for `var x: int = rhs`, otherwise nullptr bool is_immutable; // declared via 'val', not 'var' + bool is_lateinit; // var st: Storage lateinit (no assignment) bool marked_as_redef; // var (existing_var redef, new_var: int) = ... V get_identifier() const { return identifier; } @@ -592,9 +595,9 @@ struct Vertex final : ASTExprLeaf { Vertex* mutate() const { return const_cast(this); } void assign_var_ref(LocalVarPtr var_ref); - Vertex(SrcLocation loc, V identifier, AnyTypeV type_node, bool is_immutable, bool marked_as_redef) + Vertex(SrcLocation loc, V identifier, AnyTypeV type_node, bool is_immutable, bool is_lateinit, bool marked_as_redef) : ASTExprLeaf(ast_local_var_lhs, loc) - , identifier(identifier), type_node(type_node), is_immutable(is_immutable), marked_as_redef(marked_as_redef) {} + , identifier(identifier), type_node(type_node), is_immutable(is_immutable), is_lateinit(is_lateinit), marked_as_redef(marked_as_redef) {} }; template<> @@ -1176,16 +1179,18 @@ struct Vertex final : ASTOtherVararg { template<> // ast_parameter is a parameter of a function in its declaration // example: `fun f(a: int, mutate b: slice)` has 2 parameters +// example: `fun f(a: int = 0)` has 1 parameter with default value struct Vertex final : ASTOtherLeaf { std::string_view param_name; AnyTypeV type_node; // always exists, typing parameters is mandatory + AnyExprV default_value; // default value of the parameter or nullptr bool declared_as_mutate; // declared as `mutate param_name` bool is_underscore() const { return param_name.empty(); } - Vertex(SrcLocation loc, std::string_view param_name, AnyTypeV type_node, bool declared_as_mutate) + Vertex(SrcLocation loc, std::string_view param_name, AnyTypeV type_node, AnyExprV default_value, bool declared_as_mutate) : ASTOtherLeaf(ast_parameter, loc) - , param_name(param_name), type_node(type_node), declared_as_mutate(declared_as_mutate) {} + , param_name(param_name), type_node(type_node), default_value(default_value), declared_as_mutate(declared_as_mutate) {} }; template<> @@ -1207,15 +1212,16 @@ template<> // ast_annotation is @annotation above a declaration // example: `@pure fun ...` struct Vertex final : ASTOtherVararg { + std::string_view name; AnnotationKind kind; auto get_arg() const { return children.at(0)->as(); } static AnnotationKind parse_kind(std::string_view name); - Vertex(SrcLocation loc, AnnotationKind kind, V arg_probably_empty) + Vertex(SrcLocation loc, std::string_view name, AnnotationKind kind, V arg_probably_empty) : ASTOtherVararg(ast_annotation, loc, {arg_probably_empty}) - , kind(kind) {} + , name(name), kind(kind) {} }; template<> @@ -1310,14 +1316,13 @@ template<> // example: `struct Point { x: int, y: int }` is struct declaration, its body contains 2 fields struct Vertex final : ASTOtherVararg { AnyTypeV type_node; // always exists, typing struct fields is mandatory + AnyExprV default_value; // nullptr if no default auto get_identifier() const { return children.at(0)->as(); } - bool has_default_value() const { return children.at(1)->kind != ast_empty_expression; } - auto get_default_value() const { return child_as_expr(1); } Vertex(SrcLocation loc, V name_identifier, AnyExprV default_value, AnyTypeV type_node) - : ASTOtherVararg(ast_struct_field, loc, {name_identifier, default_value}) - , type_node(type_node) {} + : ASTOtherVararg(ast_struct_field, loc, {name_identifier}) + , type_node(type_node), default_value(default_value) {} }; template<> @@ -1335,20 +1340,24 @@ struct Vertex final : ASTOtherVararg { template<> // ast_struct_declaration is declaring a struct with fields (each having declared_type), like interfaces in TypeScript // example: `struct Storage { owner: User; validUntil: int }` +// example: `struct(0x0012) CounterIncrement { byValue: int32; }` (0x0012 is opcode, len 16) // currently, Tolk doesn't have "implements" or whatever, so struct declaration contains only body struct Vertex final : ASTOtherVararg { - StructPtr struct_ref = nullptr; // filled after register + StructPtr struct_ref = nullptr; // filled after register V genericsT_list; // exists for `Wrapper`; otherwise, nullptr + StructData::Overflow1023Policy overflow1023_policy; auto get_identifier() const { return children.at(0)->as(); } - auto get_struct_body() const { return children.at(1)->as(); } + bool has_opcode() const { return children.at(1)->kind != ast_empty_expression; } + auto get_opcode() const { return children.at(1)->as(); } + auto get_struct_body() const { return children.at(2)->as(); } Vertex* mutate() const { return const_cast(this); } void assign_struct_ref(StructPtr struct_ref); - Vertex(SrcLocation loc, V name_identifier, V genericsT_list, V struct_body) - : ASTOtherVararg(ast_struct_declaration, loc, {name_identifier, struct_body}) - , genericsT_list(genericsT_list) {} + Vertex(SrcLocation loc, V name_identifier, V genericsT_list, StructData::Overflow1023Policy overflow1023_policy, AnyExprV opcode, V struct_body) + : ASTOtherVararg(ast_struct_declaration, loc, {name_identifier, opcode, struct_body}) + , genericsT_list(genericsT_list), overflow1023_policy(overflow1023_policy) {} }; template<> diff --git a/tolk/builtins.cpp b/tolk/builtins.cpp index 70944cd4c2..39075452de 100644 --- a/tolk/builtins.cpp +++ b/tolk/builtins.cpp @@ -18,6 +18,7 @@ #include "compiler-state.h" #include "type-system.h" #include "generics-helpers.h" +#include "ast.h" namespace tolk { using namespace std::literals::string_literals; @@ -32,7 +33,7 @@ static std::vector define_builtin_parameters(const std::vector(params_types.size()); ++i) { - LocalVarData p_sym("", {}, params_types[i], (i == 0 && is_mutate_self) * LocalVarData::flagMutateParameter, i); + LocalVarData p_sym("", {}, params_types[i], nullptr, (i == 0 && is_mutate_self) * LocalVarData::flagMutateParameter, i); parameters.push_back(std::move(p_sym)); } @@ -938,7 +939,9 @@ static AsmOp compile_cmp_int(std::vector& res, std::vector& static AsmOp compile_throw(std::vector& res, std::vector& args, SrcLocation loc) { tolk_assert(res.empty() && args.size() == 1); VarDescr& x = args[0]; - if (x.is_int_const() && x.int_const->unsigned_fits_bits(11)) { + if (x.is_int_const() && x.int_const >= 0) { + // in Fift assembler, "N THROW" is valid if N < 2048; for big N (particularly, widely used 0xFFFF) + // we now still generate "N THROW", and later, in optimizer, transform it to "PUSHINT" + "THROWANY" x.unused(); return exec_arg_op(loc, "THROW", x.int_const, 0, 0); } else { @@ -1022,9 +1025,9 @@ static AsmOp compile_store_int(std::vector& res, std::vector auto& z = args[2]; if (z.is_int_const() && z.int_const > 0 && z.int_const <= 256) { z.unused(); - return exec_arg_op(loc, "ST"s + (sgnd ? 'I' : 'U'), z.int_const, 2, 1); + return exec_arg_op(loc, sgnd? "STI" : "STU", z.int_const, 2, 1); } - return exec_op(loc, "ST"s + (sgnd ? "IX" : "UX"), 3, 1); + return exec_op(loc, sgnd ? "STIX" : "STUX", 3, 1); } // fun slice.loadBits (mutate self, len: int): self asm(s len -> 1 0) "LDSLICEX" @@ -1043,6 +1046,22 @@ static AsmOp compile_fetch_slice(std::vector& res, std::vector& res, std::vector& args, SrcLocation loc) { + tolk_assert(args.size() == 3 && res.size() == 2); + auto& prefix = args[1]; + auto& prefix_len = args[2]; + if (prefix.is_int_const() && prefix.int_const >= 0 && prefix.int_const->signed_fits_bits(50) && + prefix_len.is_int_const() && prefix_len.int_const > 0 && prefix_len.int_const->signed_fits_bits(16)) { + prefix.unused(); + prefix_len.unused(); + StructData::PackOpcode opcode(prefix.int_const->to_long(), static_cast(prefix_len.int_const->to_long())); + return AsmOp::Custom(loc, opcode.format_as_slice() + " SDBEGINSQ", 0, 1); + } + throw ParseError(loc, "slice.tryStripPrefix can be used only with constant arguments"); +} + // fun tuple.get(t: tuple, index: int): X asm "INDEXVAR"; static AsmOp compile_tuple_get(std::vector& res, std::vector& args, SrcLocation loc) { tolk_assert(args.size() == 2 && res.size() == 1); @@ -1076,8 +1095,42 @@ static AsmOp compile_strdump(std::vector&, std::vector&, Src } // fun debug.print(x: T): void; -static AsmOp compile_debug_print_to_string(std::vector&, std::vector&, SrcLocation loc) { - return AsmOp::Custom(loc, "s0 DUMP DROP", 1, 1); +static AsmOp compile_debug_print_to_string(std::vector&, std::vector& args, SrcLocation loc) { + int n = static_cast(args.size()); + if (n == 1) { // most common case + return AsmOp::Custom(loc, "s0 DUMP DROP", 1, 1); + } + if (n > 15) { + throw ParseError(loc, "call overflow, exceeds 15 elements"); + } + std::string cmd; + for (int i = n - 1; i >= 0; --i) { + cmd += "s" + std::to_string(i) + " DUMP "; + } + cmd += std::to_string(n); + cmd += " BLKDROP"; + return AsmOp::Custom(loc, cmd, n, n); +} + +// fun T.__toTuple(self): void; (T can be any number of slots, it works for structs and tensors) +static AsmOp compile_any_object_to_tuple(std::vector& res, std::vector& args, SrcLocation loc) { + tolk_assert(res.size() == 1); + int n = static_cast(args.size()); + if (n > 15) { + throw ParseError(loc, "call overflow, exceeds 15 elements"); + } + return exec_op(loc, std::to_string(args.size()) + " TUPLE", n, 1); +} + +// fun sizeof(anything: T): int; // (returns the number of stack elements) +static AsmOp compile_any_object_sizeof(std::vector& res, std::vector& args, SrcLocation loc) { + tolk_assert(res.size() == 1); + int n = static_cast(args.size()); + res[0].set_const(n); + for (int i = 0; i < n; ++i) { + args[i].unused(); + } + return AsmOp::IntConst(loc, td::make_refint(n)); } // fun ton(amount: slice): coins; ton("0.05") replaced by 50000000 at compile-time @@ -1120,6 +1173,7 @@ void define_builtins() { TypePtr typeT = TypeDataGenericT::create("T"); const GenericsDeclaration* declGenericT = new GenericsDeclaration(std::vector{{"T", nullptr}}, 0); + const GenericsDeclaration* declReceiverT = new GenericsDeclaration(std::vector{{"T", nullptr}}, 1); std::vector ParamsInt1 = {Int}; std::vector ParamsInt2 = {Int, Int}; @@ -1129,6 +1183,9 @@ void define_builtins() { // these types are defined in stdlib, currently unknown // see patch_builtins_after_stdlib_loaded() below TypePtr debug = TypeDataUnknown::create(); + TypePtr CellT = TypeDataUnknown::create(); + TypePtr PackOptions = TypeDataUnknown::create(); + TypePtr UnpackOptions = TypeDataUnknown::create(); // builtin operators // they are internally stored as functions, because at IR level, there is no difference @@ -1247,28 +1304,28 @@ void define_builtins() { // note their parameter being `unknown`: in order to `ton(1)` pass type inferring but fire a more gentle error later define_builtin_func("ton", {TypeDataUnknown::create()}, TypeDataCoins::create(), nullptr, compile_time_only_function, - FunctionData::flagMarkedAsPure | FunctionData::flagCompileTimeOnly); + FunctionData::flagMarkedAsPure | FunctionData::flagCompileTimeVal); define_builtin_func("stringCrc32", {TypeDataUnknown::create()}, TypeDataInt::create(), nullptr, compile_time_only_function, - FunctionData::flagMarkedAsPure | FunctionData::flagCompileTimeOnly); + FunctionData::flagMarkedAsPure | FunctionData::flagCompileTimeVal); define_builtin_func("stringCrc16", {TypeDataUnknown::create()}, TypeDataInt::create(), nullptr, compile_time_only_function, - FunctionData::flagMarkedAsPure | FunctionData::flagCompileTimeOnly); + FunctionData::flagMarkedAsPure | FunctionData::flagCompileTimeVal); define_builtin_func("stringSha256", {TypeDataUnknown::create()}, TypeDataInt::create(), nullptr, compile_time_only_function, - FunctionData::flagMarkedAsPure | FunctionData::flagCompileTimeOnly); + FunctionData::flagMarkedAsPure | FunctionData::flagCompileTimeVal); define_builtin_func("stringSha256_32", {TypeDataUnknown::create()}, TypeDataInt::create(), nullptr, compile_time_only_function, - FunctionData::flagMarkedAsPure | FunctionData::flagCompileTimeOnly); + FunctionData::flagMarkedAsPure | FunctionData::flagCompileTimeVal); define_builtin_func("stringToBase256", {TypeDataUnknown::create()}, TypeDataInt::create(), nullptr, compile_time_only_function, - FunctionData::flagMarkedAsPure | FunctionData::flagCompileTimeOnly); - define_builtin_func("stringAddressToSlice", {TypeDataUnknown::create()}, TypeDataSlice::create(), nullptr, - compile_time_only_function, - FunctionData::flagMarkedAsPure | FunctionData::flagCompileTimeOnly); + FunctionData::flagMarkedAsPure | FunctionData::flagCompileTimeVal); define_builtin_func("stringHexToSlice", {TypeDataUnknown::create()}, TypeDataSlice::create(), nullptr, compile_time_only_function, - FunctionData::flagMarkedAsPure | FunctionData::flagCompileTimeOnly); + FunctionData::flagMarkedAsPure | FunctionData::flagCompileTimeVal); + define_builtin_func("address", {TypeDataUnknown::create()}, TypeDataAddress::create(), nullptr, + compile_time_only_function, + FunctionData::flagMarkedAsPure | FunctionData::flagCompileTimeVal); // functions from stdlib marked as `builtin`, implemented at compiler level for optimizations // (for example, `loadInt(1)` is `1 LDI`, but `loadInt(n)` for non-constant requires it be on a stack and `LDIX`) @@ -1305,6 +1362,9 @@ void define_builtins() { define_builtin_method("slice.preloadBits", Slice, ParamsSliceInt, Slice, nullptr, std::bind(compile_fetch_slice, _1, _2, _3, false), FunctionData::flagMarkedAsPure | FunctionData::flagAcceptsSelf); + define_builtin_method("slice.tryStripPrefix", Slice, {Slice, Int, Int}, Bool, nullptr, + compile_slice_sdbeginsq, + FunctionData::flagMarkedAsPure | FunctionData::flagHasMutateParams | FunctionData::flagAcceptsSelf); define_builtin_method("builder.storeInt", Builder, {Builder, Int, Int}, Unit, nullptr, std::bind(compile_store_int, _1, _2, _3, true), FunctionData::flagMarkedAsPure | FunctionData::flagHasMutateParams | FunctionData::flagAcceptsSelf | FunctionData::flagReturnsSelf, @@ -1321,13 +1381,49 @@ void define_builtins() { FunctionData::flagMarkedAsPure | FunctionData::flagHasMutateParams | FunctionData::flagAcceptsSelf); define_builtin_method("debug.print", debug, {typeT}, Unit, declGenericT, compile_debug_print_to_string, - 0); + FunctionData::flagAllowAnyWidthT); define_builtin_method("debug.printString", debug, {typeT}, Unit, declGenericT, compile_strdump, 0); define_builtin_method("debug.dumpStack", debug, {}, Unit, nullptr, compile_dumpstk, 0); + define_builtin_func("sizeof", {typeT}, TypeDataInt::create(), declGenericT, + compile_any_object_sizeof, + FunctionData::flagMarkedAsPure | FunctionData::flagAllowAnyWidthT); + + // serialization/deserialization methods to/from cells (or, more low-level, slices/builders) + // they work with structs (or, more low-level, with arbitrary types) + define_builtin_method("T.toCell", typeT, {typeT, PackOptions}, CellT, declReceiverT, + compile_time_only_function, + FunctionData::flagMarkedAsPure | FunctionData::flagCompileTimeGen | FunctionData::flagAcceptsSelf | FunctionData::flagAllowAnyWidthT); + define_builtin_method("T.fromCell", typeT, {TypeDataCell::create(), UnpackOptions}, typeT, declReceiverT, + compile_time_only_function, + FunctionData::flagMarkedAsPure | FunctionData::flagCompileTimeGen | FunctionData::flagAllowAnyWidthT); + define_builtin_method("T.fromSlice", typeT, {Slice, UnpackOptions}, typeT, declReceiverT, + compile_time_only_function, + FunctionData::flagMarkedAsPure | FunctionData::flagCompileTimeGen | FunctionData::flagAllowAnyWidthT); + define_builtin_method("T.estimatePackSize", typeT, {}, TypeDataBrackets::create({TypeDataInt::create(), TypeDataInt::create(), TypeDataInt::create(), TypeDataInt::create()}), declReceiverT, + compile_time_only_function, + FunctionData::flagMarkedAsPure | FunctionData::flagCompileTimeGen | FunctionData::flagAllowAnyWidthT); + define_builtin_method("T.getDeclaredPackPrefix", typeT, {}, Int, declReceiverT, + compile_time_only_function, + FunctionData::flagMarkedAsPure | FunctionData::flagCompileTimeVal | FunctionData::flagAllowAnyWidthT); + define_builtin_method("T.getDeclaredPackPrefixLen", typeT, {}, Int, declReceiverT, + compile_time_only_function, + FunctionData::flagMarkedAsPure | FunctionData::flagCompileTimeVal | FunctionData::flagAllowAnyWidthT); + define_builtin_method("Cell.load", CellT, {CellT, UnpackOptions}, typeT, declReceiverT, + compile_time_only_function, + FunctionData::flagMarkedAsPure | FunctionData::flagCompileTimeGen | FunctionData::flagAcceptsSelf | FunctionData::flagAllowAnyWidthT); + define_builtin_method("slice.loadAny", Slice, {Slice, UnpackOptions}, typeT, declGenericT, + compile_time_only_function, + FunctionData::flagMarkedAsPure | FunctionData::flagCompileTimeGen | FunctionData::flagAcceptsSelf | FunctionData::flagHasMutateParams | FunctionData::flagAllowAnyWidthT); + define_builtin_method("slice.skipAny", Slice, {Slice, UnpackOptions}, Slice, declGenericT, + compile_time_only_function, + FunctionData::flagMarkedAsPure | FunctionData::flagCompileTimeGen | FunctionData::flagAcceptsSelf | FunctionData::flagReturnsSelf | FunctionData::flagHasMutateParams | FunctionData::flagAllowAnyWidthT); + define_builtin_method("builder.storeAny", Builder, {Builder, typeT, PackOptions}, Builder, declGenericT, + compile_time_only_function, + FunctionData::flagMarkedAsPure | FunctionData::flagCompileTimeGen | FunctionData::flagAcceptsSelf | FunctionData::flagReturnsSelf | FunctionData::flagHasMutateParams | FunctionData::flagAllowAnyWidthT); // functions not presented in stdlib at all // used in tolk-tester to check/expose internal compiler state @@ -1335,6 +1431,9 @@ void define_builtins() { define_builtin_func("__expect_type", {TypeDataUnknown::create(), Slice}, Unit, nullptr, compile_expect_type, FunctionData::flagMarkedAsPure); + define_builtin_method("T.__toTuple", typeT, {typeT}, TypeDataTuple::create(), declReceiverT, + compile_any_object_to_tuple, + FunctionData::flagMarkedAsPure | FunctionData::flagAcceptsSelf | FunctionData::flagAllowAnyWidthT); } // there are some built-in functions that operate on types declared in stdlib (like Cell) @@ -1342,12 +1441,47 @@ void define_builtins() { // after all files have been loaded, symbols have been registered, and aliases exist, // we patch that earlier registered built-in functions providing types that now exist void patch_builtins_after_stdlib_loaded() { + TypePtr typeT = TypeDataGenericT::create("T"); StructPtr struct_debug = lookup_global_symbol("debug")->try_as(); TypePtr debug = TypeDataStruct::create(struct_debug); lookup_function("debug.print")->mutate()->receiver_type = debug; lookup_function("debug.printString")->mutate()->receiver_type = debug; lookup_function("debug.dumpStack")->mutate()->receiver_type = debug; + + StructPtr struct_ref_CellT = lookup_global_symbol("Cell")->try_as(); + StructPtr struct_ref_PackOptions = lookup_global_symbol("PackOptions")->try_as(); + StructPtr struct_ref_UnpackOptions = lookup_global_symbol("UnpackOptions")->try_as(); + TypePtr CellT = TypeDataGenericTypeWithTs::create(struct_ref_CellT, nullptr, {typeT}); + TypePtr PackOptions = TypeDataStruct::create(struct_ref_PackOptions); + TypePtr UnpackOptions = TypeDataStruct::create(struct_ref_UnpackOptions); + + // in stdlib, there is a default parameter `options = {}`; since default parameters are evaluated with AST, + // emulate its presence in built-in functions; it looks ugly, but currently I don't have a better solution + auto v_empty_PackOptions = createV({}, nullptr, createV({}, {})); + v_empty_PackOptions->assign_struct_ref(struct_ref_PackOptions); + v_empty_PackOptions->assign_inferred_type(PackOptions); + auto v_empty_UnpackOptions = createV({}, nullptr, createV({}, {})); + v_empty_UnpackOptions->assign_struct_ref(struct_ref_UnpackOptions); + v_empty_UnpackOptions->assign_inferred_type(UnpackOptions); + + lookup_function("T.toCell")->mutate()->declared_return_type = CellT; + lookup_function("T.toCell")->mutate()->parameters[1].declared_type = PackOptions; + lookup_function("T.toCell")->mutate()->parameters[1].default_value = v_empty_PackOptions; + lookup_function("T.fromCell")->mutate()->parameters[1].declared_type = UnpackOptions; + lookup_function("T.fromCell")->mutate()->parameters[1].default_value = v_empty_UnpackOptions; + lookup_function("T.fromSlice")->mutate()->parameters[1].declared_type = UnpackOptions; + lookup_function("T.fromSlice")->mutate()->parameters[1].default_value = v_empty_UnpackOptions; + lookup_function("Cell.load")->mutate()->parameters[0].declared_type = CellT; + lookup_function("Cell.load")->mutate()->parameters[1].declared_type = UnpackOptions; + lookup_function("Cell.load")->mutate()->parameters[1].default_value = v_empty_UnpackOptions; + lookup_function("Cell.load")->mutate()->receiver_type = CellT; + lookup_function("slice.loadAny")->mutate()->parameters[1].declared_type = UnpackOptions; + lookup_function("slice.loadAny")->mutate()->parameters[1].default_value = v_empty_UnpackOptions; + lookup_function("slice.skipAny")->mutate()->parameters[1].declared_type = UnpackOptions; + lookup_function("slice.skipAny")->mutate()->parameters[1].default_value = v_empty_UnpackOptions; + lookup_function("builder.storeAny")->mutate()->parameters[2].declared_type = PackOptions; + lookup_function("builder.storeAny")->mutate()->parameters[2].default_value = v_empty_PackOptions; } } // namespace tolk diff --git a/tolk/constant-evaluator.cpp b/tolk/constant-evaluator.cpp index a281cfbe31..20c316fdb1 100644 --- a/tolk/constant-evaluator.cpp +++ b/tolk/constant-evaluator.cpp @@ -17,6 +17,7 @@ #include "constant-evaluator.h" #include "ast.h" #include "tolk.h" +#include "type-system.h" #include "openssl/digest.hpp" #include "crypto/common/util.h" #include "td/utils/crypto.h" @@ -73,9 +74,9 @@ static bool parse_raw_address(std::string_view acc_string, int& workchain, ton:: int x; if (c >= '0' && c <= '9') { x = c - '0'; - } else if (c >= 'a' && c <= 'z') { + } else if (c >= 'a' && c <= 'f') { x = c - 'a' + 10; - } else if (c >= 'A' && c <= 'Z') { + } else if (c >= 'A' && c <= 'F') { x = c - 'A' + 10; } else { return false; @@ -90,6 +91,23 @@ static bool parse_raw_address(std::string_view acc_string, int& workchain, ton:: return true; } +static void parse_any_std_address(std::string_view str, SrcLocation loc, unsigned char (*data)[3 + 8 + 256]) { + ton::WorkchainId workchain; + ton::StdSmcAddress addr; + bool correct = (str.size() == 48 && parse_friendly_address(str.data(), workchain, addr)) || + (str.size() != 48 && parse_raw_address(str, workchain, addr)); + if (!correct) { + throw ParseError(loc, "invalid standard address"); + } + if (workchain < -128 || workchain >= 128) { + throw ParseError(loc, "anycast addresses not supported"); + } + + td::bitstring::bits_store_long_top(*data, 0, static_cast(4) << (64 - 3), 3); + td::bitstring::bits_store_long_top(*data, 3, static_cast(workchain) << (64 - 8), 8); + td::bitstring::bits_memcpy(*data, 3 + 8, addr.bits().ptr, 0, ton::StdSmcAddress::size()); +} + // internal helper: for `ton("0.05")`, parse string literal "0.05" to 50000000 static td::RefInt256 parse_nanotons_as_floating_string(SrcLocation loc, std::string_view str) { bool is_negative = false; @@ -106,6 +124,7 @@ static td::RefInt256 parse_nanotons_as_floating_string(SrcLocation loc, std::str // parse "0.05" into integer part (before dot) and fractional (after) int64_t integer_part = 0; int64_t fractional_part = 0; + int integer_digits = 0; int fractional_digits = 0; bool seen_dot = false; @@ -119,6 +138,9 @@ static td::RefInt256 parse_nanotons_as_floating_string(SrcLocation loc, std::str } else if (c >= '0' && c <= '9') { if (!seen_dot) { integer_part = integer_part * 10 + (c - '0'); + if (++integer_digits > 9) { + throw ParseError(loc, "argument is too big and leads to overflow"); + } } else if (fractional_digits < 9) { fractional_part = fractional_part * 10 + (c - '0'); fractional_digits++; @@ -140,8 +162,21 @@ static td::RefInt256 parse_nanotons_as_floating_string(SrcLocation loc, std::str // given `ton("0.05")` evaluate it to 50000000 // given `stringCrc32("some_str")` evaluate it // etc. -// currently, all compile-time functions accept 1 argument, a literal string static CompileTimeFunctionResult parse_vertex_call_to_compile_time_function(V v, std::string_view f_name) { + // most functions accept 1 argument, but static compile-time methods like `MyStruct.getDeclaredPackPrefix()` have 0 args + if (v->get_num_args() == 0) { + TypePtr receiver = v->fun_maybe->receiver_type; + f_name = v->fun_maybe->method_name; + + if (f_name == "getDeclaredPackPrefix" || f_name == "getDeclaredPackPrefixLen") { + const TypeDataStruct* t_struct = receiver->try_as(); + if (!t_struct || !t_struct->struct_ref->opcode.exists()) { + throw ParseError(v->loc, "type `" + receiver->as_human_readable() + "` does not have a serialization prefix"); + } + return td::make_refint(f_name.ends_with('x') ? t_struct->struct_ref->opcode.pack_prefix : t_struct->struct_ref->opcode.prefix_len); + } + } + tolk_assert(v->get_num_args() == 1); // checked by type inferring AnyExprV v_arg = v->get_arg(0)->get_expr(); @@ -161,6 +196,12 @@ static CompileTimeFunctionResult parse_vertex_call_to_compile_time_function(Vloc, str); } + if (f_name == "address") { // previously, postfix "..."a, but it returned `slice` (now returns `address`) + unsigned char data[3 + 8 + 256]; // addr_std$10 anycast:(Maybe Anycast) workchain_id:int8 address:bits256 = MsgAddressInt; + parse_any_std_address(str, v_arg->loc, &data); + return td::BitSlice{data, sizeof(data)}.to_hex(); + } + if (f_name == "stringCrc32") { // previously, postfix "..."c return td::make_refint(td::crc32(td::Slice{str.data(), str.size()})); } @@ -181,25 +222,6 @@ static CompileTimeFunctionResult parse_vertex_call_to_compile_time_function(Verror("invalid standard address"); - } - if (workchain < -128 || workchain >= 128) { - v_arg->error("anycast addresses not supported"); - } - - unsigned char data[3 + 8 + 256]; // addr_std$10 anycast:(Maybe Anycast) workchain_id:int8 address:bits256 = MsgAddressInt; - td::bitstring::bits_store_long_top(data, 0, static_cast(4) << (64 - 3), 3); - td::bitstring::bits_store_long_top(data, 3, static_cast(workchain) << (64 - 8), 8); - td::bitstring::bits_memcpy(data, 3 + 8, addr.bits().ptr, 0, ton::StdSmcAddress::size()); - return td::BitSlice{data, sizeof(data)}.to_hex(); - } - if (f_name == "stringHexToSlice") { // previously, postfix "..."s unsigned char buff[128]; long bits = td::bitstring::parse_bitstring_hex_literal(buff, sizeof(buff), str.data(), str.data() + str.size()); @@ -244,7 +266,7 @@ struct ConstantExpressionChecker { // `const a = ton("0.05")`, we met `ton("0.05")` static void handle_function_call(V v) { - if (v->fun_maybe && v->fun_maybe->is_compile_time_only()) { + if (v->fun_maybe && v->fun_maybe->is_compile_time_const_val()) { // `ton(local_var)` is denied; it's validated not here, but when replacing its value with a calculated one return; } @@ -258,6 +280,13 @@ struct ConstantExpressionChecker { } } + // `a: Options = {}`, object literal may occur as a default value for parameters + static void handle_object_literal(V v) { + for (int i = 0; i < v->get_num_fields(); ++i) { + visit(v->get_field(i)->get_init_val()); + } + } + static void visit(AnyExprV v) { if (v->try_as() || v->try_as() || v->try_as() || v->try_as()) { return; @@ -277,6 +306,9 @@ struct ConstantExpressionChecker { if (auto v_tensor = v->try_as()) { return handle_tensor(v_tensor); } + if (auto v_obj = v->try_as()) { + return handle_object_literal(v_obj->get_body()); + } if (auto v_par = v->try_as()) { return visit(v_par->get_expr()); } @@ -310,7 +342,7 @@ std::string eval_string_const_standalone(AnyExprV v_string) { CompileTimeFunctionResult eval_call_to_compile_time_function(AnyExprV v_call) { auto v = v_call->try_as(); - tolk_assert(v && v->fun_maybe->is_compile_time_only()); + tolk_assert(v && v->fun_maybe->is_compile_time_const_val()); return parse_vertex_call_to_compile_time_function(v, v->fun_maybe->name); } diff --git a/tolk/generics-helpers.cpp b/tolk/generics-helpers.cpp index 5c0cc88673..c2c7a9b546 100644 --- a/tolk/generics-helpers.cpp +++ b/tolk/generics-helpers.cpp @@ -366,7 +366,7 @@ FunctionPtr instantiate_generic_function(FunctionPtr fun_ref, GenericsSubstituti new_parameters.reserve(fun_ref->get_num_params()); for (const LocalVarData& orig_p : fun_ref->parameters) { TypePtr new_param_type = replace_genericT_with_deduced(orig_p.declared_type, allocatedTs); - new_parameters.emplace_back(orig_p.name, orig_p.loc, new_param_type, orig_p.flags, orig_p.param_idx); + new_parameters.emplace_back(orig_p.name, orig_p.loc, new_param_type, orig_p.default_value, orig_p.flags, orig_p.param_idx); } TypePtr new_return_type = replace_genericT_with_deduced(fun_ref->declared_return_type, allocatedTs); TypePtr new_receiver_type = replace_genericT_with_deduced(fun_ref->receiver_type, allocatedTs); @@ -443,7 +443,11 @@ AliasDefPtr instantiate_generic_alias(AliasDefPtr alias_ref, GenericsSubstitutio } // find `builder.storeInt` for called_receiver = "builder" and called_name = "storeInt" -// most practical case, when a direct method for receiver exists +// most practical case, when a direct method for receiver exists; +// note, that having an alias `type WorkchainNum = int` and methods `WorkchainNum.isMasterchain()`, +// it's okay to call `-1.isMasterchain()`, because int equals to any alias; +// currently there is no chance to change this logic, say, `type AssetList = dict` to have separate methods, +// due to smart casts, types merge of control flow rejoin, etc., which immediately become `cell?` FunctionPtr match_exact_method_for_call_not_generic(TypePtr called_receiver, std::string_view called_name) { FunctionPtr exact_found = nullptr; diff --git a/tolk/optimize.cpp b/tolk/optimize.cpp index 332069c03f..e6d6b070db 100644 --- a/tolk/optimize.cpp +++ b/tolk/optimize.cpp @@ -135,6 +135,35 @@ bool Optimizer::find_const_op(int* op_idx, int cst) { return false; } +// purpose: transform `65535 THROW` to `PUSHINT` + `THROWANY`; +// such a technique allows pushing a number onto a stack just before THROW, even if a variable is created in advance; +// used for `T.fromSlice(s, {code:0xFFFF})`, where `tmp = 0xFFFF` + serialization match + `else throw tmp` is generated; +// but since it's constant, it transforms to (unused 0xFFFF) + ... + else "65535 THROW", unwrapped here +bool Optimizer::detect_rewrite_big_THROW() { + bool is_throw = op_[0]->is_custom() && op_[0]->op.ends_with(" THROW"); + if (!is_throw) { + return false; + } + + std::string_view s_num_throw = op_[0]->op; + size_t sp = s_num_throw.find(' '); + if (sp != s_num_throw.rfind(' ') || s_num_throw[0] < '1' || s_num_throw[0] > '9') { + return false; + } + + std::string s_number(s_num_throw.substr(0, sp)); + uint64_t excno = std::stoul(s_number); + if (excno < 2048) { // "9 THROW" left as is, but "N THROW" where N>=2^11 is invalid for Fift + return false; + } + + p_ = 1; + q_ = 2; + oq_[0] = std::make_unique(AsmOp::IntConst(op_[0]->loc, td::make_refint(excno))); + oq_[1] = std::make_unique(AsmOp::Custom(op_[0]->loc, "THROWANY", 1, 0)); + return true; +} + bool Optimizer::is_push_const(int* i, int* c) const { return pb_ >= 3 && pb_ <= l2_ && tr_[pb_ - 1].is_push_const(i, c); } @@ -553,6 +582,7 @@ bool Optimizer::find_at_least(int pb) { (is_xchg(&i, &j) && rewrite(AsmOp::Xchg(loc, i, j))) || (is_push(&i) && rewrite(AsmOp::Push(loc, i))) || (is_pop(&i) && rewrite(AsmOp::Pop(loc, i))) || (is_pop_pop(&i, &j) && rewrite(AsmOp::Pop(loc, i), AsmOp::Pop(loc, j))) || (is_xchg_xchg(&i, &j, &k, &l) && rewrite(AsmOp::Xchg(loc, i, j), AsmOp::Xchg(loc, k, l))) || + detect_rewrite_big_THROW() || (!(mode_ & 1) && ((is_rot() && rewrite(AsmOp::Custom(loc, "ROT", 3, 3))) || (is_rotrev() && rewrite(AsmOp::Custom(loc, "-ROT", 3, 3))) || (is_2dup() && rewrite(AsmOp::Custom(loc, "2DUP", 2, 4))) || diff --git a/tolk/pack-unpack-api.cpp b/tolk/pack-unpack-api.cpp new file mode 100644 index 0000000000..6af6650f49 --- /dev/null +++ b/tolk/pack-unpack-api.cpp @@ -0,0 +1,268 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain 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. + + TON Blockchain 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. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . +*/ +#include "pack-unpack-api.h" +#include "generics-helpers.h" +#include "type-system.h" +#include + +/* + * This module provides high-level (de)serialization functions to be used from outer code: + * - pack to cell/builder + * - unpack from cell/slice + * - etc. + * + * For the implementation of packing primitives, consider `pack-unpack-serializers.cpp`. + */ + +namespace tolk { + + +// -------------------------------------------- +// checking serialization availability +// +// for every call `obj.toCell()` and similar, checks are executed to ensure that `obj` can be serialized +// if it can, the compilation process continues +// if not, a detailed explanation is shown +// + + +struct CantSerializeBecause { + std::string because_msg; + + explicit CantSerializeBecause(std::string because_msg) + : because_msg(std::move(because_msg)) {} + explicit CantSerializeBecause(const std::string& because_msg, const CantSerializeBecause& why) + : because_msg(because_msg + "\n" + why.because_msg) {} +}; + +class PackUnpackAvailabilityChecker { +public: + static std::optional detect_why_cant_serialize(TypePtr any_type, bool is_pack) { + if (any_type->try_as()) { + return {}; + } + if (any_type->try_as()) { + return {}; + } + if (any_type == TypeDataCoins::create()) { + return {}; + } + if (any_type == TypeDataBool::create()) { + return {}; + } + if (any_type == TypeDataCell::create()) { + return {}; + } + if (any_type == TypeDataAddress::create()) { + return {}; + } + if (any_type == TypeDataNever::create()) { + return {}; + } + + if (const auto* t_struct = any_type->try_as()) { + StructPtr struct_ref = t_struct->struct_ref; + for (StructFieldPtr field_ref : struct_ref->fields) { + if (auto why = detect_why_cant_serialize(field_ref->declared_type, is_pack)) { + return CantSerializeBecause("because field `" + struct_ref->name + "." + field_ref->name + "` of type `" + field_ref->declared_type->as_human_readable() + "` can't be serialized", why.value()); + } + } + if (is_type_cellT(t_struct)) { + TypePtr cellT = struct_ref->substitutedTs->typeT_at(0); + if (auto why = detect_why_cant_serialize(cellT, is_pack)) { + return CantSerializeBecause("because type `" + cellT->as_human_readable() + "` can't be serialized", why.value()); + } + } + return {}; + } + + if (const auto* t_union = any_type->try_as()) { + // a union can almost always be serialized if every of its variants can: + // - `T?` is TL/B `(Maybe T)` + // - `T1 | T2` is TL/B `(Either T1 T2)` (or, if opcodes manually set, just by opcodes) + // - `T1 | T2 | ...` is either by manual opcodes, or the compiler implicitly defines them + // so, even `int32 | int64 | int128` or `A | B | C | null` are serializable + // (unless corner cases occur, like duplicated opcodes, etc.) + for (int i = 0; i < t_union->size(); ++i) { + TypePtr variant = t_union->variants[i]; + if (variant == TypeDataNullLiteral::create()) { + continue; + } + if (auto why = detect_why_cant_serialize(variant, is_pack)) { + return CantSerializeBecause("because variant #" + std::to_string(i + 1) + " of type `" + variant->as_human_readable() + "` can't be serialized", why.value()); + } + } + if (!t_union->or_null) { + std::string because_msg; + auto_generate_opcodes_for_union(t_union, because_msg); + if (!because_msg.empty()) { + return CantSerializeBecause("because could not automatically generate serialization prefixes for a union\n" + because_msg); + } + } + return {}; + } + + if (const auto* t_tensor = any_type->try_as()) { + for (int i = 0; i < t_tensor->size(); ++i) { + if (auto why = detect_why_cant_serialize(t_tensor->items[i], is_pack)) { + return CantSerializeBecause("because element `tensor." + std::to_string(i) + "` of type `" + t_tensor->items[i]->as_human_readable() + "` can't be serialized", why.value()); + } + } + return {}; + } + + if (const auto* t_alias = any_type->try_as()) { + if (t_alias->alias_ref->name == "RemainingBitsAndRefs") { // it's built-in RemainingBitsAndRefs (slice) + return {}; + } + if (auto why = detect_why_cant_serialize(t_alias->underlying_type, is_pack)) { + return CantSerializeBecause("because alias `" + t_alias->as_human_readable() + "` expands to `" + t_alias->underlying_type->as_human_readable() + "`", why.value()); + } + return {}; + } + + // `builder` can be used for writing, but not for reading + if (any_type == TypeDataBuilder::create()) { + if (is_pack) { + return {}; + } + return CantSerializeBecause("because type `builder` can not be used for reading, only for writing\nhint: use `bitsN` or `RemainingBitsAndRefs` for reading\nhint: using generics, you can substitute `builder` for writing and something other for reading"); + } + + // serialization not available + // for common types, make a detailed explanation with a hint how to fix + + if (any_type == TypeDataInt::create()) { + return CantSerializeBecause("because type `int` is not serializable, it doesn't define binary width\nhint: replace `int` with `int32` / `uint64` / `coins` / etc."); + } + if (any_type == TypeDataSlice::create()) { + return CantSerializeBecause("because type `slice` is not serializable, it doesn't define binary width\nhint: replace `slice` with `address` if it's an address, actually\nhint: replace `slice` with `bits128` and similar if it represents fixed-width data without refs"); + } + if (any_type == TypeDataNullLiteral::create()) { + return CantSerializeBecause("because type `null` is not serializable\nhint: `int32?` and other nullable types will work"); + } + if (any_type == TypeDataTuple::create() || any_type->try_as()) { + return CantSerializeBecause("because tuples are not serializable\nhint: use tensors instead of tuples, they will work"); + } + + return CantSerializeBecause("because type `" + any_type->as_human_readable() + "` is not serializable"); + } +}; + +bool check_struct_can_be_packed_or_unpacked(TypePtr any_type, bool is_pack, std::string& because_msg) { + if (auto why = PackUnpackAvailabilityChecker::detect_why_cant_serialize(any_type, is_pack)) { + because_msg = why.value().because_msg; + return false; + } + return true; +} + + +// -------------------------------------------- +// high-level API for outer code +// + + +std::vector generate_pack_struct_to_cell(CodeBlob& code, SrcLocation loc, TypePtr any_type, std::vector&& ir_obj, const std::vector& ir_options) { + FunctionPtr f_beginCell = lookup_function("beginCell"); + FunctionPtr f_endCell = lookup_function("builder.endCell"); + std::vector rvect_builder = code.create_var(TypeDataBuilder::create(), loc, "b"); + code.emplace_back(loc, Op::_Call, rvect_builder, std::vector{}, f_beginCell); + + tolk_assert(ir_options.size() == 1); // struct PackOptions + PackContext ctx(code, loc, rvect_builder, ir_options); + ctx.generate_pack_any(any_type, std::move(ir_obj)); + + std::vector rvect_cell = code.create_tmp_var(TypeDataCell::create(), loc, "(cell)"); + code.emplace_back(loc, Op::_Call, rvect_cell, std::move(rvect_builder), f_endCell); + + return rvect_cell; +} + +std::vector generate_pack_struct_to_builder(CodeBlob& code, SrcLocation loc, TypePtr any_type, std::vector&& ir_builder, std::vector&& ir_obj, const std::vector& ir_options) { + PackContext ctx(code, loc, ir_builder, ir_options); // mutate this builder + ctx.generate_pack_any(any_type, std::move(ir_obj)); + + return ir_builder; // return mutated builder +} + +std::vector generate_unpack_struct_from_slice(CodeBlob& code, SrcLocation loc, TypePtr any_type, std::vector&& ir_slice, bool mutate_slice, const std::vector& ir_options) { + if (!mutate_slice) { + std::vector slice_copy = code.create_var(TypeDataSlice::create(), loc, "s"); + code.emplace_back(loc, Op::_Let, slice_copy, std::move(ir_slice)); + ir_slice = std::move(slice_copy); + } + + tolk_assert(ir_options.size() == 2); // struct UnpackOptions + UnpackContext ctx(code, loc, std::move(ir_slice), ir_options); + std::vector rvect_struct = ctx.generate_unpack_any(any_type); + tolk_assert(any_type->get_width_on_stack() == static_cast(rvect_struct.size())); + + // slice.loadAny() ignores options.assertEndAfterReading, because it's intended to read data in the middle + if (!mutate_slice && !estimate_serialization_size(any_type).is_unpredictable_infinity()) { + ctx.assertEndIfOption(); + } + return rvect_struct; +} + +std::vector generate_unpack_struct_from_cell(CodeBlob& code, SrcLocation loc, TypePtr any_type, std::vector&& ir_cell, const std::vector& ir_options) { + FunctionPtr f_beginParse = lookup_function("cell.beginParse"); + std::vector ir_slice = code.create_var(TypeDataSlice::create(), loc, "s"); + code.emplace_back(loc, Op::_Call, ir_slice, std::move(ir_cell), f_beginParse); + + tolk_assert(ir_options.size() == 2); // struct UnpackOptions + UnpackContext ctx(code, loc, std::move(ir_slice), ir_options); + std::vector rvect_struct = ctx.generate_unpack_any(any_type); + tolk_assert(any_type->get_width_on_stack() == static_cast(rvect_struct.size())); + + // if a struct has RemainingBitsAndRefs, don't test it for assertEnd + if (!estimate_serialization_size(any_type).is_unpredictable_infinity()) { + ctx.assertEndIfOption(); + } + return rvect_struct; +} + +std::vector generate_skip_struct_in_slice(CodeBlob& code, SrcLocation loc, TypePtr any_type, std::vector&& ir_slice, const std::vector& ir_options) { + UnpackContext ctx(code, loc, ir_slice, ir_options); // mutate this slice + ctx.generate_skip_any(any_type); + + return ir_slice; // return mutated slice +} + +PackSize estimate_serialization_size(TypePtr any_type) { + EstimateContext ctx; + return ctx.estimate_any(any_type); +} + +std::vector generate_estimate_size_call(CodeBlob& code, SrcLocation loc, TypePtr any_type) { + EstimateContext ctx; + PackSize pack_size = ctx.estimate_any(any_type); + + std::vector ir_tensor = code.create_tmp_var(TypeDataTensor::create({TypeDataInt::create(), TypeDataInt::create(), TypeDataInt::create(), TypeDataInt::create()}), loc, "(result-tensor)"); + code.emplace_back(loc, Op::_IntConst, std::vector{ir_tensor[0]}, td::make_refint(pack_size.min_bits)); + code.emplace_back(loc, Op::_IntConst, std::vector{ir_tensor[1]}, td::make_refint(pack_size.max_bits)); + code.emplace_back(loc, Op::_IntConst, std::vector{ir_tensor[2]}, td::make_refint(pack_size.min_refs)); + code.emplace_back(loc, Op::_IntConst, std::vector{ir_tensor[3]}, td::make_refint(pack_size.max_refs)); + + FunctionPtr f_toTuple = lookup_function("T.__toTuple"); + std::vector ir_tuple = code.create_tmp_var(TypeDataTuple::create(), loc, "(result-tuple)"); + code.emplace_back(loc, Op::_Call, ir_tuple, ir_tensor, f_toTuple); + + return ir_tuple; +} + +} // namespace tolk diff --git a/tolk/pack-unpack-api.h b/tolk/pack-unpack-api.h new file mode 100644 index 0000000000..7737125d40 --- /dev/null +++ b/tolk/pack-unpack-api.h @@ -0,0 +1,35 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain 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. + + TON Blockchain 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. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . +*/ +#pragma once + +#include "pack-unpack-serializers.h" +#include "tolk.h" + +namespace tolk { + +bool check_struct_can_be_packed_or_unpacked(TypePtr any_type, bool is_pack, std::string& because_msg); + +std::vector generate_pack_struct_to_cell(CodeBlob& code, SrcLocation loc, TypePtr any_type, std::vector&& ir_obj, const std::vector& ir_options); +std::vector generate_pack_struct_to_builder(CodeBlob& code, SrcLocation loc, TypePtr any_type, std::vector&& ir_builder, std::vector&& ir_obj, const std::vector& ir_options); +std::vector generate_unpack_struct_from_slice(CodeBlob& code, SrcLocation loc, TypePtr any_type, std::vector&& ir_slice, bool mutate_slice, const std::vector& ir_options); +std::vector generate_unpack_struct_from_cell(CodeBlob& code, SrcLocation loc, TypePtr any_type, std::vector&& ir_cell, const std::vector& ir_options); +std::vector generate_skip_struct_in_slice(CodeBlob& code, SrcLocation loc, TypePtr any_type, std::vector&& ir_slice, const std::vector& ir_options); + +PackSize estimate_serialization_size(TypePtr any_type); +std::vector generate_estimate_size_call(CodeBlob& code, SrcLocation loc, TypePtr any_type); + +} // namespace tolk diff --git a/tolk/pack-unpack-serializers.cpp b/tolk/pack-unpack-serializers.cpp new file mode 100644 index 0000000000..c0490c5c76 --- /dev/null +++ b/tolk/pack-unpack-serializers.cpp @@ -0,0 +1,1029 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain 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. + + TON Blockchain 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. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . +*/ +#include "pack-unpack-serializers.h" +#include "tolk.h" +#include "type-system.h" +#include "td/utils/crypto.h" + +/* + * This module implements serializing different types to/from cells. + * For any serializable TypePtr, we detect ISerializer, which can pack/unpack/skip/estimate size. + * See `get_serializer_for_type()`. + * Example: given an object of `struct A { f: int32 }` its type is TypeDataStruct(A), its serializer is + * "custom struct", which iterates fields, for field `f` its serializer is "intN" with N=32. + * + * Serializing compound types is complicated, involving transitioning IR variables. For example, to serialize + * `int8 | A` (it's Either), we have input rvect of size = 1 + width(A), generate dynamic IF ELSE, and in each branch, + * transition rvect slots to a narrowed type. Operating with transitions and runtime type checking are implemented + * in IR generation, here we just reference those prototypes. + * + * For high-level (de)serialization API, consider `pack-unpack-api.cpp`. + */ + +namespace tolk { + + +std::vector pre_compile_is_type(CodeBlob& code, TypePtr expr_type, TypePtr cmp_type, const std::vector& expr_ir_idx, SrcLocation loc, const char* debug_desc); +std::vector transition_to_target_type(std::vector&& rvect, CodeBlob& code, TypePtr original_type, TypePtr target_type, SrcLocation loc); + +bool is_type_cellT(TypePtr any_type) { + if (const TypeDataStruct* t_struct = any_type->try_as()) { + StructPtr struct_ref = t_struct->struct_ref; + return struct_ref->is_instantiation_of_generic_struct() && struct_ref->base_struct_ref->name == "Cell"; + } + return false; +} + + +// -------------------------------------------- +// options, context, common helpers +// +// some of the referenced functions are built-in, some are declared in stdlib +// serialization assumes that stdlib exists and is loaded correctly +// + + +PackContext::PackContext(CodeBlob& code, SrcLocation loc, std::vector ir_builder, const std::vector& ir_options) + : code(code) + , loc(loc) + , f_storeInt(lookup_function("builder.storeInt")) + , f_storeUint(lookup_function("builder.storeUint")) + , ir_builder(std::move(ir_builder)) + , ir_builder0(this->ir_builder[0]) + , option_skipBitsNFieldsValidation(ir_options[0]) { +} + +void PackContext::storeInt(var_idx_t ir_idx, int len) const { + std::vector args = { ir_builder0, ir_idx, code.create_int(loc, len, "(storeW)") }; + code.emplace_back(loc, Op::_Call, ir_builder, std::move(args), f_storeInt); +} + +void PackContext::storeUint(var_idx_t ir_idx, int len) const { + std::vector args = { ir_builder0, ir_idx, code.create_int(loc, len, "(storeW)") }; + code.emplace_back(loc, Op::_Call, ir_builder, std::move(args), f_storeUint); +} + +void PackContext::storeBool(var_idx_t ir_idx) const { + std::vector args = { ir_builder0, ir_idx }; + code.emplace_back(loc, Op::_Call, ir_builder, std::move(args), lookup_function("builder.storeBool")); +} + +void PackContext::storeCoins(var_idx_t ir_idx) const { + std::vector args = { ir_builder0, ir_idx }; + code.emplace_back(loc, Op::_Call, ir_builder, std::move(args), lookup_function("builder.storeCoins")); +} + +void PackContext::storeRef(var_idx_t ir_idx) const { + std::vector args = { ir_builder0, ir_idx }; + code.emplace_back(loc, Op::_Call, ir_builder, std::move(args), lookup_function("builder.storeRef")); +} + +void PackContext::storeMaybeRef(var_idx_t ir_idx) const { + std::vector args = { ir_builder0, ir_idx }; + code.emplace_back(loc, Op::_Call, ir_builder, std::move(args), lookup_function("builder.storeMaybeRef")); +} + +void PackContext::storeAddress(var_idx_t ir_idx) const { + std::vector args = { ir_builder0, ir_idx }; + code.emplace_back(loc, Op::_Call, ir_builder, std::move(args), lookup_function("builder.storeAddress")); +} + +void PackContext::storeBuilder(var_idx_t ir_idx) const { + std::vector args = { ir_builder0, ir_idx }; + code.emplace_back(loc, Op::_Call, ir_builder, std::move(args), lookup_function("builder.storeBuilder")); +} + +void PackContext::storeSlice(var_idx_t ir_idx) const { + std::vector args = { ir_builder0, ir_idx }; + code.emplace_back(loc, Op::_Call, ir_builder, std::move(args), lookup_function("builder.storeSlice")); +} + +void PackContext::storeOpcode(PackOpcode opcode) const { + std::vector args = { ir_builder0, code.create_int(loc, opcode.pack_prefix, "(struct-prefix)"), code.create_int(loc, opcode.prefix_len, "(storeW)") }; + code.emplace_back(loc, Op::_Call, ir_builder, std::move(args), f_storeUint); +} + + +UnpackContext::UnpackContext(CodeBlob& code, SrcLocation loc, std::vector ir_slice, const std::vector& ir_options) + : code(code) + , loc(loc) + , f_loadInt(lookup_function("slice.loadInt")) + , f_loadUint(lookup_function("slice.loadUint")) + , f_skipBits(lookup_function("slice.skipBits")) + , ir_slice(std::move(ir_slice)) + , ir_slice0(this->ir_slice[0]) + , option_assertEndAfterReading(ir_options[0]) + , option_throwIfOpcodeDoesNotMatch(ir_options[1]) { +} + +std::vector UnpackContext::loadInt(int len, const char* debug_desc) const { + std::vector args = { ir_slice0, code.create_int(loc, len, "(loadW)") }; + std::vector result = code.create_tmp_var(TypeDataInt::create(), loc, debug_desc); + code.emplace_back(loc, Op::_Call, std::vector{ir_slice0, result[0]}, std::move(args), f_loadInt); + return result; +} + +std::vector UnpackContext::loadUint(int len, const char* debug_desc) const { + std::vector args = { ir_slice0, code.create_int(loc, len, "(loadW)") }; + std::vector result = code.create_tmp_var(TypeDataInt::create(), loc, debug_desc); + code.emplace_back(loc, Op::_Call, std::vector{ir_slice0, result[0]}, std::move(args), f_loadUint); + return result; +} + +void UnpackContext::loadAndCheckOpcode(PackOpcode opcode) const { + std::vector ir_prefix_eq = code.create_tmp_var(TypeDataInt::create(), loc, "(prefix-eq)"); + std::vector args = { ir_slice0, code.create_int(loc, opcode.pack_prefix, "(pack-prefix)"), code.create_int(loc, opcode.prefix_len, "(prefix-len)") }; + code.emplace_back(loc, Op::_Call, std::vector{ir_slice0, ir_prefix_eq[0]}, std::move(args), lookup_function("slice.tryStripPrefix")); + std::vector args_assert = { option_throwIfOpcodeDoesNotMatch, ir_prefix_eq[0], code.create_int(loc, 0, "") }; + Op& op_assert = code.emplace_back(loc, Op::_Call, std::vector{}, std::move(args_assert), lookup_function("__throw_if_unless")); + op_assert.set_impure_flag(); +} + +void UnpackContext::skipBits(int len) const { + std::vector args = { ir_slice0, code.create_int(loc, len, "(skipW)") }; + code.emplace_back(loc, Op::_Call, ir_slice, std::move(args), f_skipBits); +} + +void UnpackContext::skipBits_var(var_idx_t ir_len) const { + std::vector args = { ir_slice0, ir_len }; + code.emplace_back(loc, Op::_Call, ir_slice, std::move(args), f_skipBits); +} + +void UnpackContext::assertEndIfOption() const { + Op& if_assertEnd = code.emplace_back(loc, Op::_If, std::vector{option_assertEndAfterReading}); + { + code.push_set_cur(if_assertEnd.block0); + Op& op_ends = code.emplace_back(loc, Op::_Call, std::vector{}, ir_slice, lookup_function("slice.assertEnd")); + op_ends.set_impure_flag(); + code.close_pop_cur(loc); + } + { + code.push_set_cur(if_assertEnd.block1); + code.close_pop_cur(loc); + } +} + +// -------------------------------------------- +// serializers with pack/unpack/skip/estimate +// +// for every struct field, for every atomic type, a corresponding (de)serialization instruction is generated +// we generate IR code (Ops), not ASM directly; so, all later IR analysis will later take place +// some of them are straightforward, e.g., call a predefined function for intN and coins +// some are complicated, e.g., for Either we should check a union type at runtime while packing, +// and while unpacking, read a prefix, follow different branches, and construct a resulting union +// + + +struct ISerializer { + virtual ~ISerializer() = default; + + virtual void pack(const PackContext* ctx, CodeBlob& code, SrcLocation loc, std::vector&& rvect) = 0; + virtual std::vector unpack(const UnpackContext* ctx, CodeBlob& code, SrcLocation loc) = 0; + + virtual void skip(const UnpackContext* ctx, CodeBlob& code, SrcLocation loc) = 0; + virtual PackSize estimate(const EstimateContext* ctx) = 0; +}; + +struct S_IntN final : ISerializer { + const int n_bits; + const bool is_unsigned; + + explicit S_IntN(int n_bits, bool is_unsigned) + : n_bits(n_bits), is_unsigned(is_unsigned) {} + + void pack(const PackContext* ctx, CodeBlob& code, SrcLocation loc, std::vector&& rvect) override { + if (is_unsigned) { + ctx->storeUint(rvect[0], n_bits); + } else { + ctx->storeInt(rvect[0], n_bits); + } + } + + std::vector unpack(const UnpackContext* ctx, CodeBlob& code, SrcLocation loc) override { + if (is_unsigned) { + return ctx->loadUint(n_bits, "(loaded-uint)"); + } else { + return ctx->loadInt(n_bits, "(loaded-int)"); + } + } + + void skip(const UnpackContext* ctx, CodeBlob& code, SrcLocation loc) override { + ctx->skipBits(n_bits); + } + + PackSize estimate(const EstimateContext* ctx) override { + return PackSize(n_bits); + } +}; + +struct S_BytesN final : ISerializer { + const int n_bits; + + explicit S_BytesN(int n_width, bool is_bits) + : n_bits(is_bits ? n_width : n_width * 8) {} + + void pack(const PackContext* ctx, CodeBlob& code, SrcLocation loc, std::vector&& rvect) override { + tolk_assert(rvect.size() == 1); + + Op& if_disabled_by_user = code.emplace_back(loc, Op::_If, std::vector{ctx->option_skipBitsNFieldsValidation}); + { + code.push_set_cur(if_disabled_by_user.block0); + code.close_pop_cur(loc); + } + { + code.push_set_cur(if_disabled_by_user.block1); + FunctionPtr f_assert = lookup_function("__throw_if_unless"); + constexpr int EXCNO = 9; + + std::vector ir_counts = code.create_tmp_var(TypeDataTensor::create({TypeDataInt::create(), TypeDataInt::create()}), loc, "(slice-size)"); + code.emplace_back(loc, Op::_Call, ir_counts, rvect, lookup_function("slice.remainingBitsAndRefsCount")); + std::vector args_assert0 = { code.create_int(loc, EXCNO, "(excno)"), ir_counts[1], code.create_int(loc, 1, "") }; + Op& op_assert0 = code.emplace_back(loc, Op::_Call, std::vector{}, std::move(args_assert0), f_assert); + op_assert0.set_impure_flag(); + std::vector ir_eq_n = code.create_tmp_var(TypeDataInt::create(), loc, "(eq-n)"); + code.emplace_back(loc, Op::_Call, ir_eq_n, std::vector{ir_counts[0], code.create_int(loc, n_bits, "(n-bits)")}, lookup_function("_==_")); + std::vector args_assertN = { code.create_int(loc, EXCNO, "(excno)"), ir_eq_n[0], code.create_int(loc, 0, "") }; + Op& op_assertN = code.emplace_back(loc, Op::_Call, std::vector{}, std::move(args_assertN), f_assert); + op_assertN.set_impure_flag(); + code.close_pop_cur(loc); + } + + ctx->storeSlice(rvect[0]); + } + + std::vector unpack(const UnpackContext* ctx, CodeBlob& code, SrcLocation loc) override { + FunctionPtr f_loadBits = lookup_function("slice.loadBits"); + std::vector args = { ctx->ir_slice0, code.create_int(loc, n_bits, "(loadW)") }; + std::vector ir_result = code.create_tmp_var(TypeDataSlice::create(), loc, "(loaded-slice)"); + code.emplace_back(loc, Op::_Call, std::vector{ctx->ir_slice0, ir_result[0]}, std::move(args), f_loadBits); + return ir_result; + } + + void skip(const UnpackContext* ctx, CodeBlob& code, SrcLocation loc) override { + ctx->skipBits(n_bits); + } + + PackSize estimate(const EstimateContext* ctx) override { + return PackSize(n_bits); + } +}; + +struct S_Bool final : ISerializer { + void pack(const PackContext* ctx, CodeBlob& code, SrcLocation loc, std::vector&& rvect) override { + tolk_assert(rvect.size() == 1); + ctx->storeBool(rvect[0]); + } + + std::vector unpack(const UnpackContext* ctx, CodeBlob& code, SrcLocation loc) override { + return ctx->loadInt(1, "(loaded-bool)"); + } + + void skip(const UnpackContext* ctx, CodeBlob& code, SrcLocation loc) override { + ctx->skipBits(1); + } + + PackSize estimate(const EstimateContext* ctx) override { + return PackSize(1); + } +}; + +struct S_RawTVMcell final : ISerializer { + void pack(const PackContext* ctx, CodeBlob& code, SrcLocation loc, std::vector&& rvect) override { + tolk_assert(rvect.size() == 1); + ctx->storeRef(rvect[0]); + } + + std::vector unpack(const UnpackContext* ctx, CodeBlob& code, SrcLocation loc) override { + FunctionPtr f_loadRef = lookup_function("slice.loadRef"); + std::vector args = ctx->ir_slice; + std::vector ir_result = code.create_tmp_var(TypeDataCell::create(), loc, "(loaded-cell)"); + code.emplace_back(loc, Op::_Call, std::vector{ctx->ir_slice0, ir_result[0]}, std::move(args), f_loadRef); + return ir_result; + } + + void skip(const UnpackContext* ctx, CodeBlob& code, SrcLocation loc) override { + FunctionPtr f_loadRef = lookup_function("slice.loadRef"); + std::vector args = ctx->ir_slice; + std::vector dummy_loaded = code.create_tmp_var(TypeDataCell::create(), loc, "(loaded-cell)"); + code.emplace_back(loc, Op::_Call, std::vector{ctx->ir_slice0, dummy_loaded[0]}, std::move(args), f_loadRef); + } + + PackSize estimate(const EstimateContext* ctx) override { + return PackSize(0, 0, 1, 1); + } +}; + +struct S_RawTVMcellOrNull final : ISerializer { + void pack(const PackContext* ctx, CodeBlob& code, SrcLocation loc, std::vector&& rvect) override { + tolk_assert(rvect.size() == 1); + ctx->storeMaybeRef(rvect[0]); + } + + std::vector unpack(const UnpackContext* ctx, CodeBlob& code, SrcLocation loc) override { + FunctionPtr f_loadMaybeRef = lookup_function("slice.loadMaybeRef"); + std::vector args = ctx->ir_slice; + std::vector ir_result = code.create_tmp_var(TypeDataCell::create(), loc, "(loaded-cell)"); + code.emplace_back(loc, Op::_Call, std::vector{ctx->ir_slice0, ir_result[0]}, std::move(args), f_loadMaybeRef); + return ir_result; + } + + void skip(const UnpackContext* ctx, CodeBlob& code, SrcLocation loc) override { + FunctionPtr f_skipMaybeRef = lookup_function("slice.skipMaybeRef"); + code.emplace_back(loc, Op::_Call, ctx->ir_slice, ctx->ir_slice, f_skipMaybeRef); + } + + PackSize estimate(const EstimateContext* ctx) override { + return PackSize(1, 1, 0, 1); + } +}; + +struct S_Coins final : ISerializer { + void pack(const PackContext* ctx, CodeBlob& code, SrcLocation loc, std::vector&& rvect) override { + tolk_assert(rvect.size() == 1); + ctx->storeCoins(rvect[0]); + } + + std::vector unpack(const UnpackContext* ctx, CodeBlob& code, SrcLocation loc) override { + FunctionPtr f_loadCoins = lookup_function("slice.loadCoins"); + std::vector args = ctx->ir_slice; + std::vector ir_result = code.create_tmp_var(TypeDataInt::create(), loc, "(loaded-coins)"); + code.emplace_back(loc, Op::_Call, std::vector{ctx->ir_slice0, ir_result[0]}, std::move(args), f_loadCoins); + return ir_result; + } + + void skip(const UnpackContext* ctx, CodeBlob& code, SrcLocation loc) override { + FunctionPtr f_loadCoins = lookup_function("slice.loadCoins"); + std::vector args = ctx->ir_slice; + std::vector dummy_loaded = code.create_tmp_var(TypeDataInt::create(), loc, "(loaded-coins)"); + code.emplace_back(loc, Op::_Call, std::vector{ctx->ir_slice0, dummy_loaded[0]}, std::move(args), f_loadCoins); + } + + PackSize estimate(const EstimateContext* ctx) override { + return PackSize(4, 124); + } +}; + +struct S_Address final : ISerializer { + void pack(const PackContext* ctx, CodeBlob& code, SrcLocation loc, std::vector&& rvect) override { + tolk_assert(rvect.size() == 1); + ctx->storeAddress(rvect[0]); + } + + std::vector unpack(const UnpackContext* ctx, CodeBlob& code, SrcLocation loc) override { + FunctionPtr f_loadAddress = lookup_function("slice.loadAddress"); + std::vector ir_address = code.create_tmp_var(TypeDataSlice::create(), loc, "(loaded-addr)"); + code.emplace_back(loc, Op::_Call, std::vector{ctx->ir_slice0, ir_address[0]}, ctx->ir_slice, f_loadAddress); + return ir_address; + } + + void skip(const UnpackContext* ctx, CodeBlob& code, SrcLocation loc) override { + // we can't do just + // ctx->skipBits(2 + 1 + 8 + 256); + // because it may be addr_none or addr_extern; there is no "skip address" in TVM, so just load it + FunctionPtr f_loadAddress = lookup_function("slice.loadAddress"); + std::vector ir_address = code.create_tmp_var(TypeDataSlice::create(), loc, "(tmp-addr)"); + code.emplace_back(loc, Op::_Call, std::vector{ctx->ir_slice0, ir_address[0]}, ctx->ir_slice, f_loadAddress); + } + + PackSize estimate(const EstimateContext* ctx) override { + // we can't do just + // return PackSize(2 + 1 + 8 + 256); + // because it may be addr_none or addr_extern; but since addr_extern is very-very uncommon, don't consider it + return PackSize(2, 2 + 1 + 8 + 256); + } +}; + +struct S_RemainingBitsAndRefs final : ISerializer { + void pack(const PackContext* ctx, CodeBlob& code, SrcLocation loc, std::vector&& rvect) override { + tolk_assert(rvect.size() == 1); + ctx->storeSlice(rvect[0]); + } + + std::vector unpack(const UnpackContext* ctx, CodeBlob& code, SrcLocation loc) override { + return ctx->ir_slice; + } + + void skip(const UnpackContext* ctx, CodeBlob& code, SrcLocation loc) override { + FunctionPtr f_beginCell = lookup_function("beginCell"); + FunctionPtr f_endCell = lookup_function("builder.endCell"); + FunctionPtr f_beginParse = lookup_function("cell.beginParse"); + + std::vector ir_builder = code.create_tmp_var(TypeDataBuilder::create(), loc, "(tmp-builder)"); + std::vector ir_cell = code.create_tmp_var(TypeDataCell::create(), loc, "(tmp-cell)"); + code.emplace_back(loc, Op::_Call, ir_builder, std::vector{}, f_beginCell); + code.emplace_back(loc, Op::_Call, ir_cell, ir_builder, f_endCell); + code.emplace_back(loc, Op::_Call, ctx->ir_slice, ir_cell, f_beginParse); + } + + PackSize estimate(const EstimateContext* ctx) override { + return PackSize::unpredictable_infinity(); + } +}; + +struct S_Builder final : ISerializer { + void pack(const PackContext* ctx, CodeBlob& code, SrcLocation loc, std::vector&& rvect) override { + tolk_assert(rvect.size() == 1); + std::vector args = { ctx->ir_builder0, rvect[0] }; + code.emplace_back(loc, Op::_Call, ctx->ir_builder, std::move(args), lookup_function("builder.storeBuilder")); + } + + std::vector unpack(const UnpackContext* ctx, CodeBlob& code, SrcLocation loc) override { + tolk_assert(false); // `builder` can only be used for writing, checked earlier + } + + void skip(const UnpackContext* ctx, CodeBlob& code, SrcLocation loc) override { + tolk_assert(false); // `builder` can only be used for writing, checked earlier + } + + PackSize estimate(const EstimateContext* ctx) override { + return PackSize::unpredictable_infinity(); + } +}; + +struct S_Null final : ISerializer { + void pack(const PackContext* ctx, CodeBlob& code, SrcLocation loc, std::vector&& rvect) override { + // while `null` itself is not serializable, it may be contained inside a union: + // `int32 | int64 | null`, for example; + // then the compiler generates prefixes for every variant, and `null` variant does nothing + } + + std::vector unpack(const UnpackContext* ctx, CodeBlob& code, SrcLocation loc) override { + std::vector ir_null = code.create_tmp_var(TypeDataNullLiteral::create(), loc, "(null)"); + code.emplace_back(loc, Op::_Call, ir_null, std::vector{}, lookup_function("__null")); + return ir_null; + } + + void skip(const UnpackContext* ctx, CodeBlob& code, SrcLocation loc) override { + } + + PackSize estimate(const EstimateContext* ctx) override { + return PackSize(0); + } +}; + +struct S_Never final : ISerializer { + void pack(const PackContext* ctx, CodeBlob& code, SrcLocation loc, std::vector&& rvect) override { + tolk_assert(rvect.empty()); + } + + std::vector unpack(const UnpackContext* ctx, CodeBlob& code, SrcLocation loc) override { + return {}; + } + + void skip(const UnpackContext* ctx, CodeBlob& code, SrcLocation loc) override { + } + + PackSize estimate(const EstimateContext* ctx) override { + return PackSize(0); + } +}; + +struct S_Maybe final : ISerializer { + const TypeDataUnion* t_union; + TypePtr or_null; + + explicit S_Maybe(const TypeDataUnion* t_union) + : t_union(t_union) + , or_null(t_union->or_null) { + } + + void pack(const PackContext* ctx, CodeBlob& code, SrcLocation loc, std::vector&& rvect) override { + std::vector ir_is_null = pre_compile_is_type(code, t_union, TypeDataNullLiteral::create(), rvect, loc, "(is-null)"); + Op& if_op = code.emplace_back(loc, Op::_If, ir_is_null); + { + code.push_set_cur(if_op.block0); + ctx->storeUint(code.create_int(loc, 0, "(maybeBit)"), 1); + code.close_pop_cur(loc); + } + { + code.push_set_cur(if_op.block1); + ctx->storeUint(code.create_int(loc, 1, "(maybeBit)"), 1); + rvect = transition_to_target_type(std::move(rvect), code, t_union, t_union->or_null, loc); + ctx->generate_pack_any(t_union->or_null, std::move(rvect)); + code.close_pop_cur(loc); + } + } + + std::vector unpack(const UnpackContext* ctx, CodeBlob& code, SrcLocation loc) override { + std::vector ir_result = code.create_tmp_var(t_union, loc, "(loaded-maybe)"); + std::vector ir_not_null = { ctx->loadUint(1, "(maybeBit)") }; + Op& if_op = code.emplace_back(loc, Op::_If, std::move(ir_not_null)); + { + code.push_set_cur(if_op.block0); + std::vector rvect_maybe = ctx->generate_unpack_any(t_union->or_null); + rvect_maybe = transition_to_target_type(std::move(rvect_maybe), code, t_union->or_null, t_union, loc); + code.emplace_back(loc, Op::_Let, ir_result, std::move(rvect_maybe)); + code.close_pop_cur(loc); + } + { + code.push_set_cur(if_op.block1); + std::vector rvect_null = code.create_tmp_var(TypeDataNullLiteral::create(), loc, "(maybe-null)"); + code.emplace_back(loc, Op::_Call, rvect_null, std::vector{}, lookup_function("__null")); + rvect_null = transition_to_target_type(std::move(rvect_null), code, TypeDataNullLiteral::create(), t_union, loc); + code.emplace_back(loc, Op::_Let, ir_result, std::move(rvect_null)); + code.close_pop_cur(loc); + } + return ir_result; + } + + void skip(const UnpackContext* ctx, CodeBlob& code, SrcLocation loc) override { + std::vector ir_not_null = { ctx->loadUint(1, "(maybeBit)") }; + Op& if_op = code.emplace_back(loc, Op::_If, std::move(ir_not_null)); + { + code.push_set_cur(if_op.block0); + ctx->generate_skip_any(t_union->or_null); + code.close_pop_cur(loc); + } + { + code.push_set_cur(if_op.block1); + code.close_pop_cur(loc); + } + } + + PackSize estimate(const EstimateContext* ctx) override { + PackSize maybe_size = ctx->estimate_any(t_union->or_null); + return PackSize(1, 1 + maybe_size.max_bits, 0, maybe_size.max_refs); + } +}; + +struct S_Either final : ISerializer { + const TypeDataUnion* t_union; + TypePtr t_left; + TypePtr t_right; + + explicit S_Either(const TypeDataUnion* t_union) + : t_union(t_union) + , t_left(t_union->variants[0]) + , t_right(t_union->variants[1]) { + } + + void pack(const PackContext* ctx, CodeBlob& code, SrcLocation loc, std::vector&& rvect) override { + std::vector ir_is_right = pre_compile_is_type(code, t_union, t_right, rvect, loc, "(is-right)"); + Op& if_op = code.emplace_back(loc, Op::_If, ir_is_right); + { + code.push_set_cur(if_op.block0); + ctx->storeUint(code.create_int(loc, 1, "(eitherBit)"), 1); + std::vector rvect_right = transition_to_target_type(std::vector(rvect), code, t_union, t_right, loc); + ctx->generate_pack_any(t_right, std::move(rvect_right)); + code.close_pop_cur(loc); + } + { + code.push_set_cur(if_op.block1); + ctx->storeUint(code.create_int(loc, 0, "(eitherBit)"), 1); + std::vector rvect_left = transition_to_target_type(std::move(rvect), code, t_union, t_left, loc); + ctx->generate_pack_any(t_left, std::move(rvect_left)); + code.close_pop_cur(loc); + } + } + + std::vector unpack(const UnpackContext* ctx, CodeBlob& code, SrcLocation loc) override { + std::vector ir_result = code.create_tmp_var(t_union, loc, "(loaded-either)"); + std::vector ir_is_right = ctx->loadUint(1, "(eitherBit)"); + Op& if_op = code.emplace_back(loc, Op::_If, std::move(ir_is_right)); + { + code.push_set_cur(if_op.block0); + std::vector rvect_right = ctx->generate_unpack_any(t_right); + rvect_right = transition_to_target_type(std::move(rvect_right), code, t_right, t_union, loc); + code.emplace_back(loc, Op::_Let, ir_result, std::move(rvect_right)); + code.close_pop_cur(loc); + } + { + code.push_set_cur(if_op.block1); + std::vector rvect_left = ctx->generate_unpack_any(t_left); + rvect_left = transition_to_target_type(std::move(rvect_left), code, t_left, t_union, loc); + code.emplace_back(loc, Op::_Let, ir_result, std::move(rvect_left)); + code.close_pop_cur(loc); + } + return ir_result; + } + + void skip(const UnpackContext* ctx, CodeBlob& code, SrcLocation loc) override { + std::vector ir_is_right = ctx->loadUint(1, "(eitherBit)"); + Op& if_op = code.emplace_back(loc, Op::_If, std::move(ir_is_right)); + { + code.push_set_cur(if_op.block0); + ctx->generate_skip_any(t_right); + code.close_pop_cur(loc); + } + { + code.push_set_cur(if_op.block1); + ctx->generate_skip_any(t_left); + code.close_pop_cur(loc); + } + } + + PackSize estimate(const EstimateContext* ctx) override { + PackSize either_size = EstimateContext::minmax(ctx->estimate_any(t_left), ctx->estimate_any(t_right)); + return EstimateContext::sum(PackSize(1), either_size); + } +}; + +struct S_MultipleConstructors final : ISerializer { + const TypeDataUnion* t_union; + std::vector opcodes; + + explicit S_MultipleConstructors(const TypeDataUnion* t_union, std::vector&& opcodes) + : t_union(t_union) + , opcodes(std::move(opcodes)) { + tolk_assert(this->opcodes.size() == t_union->variants.size()); + } + + void pack(const PackContext* ctx, CodeBlob& code, SrcLocation loc, std::vector&& rvect) override { + for (int i = 0; i < t_union->size() - 1; ++i) { + TypePtr variant = t_union->variants[i]; + std::vector ir_eq_ith = pre_compile_is_type(code, t_union, variant, rvect, loc, "(arm-cond-eq)"); + Op& if_op = code.emplace_back(loc, Op::_If, std::move(ir_eq_ith)); + code.push_set_cur(if_op.block0); + std::vector ith_rvect = transition_to_target_type(std::vector(rvect), code, t_union, variant, loc); + ctx->storeUint(code.create_int(loc, opcodes[i].pack_prefix, "(ith-prefix)"), opcodes[i].prefix_len); + ctx->generate_pack_any(variant, std::move(ith_rvect), PrefixWriteMode::DoNothingAlreadyWritten); + code.close_pop_cur(loc); + code.push_set_cur(if_op.block1); // open ELSE + } + + // we're inside the last ELSE + TypePtr last_variant = t_union->variants.back(); + std::vector last_rvect = transition_to_target_type(std::move(rvect), code, t_union, last_variant, loc); + ctx->storeUint(code.create_int(loc, opcodes.back().pack_prefix, "(ith-prefix)"), opcodes.back().prefix_len); + ctx->generate_pack_any(last_variant, std::move(last_rvect), PrefixWriteMode::DoNothingAlreadyWritten); + for (int i = 0; i < t_union->size() - 1; ++i) { + code.close_pop_cur(loc); // close all outer IFs + } + } + + std::vector unpack(const UnpackContext* ctx, CodeBlob& code, SrcLocation loc) override { + // assume that opcodes (either automatically generated or manually specified) + // form a valid prefix tree, and the order of reading does not matter; we'll definitely match the one; + FunctionPtr f_tryStripPrefix = lookup_function("slice.tryStripPrefix"); + + std::vector ir_result = code.create_tmp_var(t_union, loc, "(loaded-union)"); + std::vector ir_prefix_eq = code.create_tmp_var(TypeDataInt::create(), loc, "(prefix-eq)"); + + for (int i = 0; i < t_union->size(); ++i) { + TypePtr variant = t_union->variants[i]; + std::vector args = { ctx->ir_slice0, code.create_int(loc, opcodes[i].pack_prefix, "(pack-prefix)"), code.create_int(loc, opcodes[i].prefix_len, "(prefix-len)") }; + code.emplace_back(loc, Op::_Call, std::vector{ctx->ir_slice0, ir_prefix_eq[0]}, std::move(args), f_tryStripPrefix); + Op& if_prefix_eq = code.emplace_back(loc, Op::_If, ir_prefix_eq); + code.push_set_cur(if_prefix_eq.block0); + std::vector ith_rvect = ctx->generate_unpack_any(variant, PrefixReadMode::DoNothingAlreadyLoaded); + ith_rvect = transition_to_target_type(std::move(ith_rvect), code, variant, t_union, loc); + code.emplace_back(loc, Op::_Let, ir_result, std::move(ith_rvect)); + code.close_pop_cur(loc); + code.push_set_cur(if_prefix_eq.block1); // open ELSE + } + + // we're inside last ELSE + std::vector args_throw = { ctx->option_throwIfOpcodeDoesNotMatch }; + Op& op_throw = code.emplace_back(loc, Op::_Call, std::vector{}, std::move(args_throw), lookup_function("__throw")); + op_throw.set_impure_flag(); + for (int j = 0; j < t_union->size(); ++j) { + code.close_pop_cur(loc); // close all outer IFs + } + return ir_result; + } + + void skip(const UnpackContext* ctx, CodeBlob& code, SrcLocation loc) override { + FunctionPtr f_tryStripPrefix = lookup_function("slice.tryStripPrefix"); + std::vector ir_prefix_eq = code.create_tmp_var(TypeDataInt::create(), loc, "(prefix-eq)"); + + for (int i = 0; i < t_union->size(); ++i) { + TypePtr variant = t_union->variants[i]; + std::vector args = { ctx->ir_slice0, code.create_int(loc, opcodes[i].pack_prefix, "(pack-prefix)"), code.create_int(loc, opcodes[i].prefix_len, "(prefix-len)") }; + code.emplace_back(loc, Op::_Call, std::vector{ctx->ir_slice0, ir_prefix_eq[0]}, std::move(args), f_tryStripPrefix); + Op& if_prefix_eq = code.emplace_back(loc, Op::_If, ir_prefix_eq); + code.push_set_cur(if_prefix_eq.block0); + ctx->generate_skip_any(variant, PrefixReadMode::DoNothingAlreadyLoaded); + code.close_pop_cur(loc); + code.push_set_cur(if_prefix_eq.block1); // open ELSE + } + + // we're inside last ELSE + std::vector args_throw = { ctx->option_throwIfOpcodeDoesNotMatch }; + Op& op_throw = code.emplace_back(loc, Op::_Call, std::vector{}, std::move(args_throw), lookup_function("__throw")); + op_throw.set_impure_flag(); + for (int j = 0; j < t_union->size(); ++j) { + code.close_pop_cur(loc); // close all outer IFs + } + } + + PackSize estimate(const EstimateContext* ctx) override { + PackSize variants_size = ctx->estimate_any(t_union->variants[0], PrefixEstimateMode::DoNothingAlreadyIncluded); + PackSize prefix_size(opcodes[0].prefix_len); + + for (int i = 1; i < t_union->size(); ++i) { + variants_size = EstimateContext::minmax(variants_size, ctx->estimate_any(t_union->variants[i], PrefixEstimateMode::DoNothingAlreadyIncluded)); + prefix_size = EstimateContext::minmax(prefix_size, PackSize(opcodes[i].prefix_len)); + } + + return EstimateContext::sum(variants_size, prefix_size); + } +}; + +struct S_Tensor final : ISerializer { + const TypeDataTensor* t_tensor; + + explicit S_Tensor(const TypeDataTensor* t_tensor) + : t_tensor(t_tensor) { + } + + void pack(const PackContext* ctx, CodeBlob& code, SrcLocation loc, std::vector&& rvect) override { + int stack_offset = 0; + for (TypePtr item : t_tensor->items) { + int stack_width = item->get_width_on_stack(); + std::vector item_vars(rvect.begin() + stack_offset, rvect.begin() + stack_offset + stack_width); + ctx->generate_pack_any(item, std::move(item_vars)); + stack_offset += stack_width; + } + tolk_assert(stack_offset == t_tensor->get_width_on_stack()); + } + + std::vector unpack(const UnpackContext* ctx, CodeBlob& code, SrcLocation loc) override { + std::vector tensor_vars; + tensor_vars.reserve(t_tensor->get_width_on_stack()); + for (TypePtr item : t_tensor->items) { + std::vector item_vars = ctx->generate_unpack_any(item); + tensor_vars.insert(tensor_vars.end(), item_vars.begin(), item_vars.end()); + } + tolk_assert(static_cast(tensor_vars.size()) == t_tensor->get_width_on_stack()); + return tensor_vars; + } + + void skip(const UnpackContext* ctx, CodeBlob& code, SrcLocation loc) override { + for (TypePtr item : t_tensor->items) { + ctx->generate_skip_any(item); + } + } + + PackSize estimate(const EstimateContext* ctx) override { + PackSize sum = PackSize(0); + for (TypePtr item : t_tensor->items) { + sum = EstimateContext::sum(sum, ctx->estimate_any(item)); + } + return sum; + } +}; + +struct S_CustomStruct final : ISerializer { + StructPtr struct_ref; + + explicit S_CustomStruct(StructPtr struct_ref) + : struct_ref(struct_ref) { + } + + void pack(const PackContext* ctx, CodeBlob& code, SrcLocation loc, std::vector&& rvect) override { + if (struct_ref->opcode.exists() && ctx->get_prefix_mode() == PrefixWriteMode::WritePrefixOfStruct) { + ctx->storeOpcode(struct_ref->opcode); + } + + int stack_offset = 0; + for (StructFieldPtr field_ref : struct_ref->fields) { + int stack_width = field_ref->declared_type->get_width_on_stack(); + std::vector field_vars(rvect.begin() + stack_offset, rvect.begin() + stack_offset + stack_width); + ctx->generate_pack_any(field_ref->declared_type, std::move(field_vars)); + stack_offset += stack_width; + } + tolk_assert(stack_offset == TypeDataStruct::create(struct_ref)->get_width_on_stack()); + } + + std::vector unpack(const UnpackContext* ctx, CodeBlob& code, SrcLocation loc) override { + if (struct_ref->opcode.exists() && ctx->get_prefix_mode() == PrefixReadMode::LoadAndCheck) { + ctx->loadAndCheckOpcode(struct_ref->opcode); + } + + int total_stack_w = TypeDataStruct::create(struct_ref)->get_width_on_stack(); + std::vector ir_struct; + ir_struct.reserve(total_stack_w); + for (StructFieldPtr field_ref : struct_ref->fields) { + std::vector field_vars = ctx->generate_unpack_any(field_ref->declared_type); + ir_struct.insert(ir_struct.end(), field_vars.begin(), field_vars.end()); + } + tolk_assert(static_cast(ir_struct.size()) == total_stack_w); + return ir_struct; + } + + void skip(const UnpackContext* ctx, CodeBlob& code, SrcLocation loc) override { + if (struct_ref->opcode.exists() && ctx->get_prefix_mode() == PrefixReadMode::LoadAndCheck) { + ctx->loadAndCheckOpcode(struct_ref->opcode); + } + + for (StructFieldPtr field_ref : struct_ref->fields) { + ctx->generate_skip_any(field_ref->declared_type); + } + } + + PackSize estimate(const EstimateContext* ctx) override { + PackSize sum(0); + + if (struct_ref->opcode.exists() && ctx->get_prefix_mode() == PrefixEstimateMode::IncludePrefixOfStruct) { + sum = EstimateContext::sum(sum, PackSize(struct_ref->opcode.prefix_len)); + } + + for (StructFieldPtr field_ref : struct_ref->fields) { + sum = EstimateContext::sum(sum, ctx->estimate_any(field_ref->declared_type)); + } + return sum; + } +}; + + +// -------------------------------------------- +// automatically generate opcodes +// +// for union types like `T1 | T2 | ...`, if prefixes for structs are not manually specified, +// the compiler generates a valid prefix tree: for `int32 | int64 | int128` it's '00' '01' '10'; +// it works both for structs (with unspecified prefixes) and primitives: `int32 | A | B` is ok; +// but if some prefixes are specified, some not — it's an error +// + + +std::vector auto_generate_opcodes_for_union(TypePtr union_type, std::string& because_msg) { + const TypeDataUnion* t_union = union_type->try_as(); + std::vector result; + result.reserve(t_union->size()); + + int n_have_opcode = 0; + bool has_null = false; + StructPtr last_struct_with_opcode = nullptr; // for error message + StructPtr last_struct_no_opcode = nullptr; + for (TypePtr variant : t_union->variants) { + if (const TypeDataStruct* variant_struct = variant->unwrap_alias()->try_as()) { + if (variant_struct->struct_ref->opcode.exists()) { + n_have_opcode++; + last_struct_with_opcode = variant_struct->struct_ref; + } else { + last_struct_no_opcode = variant_struct->struct_ref; + } + } else if (variant == TypeDataNullLiteral::create()) { + has_null = true; + } + } + + // `A | B | C`, all of them have opcodes — just use them; + // for instance, `A | B` is not either (0/1 + data), but uses manual opcodes + if (n_have_opcode == t_union->size()) { + for (TypePtr variant : t_union->variants) { + result.push_back(variant->unwrap_alias()->try_as()->struct_ref->opcode); + } + return result; + } + + // invalid: `A | B | C`, some of them have opcodes, some not; + // example: `A | B` if A has opcode, B not; + // example: `int32 | A` if A has opcode; + // example: `int32 | int64 | A` if A has opcode; + if (n_have_opcode) { + if (last_struct_with_opcode && last_struct_no_opcode) { + because_msg = "because struct `" + last_struct_with_opcode->as_human_readable() + "` has opcode, but `" + last_struct_no_opcode->as_human_readable() + "` does not\nhint: manually specify opcodes to all structures"; + } else { + because_msg = "because of mixing primitives and struct `" + last_struct_with_opcode->as_human_readable() + "` with serialization prefix\nhint: extract primitives to single-field structs and provide prefixes"; + } + return result; + } + + // okay, none of the opcodes are specified, generate a prefix tree; + // examples: `int32 | int64 | int128` / `int32 | A | null` / `A | B` / `A | B | C`; + // create prefixes `0b00 0b01 0b10` / `0b01 0b10 0b00` (use 0b00 for null if exists): + // for 3/4 variants — two bits, for 5 — three + int prefix_len = static_cast(std::ceil(std::log2(t_union->size()))); + int cur_prefix = has_null ? 1 : 0; // will use 0b00 for null, so start with 0b01 + for (TypePtr variant : t_union->variants) { + if (variant == TypeDataNullLiteral::create()) { + result.emplace_back(0, prefix_len); + } else { + result.emplace_back(cur_prefix++, prefix_len); + } + } + return result; +} + + +// -------------------------------------------- +// detect serializer by TypePtr +// +// note that at earlier compilation steps there already passed a check that any_type is serializable; +// see `check_struct_can_be_packed_or_unpacked()`, its structure reminds this function +// + + +static std::unique_ptr get_serializer_for_type(TypePtr any_type) { + if (const auto* t_intN = any_type->try_as()) { + return std::make_unique(t_intN->n_bits, t_intN->is_unsigned); + } + if (const auto* t_bytesN = any_type->try_as()) { + return std::make_unique(t_bytesN->n_width, t_bytesN->is_bits); + } + if (any_type == TypeDataCoins::create()) { + return std::make_unique(); + } + if (any_type == TypeDataBool::create()) { + return std::make_unique(); + } + if (any_type == TypeDataCell::create()) { + return std::make_unique(); + } + if (any_type == TypeDataAddress::create()) { + return std::make_unique(); + } + if (any_type == TypeDataBuilder::create()) { + return std::make_unique(); + } + if (any_type == TypeDataNullLiteral::create()) { + return std::make_unique(); + } + if (any_type == TypeDataNever::create()) { + return std::make_unique(); + } + + if (const auto* t_struct = any_type->try_as()) { + return std::make_unique(t_struct->struct_ref); + } + + if (const auto* t_union = any_type->try_as()) { + // `T?` is always `(Maybe T)`, even if T has custom opcode (opcode will follow bit '1') + if (t_union->or_null) { + TypePtr or_null = t_union->or_null->unwrap_alias(); + if (or_null == TypeDataCell::create() || is_type_cellT(or_null)) { + return std::make_unique(); + } + return std::make_unique(t_union); + } + + // `T1 | T2` is `(Either T1 T2)` (0/1 + contents) unless they both have custom prefixes + bool all_have_opcode = true; + for (TypePtr variant : t_union->variants) { + const TypeDataStruct* variant_struct = variant->unwrap_alias()->try_as(); + all_have_opcode &= variant_struct && variant_struct->struct_ref->opcode.exists(); + } + if (t_union->size() == 2 && !all_have_opcode) { + return std::make_unique(t_union); + } + // `T1 | T2 | T3`, probably nullable, probably with primitives, probably with custom opcodes; + // compiler is able to generate serialization prefixes automatically; + // and this type is valid, it was checked earlier + std::string err_msg; + std::vector opcodes = auto_generate_opcodes_for_union(t_union, err_msg); + tolk_assert(err_msg.empty()); + return std::make_unique(t_union, std::move(opcodes)); + } + + if (const auto* t_tensor = any_type->try_as()) { + return std::make_unique(t_tensor); + } + + if (const auto* t_alias = any_type->try_as()) { + if (t_alias->alias_ref->name == "RemainingBitsAndRefs") { + return std::make_unique(); + } + return get_serializer_for_type(t_alias->underlying_type); + } + + // this should not be reachable, serialization availability is checked earlier + throw Fatal("type `" + any_type->as_human_readable() + "` can not be serialized"); +} + + +void PackContext::generate_pack_any(TypePtr any_type, std::vector&& rvect, PrefixWriteMode prefix_mode) const { + PrefixWriteMode backup = this->prefix_mode; + this->prefix_mode = prefix_mode; + get_serializer_for_type(any_type)->pack(this, code, loc, std::move(rvect)); + this->prefix_mode = backup; +} + +std::vector UnpackContext::generate_unpack_any(TypePtr any_type, PrefixReadMode prefix_mode) const { + PrefixReadMode backup = this->prefix_mode; + this->prefix_mode = prefix_mode; + std::vector result = get_serializer_for_type(any_type)->unpack(this, code, loc); + this->prefix_mode = backup; + return result; +} + +void UnpackContext::generate_skip_any(TypePtr any_type, PrefixReadMode prefix_mode) const { + PrefixReadMode backup = this->prefix_mode; + this->prefix_mode = prefix_mode; + get_serializer_for_type(any_type)->skip(this, code, loc); + this->prefix_mode = backup; +} + +PackSize EstimateContext::estimate_any(TypePtr any_type, PrefixEstimateMode prefix_mode) const { + PrefixEstimateMode backup = this->prefix_mode; + this->prefix_mode = prefix_mode; + PackSize result = get_serializer_for_type(any_type)->estimate(this); + this->prefix_mode = backup; + return result; +} + +} // namespace tolk diff --git a/tolk/pack-unpack-serializers.h b/tolk/pack-unpack-serializers.h new file mode 100644 index 0000000000..c2c99ea2ae --- /dev/null +++ b/tolk/pack-unpack-serializers.h @@ -0,0 +1,149 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain 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. + + TON Blockchain 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. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . +*/ +#pragma once + +#include "fwd-declarations.h" +#include "tolk.h" + +namespace tolk { + +using PackOpcode = StructData::PackOpcode; + +struct PackSize { + int min_bits; + int max_bits; + int min_refs; + int max_refs; + + bool is_unpredictable_infinity() const { + return max_bits >= 9999; + } + + explicit PackSize(int exact_bits) + : min_bits(exact_bits), max_bits(exact_bits), min_refs(0), max_refs(0) { + } + PackSize(int min_bits, int max_bits) + : min_bits(min_bits), max_bits(max_bits), min_refs(0), max_refs() { + } + PackSize(int min_bits, int max_bits, int min_refs, int max_refs) + : min_bits(min_bits), max_bits(max_bits), min_refs(min_refs), max_refs(max_refs) { + } + + static PackSize unpredictable_infinity() { + return PackSize(0, 9999, 0, 4); + } +}; + + +enum class PrefixWriteMode { + WritePrefixOfStruct, + DoNothingAlreadyWritten, +}; + +class PackContext { + CodeBlob& code; + SrcLocation loc; + const FunctionPtr f_storeInt; + const FunctionPtr f_storeUint; + mutable PrefixWriteMode prefix_mode = PrefixWriteMode::WritePrefixOfStruct; + +public: + const std::vector ir_builder; + const var_idx_t ir_builder0; + const var_idx_t option_skipBitsNFieldsValidation; + + PackContext(CodeBlob& code, SrcLocation loc, std::vector ir_builder, const std::vector& ir_options); + + PrefixWriteMode get_prefix_mode() const { return prefix_mode; } + + void storeInt(var_idx_t ir_idx, int len) const; + void storeUint(var_idx_t ir_idx, int len) const; + void storeBool(var_idx_t ir_idx) const; + void storeCoins(var_idx_t ir_idx) const; + void storeRef(var_idx_t ir_idx) const; + void storeMaybeRef(var_idx_t ir_idx) const; + void storeAddress(var_idx_t ir_idx) const; + void storeBuilder(var_idx_t ir_idx) const; + void storeSlice(var_idx_t ir_idx) const; + void storeOpcode(PackOpcode opcode) const; + + void generate_pack_any(TypePtr any_type, std::vector&& rvect, PrefixWriteMode prefix_mode = PrefixWriteMode::WritePrefixOfStruct) const; +}; + + +enum class PrefixReadMode { + LoadAndCheck, + DoNothingAlreadyLoaded, +}; + +class UnpackContext { + CodeBlob& code; + SrcLocation loc; + const FunctionPtr f_loadInt; + const FunctionPtr f_loadUint; + const FunctionPtr f_skipBits; + mutable PrefixReadMode prefix_mode = PrefixReadMode::LoadAndCheck; + +public: + const std::vector ir_slice; + const var_idx_t ir_slice0; + const var_idx_t option_assertEndAfterReading; + const var_idx_t option_throwIfOpcodeDoesNotMatch; + + UnpackContext(CodeBlob& code, SrcLocation loc, std::vector ir_slice, const std::vector& ir_options); + + PrefixReadMode get_prefix_mode() const { return prefix_mode; } + + std::vector loadInt(int len, const char* debug_desc) const; + std::vector loadUint(int len, const char* debug_desc) const; + void loadAndCheckOpcode(PackOpcode opcode) const; + void skipBits(int len) const; + void skipBits_var(var_idx_t ir_len) const; + void assertEndIfOption() const; + + std::vector generate_unpack_any(TypePtr any_type, PrefixReadMode prefix_mode = PrefixReadMode::LoadAndCheck) const; + void generate_skip_any(TypePtr any_type, PrefixReadMode prefix_mode = PrefixReadMode::LoadAndCheck) const; +}; + + +enum class PrefixEstimateMode { + IncludePrefixOfStruct, + DoNothingAlreadyIncluded, +}; + +class EstimateContext { + mutable PrefixEstimateMode prefix_mode = PrefixEstimateMode::IncludePrefixOfStruct; + +public: + + PrefixEstimateMode get_prefix_mode() const { return prefix_mode; } + + static PackSize minmax(PackSize a, PackSize b) { + return PackSize(std::min(a.min_bits, b.min_bits), std::max(a.max_bits, b.max_bits), std::min(a.min_refs, b.min_refs), std::max(a.max_refs, b.max_refs)); + } + static PackSize sum(PackSize a, PackSize b) { + return PackSize(a.min_bits + b.min_bits, std::min(9999, a.max_bits + b.max_bits), a.min_refs + b.min_refs, a.max_refs + b.max_refs); + } + + PackSize estimate_any(TypePtr any_type, PrefixEstimateMode prefix_mode = PrefixEstimateMode::IncludePrefixOfStruct) const; +}; + + +bool is_type_cellT(TypePtr any_type); +std::vector auto_generate_opcodes_for_union(TypePtr union_type, std::string& because_msg); + +} // namespace tolk diff --git a/tolk/pipe-ast-to-legacy.cpp b/tolk/pipe-ast-to-legacy.cpp index a45a4ccb6f..91891f8c16 100644 --- a/tolk/pipe-ast-to-legacy.cpp +++ b/tolk/pipe-ast-to-legacy.cpp @@ -22,6 +22,8 @@ #include "type-system.h" #include "common/refint.h" #include "smart-casts-cfg.h" +#include "pack-unpack-api.h" +#include "generics-helpers.h" #include /* @@ -205,7 +207,7 @@ class LValContext { code.emplace_back(loc, Op::_IntConst, index_ir_idx, td::make_refint(index_at)); vars_modification_watcher.trigger_callbacks(tuple_ir_idx, loc); - FunctionPtr builtin_sym = lookup_global_symbol("tuple.set")->try_as(); + FunctionPtr builtin_sym = lookup_function("tuple.set"); code.emplace_back(loc, Op::_Call, std::vector{tuple_ir_idx}, std::vector{tuple_ir_idx[0], lval_ir_idx[0], index_ir_idx[0]}, builtin_sym); local_lval.after_let(std::move(tuple_ir_idx), code, loc); } @@ -281,14 +283,15 @@ class LValContext { // the purpose of this class is having a call `f(a1,a2,...)` when f has asm arg_order, to check // whether it's safe to rearrange arguments (to evaluate them in arg_order right here for fewer stack manipulations) // or it's unsafe, and we should evaluate them left-to-right; -// example: `f(1,2,3)` / `b.storeUint(2,32)` is safe -// example: `f(x,x+=5,x)` / `f(impureF1(), global_var)` is unsafe +// example: `f(1,2,3)` / `b.storeUint(2,32)` is safe; +// example: `f(x,x+=5,x)` / `f(impureF1(), global_var)` / `f(s.loadInt(), s.loadInt())` is unsafe; +// the same rules are used to check an object literal: is it safe to convert `{y:expr, x:expr}` to declaration order {x,y} class CheckReorderingForAsmArgOrderIsSafeVisitor final : public ASTVisitorFunctionBody { bool has_side_effects = false; protected: void visit(V v) override { - has_side_effects |= v->fun_maybe == nullptr || !v->fun_maybe->is_marked_as_pure(); + has_side_effects |= v->fun_maybe == nullptr || !v->fun_maybe->is_marked_as_pure() || v->fun_maybe->has_mutate_params(); parent::visit(v); } @@ -323,6 +326,14 @@ class CheckReorderingForAsmArgOrderIsSafeVisitor final : public ASTVisitorFuncti } return !visitor.has_side_effects; } + + static bool is_safe_to_reorder(V v) { + CheckReorderingForAsmArgOrderIsSafeVisitor visitor; + for (int i = 0; i < v->get_num_fields(); ++i) { + visitor.ASTVisitorFunctionBody::visit(v->get_field(i)->get_init_val()); + } + return !visitor.has_side_effects; + } }; // given `{some_expr}!`, return some_expr @@ -482,10 +493,10 @@ static std::vector pre_compile_let(CodeBlob& code, AnyExprV lhs, AnyE return right; } -static std::vector pre_compile_is_type(CodeBlob& code, TypePtr expr_type, TypePtr cmp_type, const std::vector& expr_ir_idx, SrcLocation loc, const char* debug_desc) { - FunctionPtr eq_sym = lookup_global_symbol("_==_")->try_as(); - FunctionPtr isnull_sym = lookup_global_symbol("__isNull")->try_as(); - FunctionPtr not_sym = lookup_global_symbol("!b_")->try_as(); +std::vector pre_compile_is_type(CodeBlob& code, TypePtr expr_type, TypePtr cmp_type, const std::vector& expr_ir_idx, SrcLocation loc, const char* debug_desc) { + FunctionPtr eq_sym = lookup_function("_==_"); + FunctionPtr isnull_sym = lookup_function("__isNull"); + FunctionPtr not_sym = lookup_function("!b_"); std::vector result_ir_idx = code.create_tmp_var(TypeDataBool::create(), loc, debug_desc); const TypeDataUnion* lhs_union = expr_type->try_as(); @@ -529,6 +540,59 @@ static std::vector gen_op_call(CodeBlob& code, TypePtr ret_type, SrcL return rvect; } +static std::vector gen_compile_time_code_instead_of_fun_call(CodeBlob& code, SrcLocation loc, const std::vector>& vars_per_arg, FunctionPtr called_f) { + if (called_f->is_instantiation_of_generic_function()) { + std::string_view f_name = called_f->base_fun_ref->name; + TypePtr typeT = called_f->substitutedTs->typeT_at(0); + + if (f_name == "T.toCell") { + // in: object T, out: Cell (just a cell, wrapped) + std::vector ir_obj = vars_per_arg[0]; + return generate_pack_struct_to_cell(code, loc, typeT, std::move(ir_obj), vars_per_arg[1]); + } + if (f_name == "T.fromCell") { + // in: cell, out: object T + std::vector ir_cell = vars_per_arg[0]; + return generate_unpack_struct_from_cell(code, loc, typeT, std::move(ir_cell), vars_per_arg[1]); + } + if (f_name == "T.fromSlice") { + // in: slice, out: object T, input slice NOT mutated + std::vector ir_slice = vars_per_arg[0]; + return generate_unpack_struct_from_slice(code, loc, typeT, std::move(ir_slice), false, vars_per_arg[1]); + } + if (f_name == "Cell.load") { + // in: cell, out: object T + std::vector ir_cell = vars_per_arg[0]; + return generate_unpack_struct_from_cell(code, loc, typeT, std::move(ir_cell), vars_per_arg[1]); + } + if (f_name == "slice.loadAny") { + // in: slice, out: object T, input slice is mutated, so prepend self before an object + var_idx_t ir_self = vars_per_arg[0][0]; + std::vector ir_slice = vars_per_arg[0]; + std::vector ir_obj = generate_unpack_struct_from_slice(code, loc, typeT, std::move(ir_slice), true, vars_per_arg[1]); + std::vector ir_result = {ir_self}; + ir_result.insert(ir_result.end(), ir_obj.begin(), ir_obj.end()); + return ir_result; + } + if (f_name == "slice.skipAny") { + // in: slice, out: the same slice, with a shifted pointer + std::vector ir_slice = vars_per_arg[0]; + return generate_skip_struct_in_slice(code, loc, typeT, std::move(ir_slice), vars_per_arg[1]); + } + if (f_name == "builder.storeAny") { + // in: builder and object T, out: mutated builder + std::vector ir_builder = vars_per_arg[0]; + std::vector ir_obj = vars_per_arg[1]; + return generate_pack_struct_to_builder(code, loc, typeT, std::move(ir_builder), std::move(ir_obj), vars_per_arg[2]); + } + if (f_name == "T.estimatePackSize") { + return generate_estimate_size_call(code, loc, typeT); + } + } + + tolk_assert(false); +} + // "Transition to target (runtime) type" is the following process. // Imagine `fun analyze(t: (int,int)?)` and a call `analyze((1,2))`. // `(1,2)` (inferred_type) is 2 stack slots, but `t` (target_type) is 3 (one for null-flag). @@ -606,7 +670,7 @@ static std::vector transition_expr_to_runtime_type_impl(std::vector 1 && original_type == TypeDataNullLiteral::create()) { tolk_assert(t_union->has_null()); - FunctionPtr null_sym = lookup_global_symbol("__null")->try_as(); + FunctionPtr null_sym = lookup_function("__null"); rvect.reserve(target_w); // keep rvect[0], it's already null for (int i = 1; i < target_w - 1; ++i) { std::vector ith_null = code.create_tmp_var(TypeDataNullLiteral::create(), loc, "(null-literal)"); @@ -648,7 +712,7 @@ static std::vector transition_expr_to_runtime_type_impl(std::vectorhas_null()); - FunctionPtr null_sym = lookup_global_symbol("__null")->try_as(); + FunctionPtr null_sym = lookup_function("__null"); std::vector new_rvect = code.create_tmp_var(TypeDataNullLiteral::create(), loc, "(null-literal)"); code.emplace_back(loc, Op::_Call, new_rvect, std::vector{}, null_sym); return new_rvect; @@ -665,7 +729,7 @@ static std::vector transition_expr_to_runtime_type_impl(std::vectortry_as(); + FunctionPtr null_sym = lookup_function("__null"); std::vector new_rvect; new_rvect.resize(target_w); for (int i = 0; i < target_w - 2; ++i) { // N-1 nulls @@ -677,7 +741,7 @@ static std::vector transition_expr_to_runtime_type_impl(std::vector eq_null_cond = code.create_tmp_var(TypeDataBool::create(), loc, "(value-is-null)"); - FunctionPtr isnull_sym = lookup_global_symbol("__isNull")->try_as(); + FunctionPtr isnull_sym = lookup_function("__isNull"); code.emplace_back(loc, Op::_Call, eq_null_cond, rvect, isnull_sym); Op& if_op = code.emplace_back(loc, Op::_If, eq_null_cond); code.push_set_cur(if_op.block0); @@ -703,7 +767,7 @@ static std::vector transition_expr_to_runtime_type_impl(std::vector prepend_nulls; prepend_nulls.reserve(target_w - t_subtype->get_width_on_stack() - 1); for (int i = 0; i < target_w - t_subtype->get_width_on_stack() - 1; ++i) { - FunctionPtr null_sym = lookup_global_symbol("__null")->try_as(); + FunctionPtr null_sym = lookup_function("__null"); std::vector ith_null = code.create_tmp_var(TypeDataNullLiteral::create(), loc, "(UVar.null)"); prepend_nulls.push_back(ith_null[0]); code.emplace_back(loc, Op::_Call, std::move(ith_null), std::vector{}, null_sym); @@ -738,7 +802,7 @@ static std::vector transition_expr_to_runtime_type_impl(std::vector prepend_nulls; prepend_nulls.reserve(target_w - orig_w); for (int i = 0; i < target_w - orig_w; ++i) { - FunctionPtr null_sym = lookup_global_symbol("__null")->try_as(); + FunctionPtr null_sym = lookup_function("__null"); std::vector ith_null_ir_idx = code.create_tmp_var(TypeDataNullLiteral::create(), loc, "(UVar.null)"); prepend_nulls.push_back(ith_null_ir_idx[0]); code.emplace_back(loc, Op::_Call, std::move(ith_null_ir_idx), std::vector{}, null_sym); @@ -889,6 +953,29 @@ static std::vector transition_expr_to_runtime_type_impl(std::vector` to `cell`, e.g. `setContractData(obj.toCell())` + if (target_type == TypeDataCell::create() && original_type->try_as()) { + tolk_assert(orig_w == 1 && original_type->try_as()->struct_ref->is_instantiation_of_generic_struct()); + return rvect; + } + // and vice versa, `cell as Cell` + if (original_type == TypeDataCell::create() && target_type->try_as()) { + tolk_assert(target_w == 1 && target_type->try_as()->struct_ref->is_instantiation_of_generic_struct()); + return rvect; + } + throw Fatal("unhandled transition_expr_to_runtime_type_impl() combination"); } @@ -908,7 +995,7 @@ static std::vector transition_to_target_type(std::vector&& #ifndef TOLK_DEBUG GNU_ATTRIBUTE_ALWAYS_INLINE #endif -static std::vector transition_to_target_type(std::vector&& rvect, CodeBlob& code, TypePtr original_type, TypePtr target_type, SrcLocation loc) { +std::vector transition_to_target_type(std::vector&& rvect, CodeBlob& code, TypePtr original_type, TypePtr target_type, SrcLocation loc) { if (target_type != original_type) { rvect = transition_expr_to_runtime_type_impl(std::move(rvect), code, original_type, target_type, loc); } @@ -1016,7 +1103,7 @@ static std::vector process_binary_operator(V v, v_1->mutate()->assign_inferred_type(TypeDataInt::create()); auto v_b_ne_0 = createV(v->loc, "!=", tok_neq, v->get_rhs(), v_0); v_b_ne_0->mutate()->assign_inferred_type(TypeDataInt::create()); - v_b_ne_0->mutate()->assign_fun_ref(lookup_global_symbol("_!=_")->try_as()); + v_b_ne_0->mutate()->assign_fun_ref(lookup_function("_!=_")); std::vector cond = pre_compile_expr(v->get_lhs(), code, nullptr); tolk_assert(cond.size() == 1); std::vector rvect = code.create_tmp_var(v->inferred_type, v->loc, "(ternary)"); @@ -1029,6 +1116,20 @@ static std::vector process_binary_operator(V v, code.close_pop_cur(v->loc); return transition_to_target_type(std::move(rvect), code, target_type, v); } + if (t == tok_eq || t == tok_neq) { + if (v->get_lhs()->inferred_type->unwrap_alias() == TypeDataAddress::create() && v->get_rhs()->inferred_type->unwrap_alias() == TypeDataAddress::create()) { + FunctionPtr f_sliceEq = lookup_function("slice.bitsEqual"); + std::vector ir_lhs_slice = pre_compile_expr(v->get_lhs(), code); + std::vector ir_rhs_slice = pre_compile_expr(v->get_rhs(), code); + std::vector rvect = code.create_tmp_var(TypeDataBool::create(), v->loc, "(addr-eq)"); + code.emplace_back(v->loc, Op::_Call, rvect, std::vector{ir_lhs_slice[0], ir_rhs_slice[0]}, f_sliceEq); + if (t == tok_neq) { + FunctionPtr not_sym = lookup_function("!b_"); + code.emplace_back(v->loc, Op::_Call, rvect, rvect, not_sym); + } + return transition_to_target_type(std::move(rvect), code, target_type, v); + } + } throw UnexpectedASTNodeKind(v, "process_binary_operator"); } @@ -1077,7 +1178,7 @@ static std::vector process_is_type_operator(V v std::vector result_ir_idx = pre_compile_is_type(code, lhs_type, cmp_type, expr_ir_idx, v->loc, is_null_check ? "(is-null)" : "(is-type)"); if (v->is_negated) { - FunctionPtr not_sym = lookup_global_symbol("!b_")->try_as(); + FunctionPtr not_sym = lookup_function("!b_"); code.emplace_back(v->loc, Op::_Call, result_ir_idx, result_ir_idx, not_sym); } return transition_to_target_type(std::move(result_ir_idx), code, target_type, v); @@ -1117,7 +1218,7 @@ static std::vector process_match_expression(V v // construct nested IFs: IF is int { ... } ELSE { IF is slice { ... } ELSE { ... } } // example 3 (not exhaustive): `match (v) { -1 => ... 0 => ... 1 => ... }` // construct nested IFs: IF == -1 { ... } ELSE { IF == 0 { ... } ELSE { IF == 1 { ... } } } - FunctionPtr eq_sym = lookup_global_symbol("_==_")->try_as(); + FunctionPtr eq_sym = lookup_function("_==_"); for (int i = 0; i < n_arms - is_exhaustive; ++i) { auto v_ith_arm = v->get_arm(i); std::vector eq_ith_ir_idx; @@ -1224,7 +1325,7 @@ static std::vector process_dot_access(V v, CodeBlob& code.emplace_back(v->loc, Op::_IntConst, index_ir_idx, td::make_refint(index_at)); std::vector field_ir_idx = code.create_tmp_var(v->inferred_type, v->loc, "(tuple-field)"); tolk_assert(tuple_ir_idx.size() == 1 && field_ir_idx.size() == 1); // tuples contain only 1-slot values - FunctionPtr builtin_sym = lookup_global_symbol("tuple.get")->try_as(); + FunctionPtr builtin_sym = lookup_function("tuple.get"); code.emplace_back(v->loc, Op::_Call, field_ir_idx, std::vector{tuple_ir_idx[0], index_ir_idx[0]}, builtin_sym); if (lval_ctx && calc_sink_leftmost_obj(v)) { // `tupleVar.0.1 = rhs`, then `tupleVar.0` is rval inside lval lval_ctx->capture_tuple_index_modification(v->get_obj(), index_at, field_ir_idx); @@ -1272,10 +1373,11 @@ static std::vector process_function_call(V v, Code return transition_to_target_type(std::move(rvect), code, target_type, v); } + // fill args for evaluation: dot object + passed arguments + parameters defaults if not all passed AnyExprV obj_leftmost = v->get_self_obj(); int delta_self = obj_leftmost != nullptr; std::vector args; - args.reserve(delta_self + v->get_num_args()); + args.reserve(fun_ref->get_num_params()); if (delta_self) { args.push_back(obj_leftmost); while (obj_leftmost->kind == ast_function_call && obj_leftmost->as()->get_self_obj() && obj_leftmost->as()->fun_maybe && obj_leftmost->as()->fun_maybe->does_return_self()) { @@ -1285,6 +1387,14 @@ static std::vector process_function_call(V v, Code for (int i = 0; i < v->get_num_args(); ++i) { args.push_back(v->get_arg(i)->get_expr()); } + for (int i = delta_self + v->get_num_args(); i < fun_ref->get_num_params(); ++i) { + LocalVarPtr param_ref = &fun_ref->get_param(i); + tolk_assert(param_ref->has_default_value()); + SrcLocation last_loc = args.empty() ? v->loc : args.back()->loc; + ASTAuxData *aux_data = new AuxData_ForceFiftLocation(last_loc); + auto v_force_loc = createV(last_loc, param_ref->default_value, aux_data, param_ref->declared_type); + args.push_back(v_force_loc); + } // the purpose of tensor_tt ("tensor target type") is to transition `null` to `(int, int)?` and so on // the purpose of calling `pre_compile_tensor_inner` is to have 0-th IR vars to handle return self @@ -1336,7 +1446,9 @@ static std::vector process_function_call(V v, Code for (const std::vector& list : vars_per_arg) { args_vars.insert(args_vars.end(), list.cbegin(), list.cend()); } - std::vector rvect_apply = gen_op_call(code, op_call_type, v->loc, std::move(args_vars), fun_ref, "(fun-call)", arg_order_already_equals_asm); + std::vector rvect_apply = fun_ref->is_compile_time_special_gen() + ? gen_compile_time_code_instead_of_fun_call(code, v->loc, vars_per_arg, fun_ref) + : gen_op_call(code, op_call_type, v->loc, std::move(args_vars), fun_ref, "(fun-call)", arg_order_already_equals_asm); if (fun_ref->has_mutate_params()) { LValContext local_lval; @@ -1399,10 +1511,78 @@ static std::vector process_typed_tuple(V v, CodeBl return transition_to_target_type(std::move(left), code, target_type, v); } +static std::vector process_object_literal_shuffled(V v, CodeBlob& code, TypePtr target_type, LValContext* lval_ctx) { + // creating an object like `Point { y: getY(), x: getX() }`, where fields order doesn't match declaration; + // as opposed to a non-shuffled version `{x:..., y:...}`, we should at first evaluate fields as they created, + // and then to place them in a correct order + std::vector tensor_items; // create a tensor of literal fields values + std::vector target_types; + tensor_items.reserve(v->get_body()->get_num_fields()); + target_types.reserve(v->get_body()->get_num_fields()); + for (int i = 0; i < v->get_body()->get_num_fields(); ++i) { + auto v_field = v->get_body()->get_field(i); + StructFieldPtr field_ref = v->struct_ref->find_field(v_field->get_field_name()); + tensor_items.push_back(v_field->get_init_val()); + target_types.push_back(field_ref->declared_type); + } + const auto* tensor_target_type = TypeDataTensor::create(std::move(target_types))->try_as(); + std::vector literal_rvect = pre_compile_tensor(code, tensor_items, lval_ctx, tensor_target_type); + + std::vector rvect = code.create_tmp_var(TypeDataStruct::create(v->struct_ref), v->loc, "(object)"); + int stack_offset = 0; + for (StructFieldPtr field_ref : v->struct_ref->fields) { + int stack_width = field_ref->declared_type->get_width_on_stack(); + std::vector field_rvect(rvect.begin() + stack_offset, rvect.begin() + stack_offset + stack_width); + stack_offset += stack_width; + + int tensor_offset = 0; + bool exists_in_literal = false; + for (int i = 0; i < v->get_body()->get_num_fields(); ++i) { + auto v_field = v->get_body()->get_field(i); + int tensor_item_width = v_field->field_ref->declared_type->get_width_on_stack(); + if (v_field->get_field_name() == field_ref->name) { + exists_in_literal = true; + std::vector literal_field_rvect(literal_rvect.begin() + tensor_offset, literal_rvect.begin() + tensor_offset + tensor_item_width); + code.emplace_back(v->loc, Op::_Let, std::move(field_rvect), std::move(literal_field_rvect)); + break; + } + tensor_offset += tensor_item_width; + } + if (exists_in_literal || field_ref->declared_type == TypeDataNever::create()) { + continue; + } + + tolk_assert(field_ref->has_default_value()); + SrcLocation last_loc = v->get_body()->empty() ? v->loc : v->get_body()->get_all_fields().back()->loc; + ASTAuxData *aux_data = new AuxData_ForceFiftLocation(last_loc); + auto v_force_loc = createV(v->loc, field_ref->default_value, aux_data, field_ref->declared_type); + std::vector def_rvect = pre_compile_expr(v_force_loc, code, field_ref->declared_type); + code.emplace_back(v->loc, Op::_Let, std::move(field_rvect), std::move(def_rvect)); + } + + return transition_to_target_type(std::move(rvect), code, target_type, v); +} + static std::vector process_object_literal(V v, CodeBlob& code, TypePtr target_type, LValContext* lval_ctx) { // an object (an instance of a struct) is actually a tensor at low-level // for example, `struct User { id: int; name: slice; }` occupies 2 slots // fields of a tensor are placed in order of declaration (in a literal they might be shuffled) + bool are_fields_shuffled = false; + for (int i = 1; i < v->get_body()->get_num_fields(); ++i) { + StructFieldPtr field_ref = v->struct_ref->find_field(v->get_body()->get_field(i)->get_field_name()); + StructFieldPtr prev_field_ref = v->struct_ref->find_field(v->get_body()->get_field(i - 1)->get_field_name()); + are_fields_shuffled |= prev_field_ref->field_idx > field_ref->field_idx; + } + + // if fields are created {y,x} (not {x,y}), maybe, it's nevertheless safe to evaluate them as {x,y}; + // for example, if they are just constants, calls to pure non-mutating functions, etc.; + // generally, rules of "can we evaluate {x,y} instead of {y,x}" follows the same logic + // as passing of calling `f(x,y)` with asm arg_order, is it safe to avoid SWAP + if (are_fields_shuffled && !CheckReorderingForAsmArgOrderIsSafeVisitor::is_safe_to_reorder(v->get_body())) { + // okay, we have `{y: getY(), x: getX()}` / `{y: v += 1, x: v}`, evaluate them in created order + return process_object_literal_shuffled(v, code, target_type, lval_ctx); + } + SrcLocation prev_loc = v->loc; std::vector tensor_items; std::vector target_types; @@ -1450,13 +1630,13 @@ static std::vector process_string_const(V v, CodeBl } static std::vector process_bool_const(V v, CodeBlob& code, TypePtr target_type) { - FunctionPtr builtin_sym = lookup_global_symbol(v->bool_val ? "__true" : "__false")->try_as(); + FunctionPtr builtin_sym = lookup_function(v->bool_val ? "__true" : "__false"); std::vector rvect = gen_op_call(code, v->inferred_type, v->loc, {}, builtin_sym, "(bool-const)"); return transition_to_target_type(std::move(rvect), code, target_type, v); } static std::vector process_null_keyword(V v, CodeBlob& code, TypePtr target_type) { - FunctionPtr builtin_sym = lookup_global_symbol("__null")->try_as(); + FunctionPtr builtin_sym = lookup_function("__null"); std::vector rvect = gen_op_call(code, v->inferred_type, v->loc, {}, builtin_sym, "(null-literal)"); return transition_to_target_type(std::move(rvect), code, target_type, v); } @@ -1476,6 +1656,7 @@ static std::vector process_local_var(V v, CodeBlob static std::vector process_local_vars_declaration(V, CodeBlob&) { // it can not appear as a standalone expression // `var ... = rhs` is handled by ast_assign + // `var rhs: int lateinit` is ast_local_var_lhs tolk_assert(false); } @@ -1581,7 +1762,7 @@ static void process_assert_statement(V v, CodeBlob& code) args[2]->mutate()->assign_inferred_type(TypeDataInt::create()); } - FunctionPtr builtin_sym = lookup_global_symbol("__throw_if_unless")->try_as(); + FunctionPtr builtin_sym = lookup_function("__throw_if_unless"); std::vector args_vars = pre_compile_tensor(code, args); gen_op_call(code, TypeDataVoid::create(), v->loc, std::move(args_vars), builtin_sym, "(throw-call)"); } @@ -1677,9 +1858,9 @@ static void process_do_while_statement(V v, CodeBlob& co } until_cond->mutate()->assign_inferred_type(TypeDataInt::create()); if (auto v_bin = until_cond->try_as(); v_bin && !v_bin->fun_ref) { - v_bin->mutate()->assign_fun_ref(lookup_global_symbol("_" + static_cast(v_bin->operator_name) + "_")->try_as()); + v_bin->mutate()->assign_fun_ref(lookup_function("_" + static_cast(v_bin->operator_name) + "_")); } else if (auto v_un = until_cond->try_as(); v_un && !v_un->fun_ref) { - v_un->mutate()->assign_fun_ref(lookup_global_symbol(static_cast(v_un->operator_name) + "_")->try_as()); + v_un->mutate()->assign_fun_ref(lookup_function(static_cast(v_un->operator_name) + "_")); } until_op.left = pre_compile_expr(until_cond, code, nullptr); @@ -1700,11 +1881,11 @@ static void process_while_statement(V v, CodeBlob& code) { static void process_throw_statement(V v, CodeBlob& code) { if (v->has_thrown_arg()) { - FunctionPtr builtin_sym = lookup_global_symbol("__throw_arg")->try_as(); + FunctionPtr builtin_sym = lookup_function("__throw_arg"); std::vector args_vars = pre_compile_tensor(code, {v->get_thrown_arg(), v->get_thrown_code()}); gen_op_call(code, TypeDataVoid::create(), v->loc, std::move(args_vars), builtin_sym, "(throw-call)"); } else { - FunctionPtr builtin_sym = lookup_global_symbol("__throw")->try_as(); + FunctionPtr builtin_sym = lookup_function("__throw"); std::vector args_vars = pre_compile_tensor(code, {v->get_thrown_code()}); gen_op_call(code, TypeDataVoid::create(), v->loc, std::move(args_vars), builtin_sym, "(throw-call)"); } diff --git a/tolk/pipe-calc-rvalue-lvalue.cpp b/tolk/pipe-calc-rvalue-lvalue.cpp index b6a239116c..d451a7c601 100644 --- a/tolk/pipe-calc-rvalue-lvalue.cpp +++ b/tolk/pipe-calc-rvalue-lvalue.cpp @@ -246,7 +246,7 @@ class CalculateRvalueLvalueVisitor final : public ASTVisitorFunctionBody { } void visit(V v) override { - tolk_assert(cur_state == MarkingState::LValue); + tolk_assert(cur_state == MarkingState::LValue || v->is_lateinit); mark_vertex(v); parent::visit(v); } diff --git a/tolk/pipe-check-inferred-types.cpp b/tolk/pipe-check-inferred-types.cpp index a863fc5929..ba15825592 100644 --- a/tolk/pipe-check-inferred-types.cpp +++ b/tolk/pipe-check-inferred-types.cpp @@ -65,6 +65,10 @@ static void fire_error_cannot_apply_operator(FunctionPtr cur_f, SrcLocation loc, GNU_ATTRIBUTE_NOINLINE static void warning_condition_always_true_or_false(FunctionPtr cur_f, SrcLocation loc, AnyExprV cond, const char* operator_name) { + bool no_warning = cond->kind == ast_bool_const || cond->kind == ast_int_const; + if (no_warning) { // allow `while(true)` without a warning + return; + } loc.show_warning("condition of " + static_cast(operator_name) + " is always " + (cond->is_always_true ? "true" : "false")); } @@ -155,6 +159,20 @@ static bool expect_boolean(AnyExprV v_inferred) { return expect_boolean(v_inferred->inferred_type); } +static bool expect_address(TypePtr inferred_type) { + if (inferred_type == TypeDataAddress::create()) { + return true; + } + if (const TypeDataAlias* as_alias = inferred_type->try_as()) { + return expect_address(as_alias->underlying_type); + } + return false; +} + +static bool expect_address(AnyExprV v_inferred) { + return expect_address(v_inferred->inferred_type); +} + class CheckInferredTypesVisitor final : public ASTVisitorFunctionBody { FunctionPtr cur_f = nullptr; // may be nullptr if checking `const a = ...` init_value @@ -211,7 +229,10 @@ class CheckInferredTypesVisitor final : public ASTVisitorFunctionBody { bool both_int = expect_integer(lhs) && expect_integer(rhs); bool both_bool = expect_boolean(lhs) && expect_boolean(rhs); if (!both_int && !both_bool) { - if (lhs->inferred_type->equal_to(rhs->inferred_type)) { // compare slice with slice, int? with int? + bool both_address = expect_address(lhs) && expect_address(rhs); + if (both_address) { // address can be compared with ==, but it's not integer comparison, it's handled specially + v->mutate()->assign_fun_ref(nullptr); + } else if (lhs->inferred_type->equal_to(rhs->inferred_type)) { // compare slice with slice, int? with int? fire(cur_f, v->loc, "type " + to_string(lhs) + " can not be compared with `== !=`"); } else { fire_error_cannot_apply_operator(cur_f, v->loc, v->operator_name, lhs, rhs); @@ -709,6 +730,18 @@ class CheckInferredTypesVisitor final : public ASTVisitorFunctionBody { fire(fun_ref, v_function->get_body()->as()->loc_end, "missing return"); } } + + // visit default values of parameters + for (int i = 0; i < fun_ref->get_num_params(); ++i) { + if (LocalVarPtr param_ref = &fun_ref->get_param(i); param_ref->has_default_value()) { + parent::visit(param_ref->default_value); + + TypePtr inferred_type = param_ref->default_value->inferred_type; + if (!param_ref->declared_type->can_rhs_be_assigned(inferred_type)) { + throw ParseError(param_ref->loc, "can not assign " + to_string(inferred_type) + " to " + to_string(param_ref->declared_type)); + } + } + } } // given `const a = 2 + 3` check types within its init_value diff --git a/tolk/pipe-check-serialized-fields.cpp b/tolk/pipe-check-serialized-fields.cpp new file mode 100644 index 0000000000..3ed8fc33a2 --- /dev/null +++ b/tolk/pipe-check-serialized-fields.cpp @@ -0,0 +1,115 @@ +/* + This file is part of TON Blockchain source code. + + TON Blockchain is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + TON Blockchain 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with TON Blockchain. If not, see . +*/ +#include "tolk.h" +#include "ast.h" +#include "ast-visitor.h" +#include "pack-unpack-api.h" +#include "generics-helpers.h" +#include "type-system.h" + +namespace tolk { + +// fire an error on overflow 1023 bits +GNU_ATTRIBUTE_NORETURN GNU_ATTRIBUTE_COLD +static void fire_error_theoretical_overflow_1023(StructPtr struct_ref, PackSize size) { + throw ParseError(struct_ref->ast_root->loc, + "struct `" + struct_ref->as_human_readable() + "` can exceed 1023 bits in serialization (estimated size: " + std::to_string(size.min_bits) + ".." + std::to_string(size.max_bits) + " bits)\n\n" + "1) either suppress it by adding an annotation:\n" + "> @overflow1023_policy(\"suppress\")\n" + "> struct " + struct_ref->name + " {\n" + "> ...\n" + "> }\n" + " then, if limit exceeds, it will fail at runtime: you've manually agreed to ignore this\n\n" + "2) or place some fields into a separate struct (e.g. ExtraFields), and create a ref:\n" + "> struct " + struct_ref->name + " {\n" + "> ...\n" + "> more: Cell;\n" + "> }\n" + ); +} + + +class CheckSerializedFieldsAndTypesVisitor final : public ASTVisitorFunctionBody { + FunctionPtr cur_f = nullptr; + + static void check_type_fits_cell_or_has_policy(TypePtr serialized_type) { + if (const TypeDataStruct* s_struct = serialized_type->unwrap_alias()->try_as()) { + check_struct_fits_cell_or_has_policy(s_struct); + } else if (const TypeDataUnion* s_union = serialized_type->unwrap_alias()->try_as()) { + for (TypePtr variant : s_union->variants) { + check_type_fits_cell_or_has_policy(variant); + } + } + } + + static void check_struct_fits_cell_or_has_policy(const TypeDataStruct* t_struct) { + StructPtr struct_ref = t_struct->struct_ref; + PackSize size = estimate_serialization_size(t_struct); + if (size.max_bits > 1023 && !size.is_unpredictable_infinity()) { + if (struct_ref->overflow1023_policy == StructData::Overflow1023Policy::not_specified) { + fire_error_theoretical_overflow_1023(struct_ref, size); + } + } + for (StructFieldPtr field_ref : struct_ref->fields) { + if (is_type_cellT(field_ref->declared_type)) { + const TypeDataStruct* f_struct = field_ref->declared_type->try_as(); + check_type_fits_cell_or_has_policy(f_struct->struct_ref->substitutedTs->typeT_at(0)); + } + } + } + + void visit(V v) override { + FunctionPtr fun_ref = v->fun_maybe; + if (!fun_ref || !fun_ref->is_compile_time_special_gen() || !fun_ref->is_instantiation_of_generic_function()) { + return; + } + + std::string_view f_name = fun_ref->base_fun_ref->name; + TypePtr serialized_type = nullptr; + bool is_pack = false; + if (f_name == "Cell.load" || f_name == "T.fromSlice" || f_name == "T.fromCell" || f_name == "T.toCell" || + f_name == "T.loadAny" || f_name == "slice.skipAny" || f_name == "slice.storeAny" || f_name == "T.estimatePackSize") { + serialized_type = fun_ref->substitutedTs->typeT_at(0); + is_pack = f_name == "T.toCell" || f_name == "slice.storeAny" || f_name == "T.estimatePackSize"; + } else { + return; // not a serialization function + } + + std::string because_msg; + if (!check_struct_can_be_packed_or_unpacked(serialized_type, is_pack, because_msg)) { + fire(cur_f, v->loc, "auto-serialization via " + fun_ref->method_name + "() is not available for type `" + serialized_type->as_human_readable() + "`\n" + because_msg); + } + + check_type_fits_cell_or_has_policy(serialized_type); + } + + public: + bool should_visit_function(FunctionPtr fun_ref) override { + return fun_ref->is_code_function() && !fun_ref->is_generic_function(); + } + + void start_visiting_function(FunctionPtr fun_ref, V v_function) override { + cur_f = fun_ref; + parent::visit(v_function->get_body()); + } +}; + +void pipeline_check_serialized_fields() { + visit_ast_of_all_functions(); +} + +} // namespace tolk diff --git a/tolk/pipe-constant-folding.cpp b/tolk/pipe-constant-folding.cpp index 32b7c28c2a..74fbb3453a 100644 --- a/tolk/pipe-constant-folding.cpp +++ b/tolk/pipe-constant-folding.cpp @@ -58,9 +58,9 @@ class ConstantFoldingReplacer final : public ASTReplacerInFunctionBody { return inner; } - static V create_string_const(SrcLocation loc, std::string&& literal_value) { + static V create_string_const(SrcLocation loc, std::string&& literal_value, TypePtr inferred_type) { auto v_string = createV(loc, literal_value); - v_string->assign_inferred_type(TypeDataSlice::create()); + v_string->assign_inferred_type(inferred_type); v_string->assign_literal_value(std::move(literal_value)); v_string->assign_rvalue_true(); return v_string; @@ -112,12 +112,12 @@ class ConstantFoldingReplacer final : public ASTReplacerInFunctionBody { parent::replace(v); // replace `ton("0.05")` with 50000000 / `stringCrc32("some_str")` with calculated value / etc. - if (v->fun_maybe && v->fun_maybe->is_compile_time_only()) { + if (v->fun_maybe && v->fun_maybe->is_compile_time_const_val()) { CompileTimeFunctionResult value = eval_call_to_compile_time_function(v); if (std::holds_alternative(value)) { return create_int_const(v->loc, std::move(std::get(value))); } else { - return create_string_const(v->loc, std::move(std::get(value))); + return create_string_const(v->loc, std::move(std::get(value)), v->fun_maybe->declared_return_type); } } @@ -150,6 +150,19 @@ class ConstantFoldingReplacer final : public ASTReplacerInFunctionBody { return fun_ref->is_code_function() && !fun_ref->is_generic_function(); } + void start_replacing_in_function(FunctionPtr fun_ref, V v_function) override { + // visit default values of parameters + for (int i = 0; i < fun_ref->get_num_params(); ++i) { + if (LocalVarPtr param_ref = &fun_ref->get_param(i); param_ref->has_default_value()) { + check_expression_is_constant(param_ref->default_value); + AnyExprV replaced = replace_in_expression(param_ref->default_value); + param_ref->mutate()->assign_default_value(replaced); + } + } + + parent::replace(v_function->get_body()); + } + // used to replace `ton("0.05")` and other compile-time functions inside fields defaults, etc. AnyExprV replace_in_expression(AnyExprV init_value) { return parent::replace(init_value); diff --git a/tolk/pipe-infer-types-and-calls.cpp b/tolk/pipe-infer-types-and-calls.cpp index abeaffa1f6..0345ac0aa1 100644 --- a/tolk/pipe-infer-types-and-calls.cpp +++ b/tolk/pipe-infer-types-and-calls.cpp @@ -21,6 +21,7 @@ #include "generics-helpers.h" #include "type-system.h" #include "smart-casts-cfg.h" +#include /* * This is a complicated and crucial part of the pipeline. It simultaneously does the following: @@ -150,6 +151,30 @@ static void fire_error_cannot_deduce_untyped_tuple_access(FunctionPtr cur_f, Src fire(cur_f, loc, "can not deduce type of `" + idx_access + "`\neither assign it to variable like `var c: int = " + idx_access + "` or cast the result like `" + idx_access + " as int`"); } +// fire an error on using lateinit variable before definite assignment +GNU_ATTRIBUTE_NORETURN GNU_ATTRIBUTE_COLD +static void fire_error_using_lateinit_variable_uninitialized(FunctionPtr cur_f, SrcLocation loc, std::string_view name) { + fire(cur_f, loc, "using variable `" + static_cast(name) + "` before it's definitely assigned"); +} + +// fire an error when `obj.f()`, method `f` not found, try to locate a method for another type +GNU_ATTRIBUTE_NORETURN GNU_ATTRIBUTE_COLD +static void fire_error_method_not_found(FunctionPtr cur_f, SrcLocation loc, TypePtr receiver_type, std::string_view method_name) { + if (std::vector other = lookup_methods_with_name(method_name); !other.empty()) { + fire(cur_f, loc, "method `" + to_string(method_name) + "` not found for type " + to_string(receiver_type) + "\n(but it exists for type " + to_string(other.front()->receiver_type) + ")"); + } + if (const Symbol* sym = lookup_global_symbol(method_name); sym && sym->try_as()) { + fire(cur_f, loc, "method `" + to_string(method_name) + "` not found, but there is a global function named `" + to_string(method_name) + "`\n(a function should be called `foo(arg)`, not `arg.foo()`)"); + } + fire(cur_f, loc, "method `" + to_string(method_name) + "` not found"); +} + +// safe version of std::stoi that does not crash on long numbers +static bool try_parse_string_to_int(std::string_view str, int& out) { + auto result = std::from_chars(str.data(), str.data() + str.size(), out); + return result.ec == std::errc() && result.ptr == str.data() + str.size(); +} + // helper function: given hint = `Ok | Err` and struct `Ok`, return `Ok` // example: `match (...) { Ok => ... }` we need to deduce `Ok` based on subject static TypePtr try_pick_instantiated_generic_from_hint(TypePtr hint, StructPtr lookup_ref) { @@ -229,6 +254,28 @@ static MethodCallCandidate choose_only_method_to_call(FunctionPtr cur_f, SrcLoca fire(cur_f, loc, msg.str()); } +// given fun `f` and a call `f(a,b,c)`, check that argument count is expected; +// (parameters may have default values, so it's not as trivial as to compare params and args size) +void check_arguments_count_at_fun_call(FunctionPtr cur_f, V v, FunctionPtr called_f, AnyExprV self_obj) { + int delta_self = self_obj != nullptr; + int n_arguments = v->get_num_args() + delta_self; + int n_max_params = called_f->get_num_params(); + int n_min_params = n_max_params; + while (n_min_params && called_f->get_param(n_min_params - 1).has_default_value()) { + n_min_params--; + } + + if (!called_f->does_accept_self() && self_obj) { // static method `Point.create(...)` called as `p.create()` + fire(cur_f, v->loc, "method " + to_string(called_f) + " can not be called via dot\n(it's a static method, it does not accept `self`)"); + } + if (n_max_params < n_arguments) { + fire(cur_f, v->loc, "too many arguments in call to " + to_string(called_f) + ", expected " + std::to_string(n_max_params - delta_self) + ", have " + std::to_string(n_arguments - delta_self)); + } + if (n_arguments < n_min_params) { + fire(cur_f, v->loc, "too few arguments in call to " + to_string(called_f) + ", expected " + std::to_string(n_min_params - delta_self) + ", have " + std::to_string(n_arguments - delta_self)); + } +} + /* * This class handles all types of AST vertices and traverses them, filling all AnyExprV::inferred_type. * Note, that it isn't derived from ASTVisitor, it has manual `switch` over all existing vertex types. @@ -406,6 +453,7 @@ class InferTypesAndCallsAndFieldsVisitor final { assign_inferred_type(v, v->var_ref->declared_type); } else { assign_inferred_type(v, v->type_node ? v->type_node->resolved_type : TypeDataUnknown::create()); + flow.register_known_type(SinkExpression(v->var_ref), TypeDataUnknown::create()); // it's unknown before assigned } return ExprFlow(std::move(flow), used_as_condition); } @@ -551,7 +599,7 @@ class InferTypesAndCallsAndFieldsVisitor final { assign_inferred_type(v, lhs); - FunctionPtr builtin_sym = lookup_global_symbol("_" + static_cast(builtin_func) + "_")->try_as(); + FunctionPtr builtin_sym = lookup_function("_" + static_cast(builtin_func) + "_"); v->mutate()->assign_fun_ref(builtin_sym); return ExprFlow(std::move(after_rhs.out_flow), used_as_condition); @@ -581,7 +629,7 @@ class InferTypesAndCallsAndFieldsVisitor final { tolk_assert(false); } - FunctionPtr builtin_sym = lookup_global_symbol(static_cast(builtin_func) + "_")->try_as(); + FunctionPtr builtin_sym = lookup_function(static_cast(builtin_func) + "_"); v->mutate()->assign_fun_ref(builtin_sym); return after_rhs; @@ -659,7 +707,7 @@ class InferTypesAndCallsAndFieldsVisitor final { } if (!builtin_func.empty()) { - FunctionPtr builtin_sym = lookup_global_symbol("_" + static_cast(builtin_func) + "_")->try_as(); + FunctionPtr builtin_sym = lookup_function("_" + static_cast(builtin_func) + "_"); v->mutate()->assign_fun_ref(builtin_sym); } @@ -823,7 +871,7 @@ class InferTypesAndCallsAndFieldsVisitor final { // T for asm function must be a TVM primitive (width 1), otherwise, asm would act incorrectly if (fun_ref->is_asm_function() || fun_ref->is_builtin_function()) { for (int i = 0; i < substitutedTs.size(); ++i) { - if (substitutedTs.typeT_at(i)->get_width_on_stack() != 1) { + if (substitutedTs.typeT_at(i)->get_width_on_stack() != 1 && !fun_ref->is_variadic_width_T_allowed()) { fire_error_calling_asm_function_with_non1_stack_width_arg(cur_f, loc, fun_ref, substitutedTs, i); } } @@ -842,6 +890,9 @@ class InferTypesAndCallsAndFieldsVisitor final { TypePtr declared_or_smart_casted = flow.smart_cast_if_exists(SinkExpression(var_ref)); tolk_assert(declared_or_smart_casted != nullptr); // all local vars are presented in flow assign_inferred_type(v, declared_or_smart_casted); + if (var_ref->is_lateinit() && declared_or_smart_casted == TypeDataUnknown::create() && v->is_rvalue) { + fire_error_using_lateinit_variable_uninitialized(cur_f, v->loc, v->get_name()); + } // it might be `local_var()` also, don't fill out_f_called, we have no fun_ref, it's a call of arbitrary expression } else if (GlobalConstPtr const_ref = v->sym->try_as()) { @@ -879,7 +930,7 @@ class InferTypesAndCallsAndFieldsVisitor final { if (out_f_called) { // so, it's `globalF()` / `genericFn()` / `genericFn()` *out_f_called = fun_ref; // (it's still may be a generic one, then Ts will be deduced from arguments) } else { // so, it's `globalF` / `genericFn` as a reference - if (fun_ref->is_compile_time_only()) { + if (fun_ref->is_compile_time_const_val() || fun_ref->is_compile_time_special_gen()) { fire(cur_f, v->loc, "can not get reference to this function, it's compile-time only"); } fun_ref->mutate()->assign_is_used_as_noncall(); @@ -948,7 +999,10 @@ class InferTypesAndCallsAndFieldsVisitor final { // check for indexed access (`tensorVar.0` / `tupleVar.1`) if (!fun_ref && field_name[0] >= '0' && field_name[0] <= '9') { - int index_at = std::stoi(std::string(field_name)); + int index_at; + if (!try_parse_string_to_int(field_name, index_at)) { + fire(cur_f, v_ident->loc, "invalid numeric index"); + } if (const auto* t_tensor = obj_type->try_as()) { if (index_at >= t_tensor->size()) { fire(cur_f, v_ident->loc, "invalid tensor index, expected 0.." + std::to_string(t_tensor->items.size() - 1)); @@ -1008,7 +1062,7 @@ class InferTypesAndCallsAndFieldsVisitor final { } } if (out_f_called) { - fire(cur_f, v_ident->loc, "method `" + to_string(v->get_field_name()) + "` not found for type " + to_string(obj_type)); + fire_error_method_not_found(cur_f, v_ident->loc, dot_obj->inferred_type, v->get_field_name()); } else { fire(cur_f, v_ident->loc, "field `" + static_cast(field_name) + "` doesn't exist in type " + to_string(dot_obj)); } @@ -1039,7 +1093,7 @@ class InferTypesAndCallsAndFieldsVisitor final { *out_f_called = fun_ref; // (it's still may be a generic one, then Ts will be deduced from arguments) *out_dot_obj = dot_obj; } else { // so, it's `user.method` / `t.tupleAt` as a reference - if (fun_ref->is_compile_time_only()) { + if (fun_ref->is_compile_time_const_val() || fun_ref->is_compile_time_special_gen()) { fire(cur_f, v->get_identifier()->loc, "can not get reference to this method, it's compile-time only"); } fun_ref->mutate()->assign_is_used_as_noncall(); @@ -1087,19 +1141,9 @@ class InferTypesAndCallsAndFieldsVisitor final { // so, we have a call `f(args)` or `obj.f(args)`, f is fun_ref (function / method) (code / asm / builtin) // we're going to iterate over passed arguments, and (if generic) infer substitutedTs - // at first, check arguments count (Tolk doesn't have optional parameters, so just compare counts) + // at first, check argument count int delta_self = self_obj != nullptr; - int n_arguments = v->get_num_args() + delta_self; - int n_parameters = fun_ref->get_num_params(); - if (!fun_ref->does_accept_self() && self_obj) { // static method `Point.create(...)` called as `p.create()` - fire(cur_f, v->loc, "method " + to_string(fun_ref) + " can not be called via dot\n(it's a static method, it does not accept `self`)"); - } - if (n_parameters < n_arguments) { - fire(cur_f, v->loc, "too many arguments in call to " + to_string(fun_ref) + ", expected " + std::to_string(n_parameters - delta_self) + ", have " + std::to_string(n_arguments - delta_self)); - } - if (n_arguments < n_parameters) { - fire(cur_f, v->loc, "too few arguments in call to " + to_string(fun_ref) + ", expected " + std::to_string(n_parameters - delta_self) + ", have " + std::to_string(n_arguments - delta_self)); - } + check_arguments_count_at_fun_call(cur_f, v, fun_ref, self_obj); // for every passed argument, we need to infer its type // for generic functions, we need to infer type arguments (substitutedTs) on the fly @@ -1154,7 +1198,7 @@ class InferTypesAndCallsAndFieldsVisitor final { if (fun_ref->is_generic_function()) { // if `f(args)` was called, Ts were inferred; check that all of them are known std::string_view nameT_unknown = deducingTs.get_first_not_deduced_nameT(); - if (!nameT_unknown.empty() && hint && fun_ref->declared_return_type) { + if (!nameT_unknown.empty() && hint && !hint->has_genericT_inside() && fun_ref->declared_return_type) { // example: `t.tupleFirst()`, T doesn't depend on arguments, but is determined by return type // if used like `var x: int = t.tupleFirst()` / `t.tupleFirst() as int` / etc., use hint deducingTs.auto_deduce_from_argument(cur_f, v->loc, fun_ref->declared_return_type, hint); @@ -1220,6 +1264,8 @@ class InferTypesAndCallsAndFieldsVisitor final { TypeInferringUnifyStrategy branches_unifier; FlowContext arms_entry_facts = flow.clone(); FlowContext match_out_flow; + bool has_expr_arm = false; + bool has_else_arm = false; for (int i = 0; i < v->get_arms_count(); ++i) { auto v_arm = v->get_arm(i); @@ -1245,7 +1291,13 @@ class InferTypesAndCallsAndFieldsVisitor final { } } arm_flow.register_known_type(s_expr, exact_type); + + } else if (v_arm->pattern_kind == MatchArmKind::const_expression) { + has_expr_arm = true; + } else if (v_arm->pattern_kind == MatchArmKind::else_branch) { + has_else_arm = true; } + arm_flow = infer_any_expr(v_arm->get_body(), std::move(arm_flow), false, hint).out_flow; match_out_flow = i == 0 ? std::move(arm_flow) : FlowContext::merge_flow(std::move(match_out_flow), std::move(arm_flow)); branches_unifier.unify_with(v_arm->get_body()->inferred_type, hint); @@ -1253,6 +1305,10 @@ class InferTypesAndCallsAndFieldsVisitor final { if (v->get_arms_count() == 0) { match_out_flow = std::move(arms_entry_facts); } + if (has_expr_arm && !has_else_arm) { + FlowContext else_flow = process_any_statement(createV(v->loc), std::move(arms_entry_facts)); + match_out_flow = FlowContext::merge_flow(std::move(match_out_flow), std::move(else_flow)); + } if (v->is_statement()) { assign_inferred_type(v, TypeDataVoid::create()); @@ -1594,6 +1650,16 @@ class InferTypesAndCallsAndFieldsVisitor final { tolk_assert(fun_ref->declared_return_type); } + // visit default values of parameters; to correctly track symbols in `fun f(a: int, b: int = a)`, use flow context + FlowContext params_flow; + for (int i = 0; i < fun_ref->get_num_params(); ++i) { + LocalVarPtr param_ref = &fun_ref->get_param(i); + if (param_ref->has_default_value()) { + params_flow = infer_any_expr(param_ref->default_value, std::move(params_flow), false, param_ref->declared_type).out_flow; + } + params_flow.register_known_type(SinkExpression(param_ref), param_ref->declared_type); + } + assign_fun_full_type(fun_ref, inferred_return_type); fun_ref->mutate()->assign_is_type_inferring_done(); } diff --git a/tolk/pipe-optimize-boolean-expr.cpp b/tolk/pipe-optimize-boolean-expr.cpp index 4b05923c2a..6b053915f7 100644 --- a/tolk/pipe-optimize-boolean-expr.cpp +++ b/tolk/pipe-optimize-boolean-expr.cpp @@ -53,7 +53,7 @@ struct OptimizerBooleanExpressionsReplacer final : ASTReplacerInFunctionBody { auto v_not = createV(loc, "!", tok_logical_not, rhs); v_not->assign_inferred_type(TypeDataBool::create()); v_not->assign_rvalue_true(); - v_not->assign_fun_ref(lookup_global_symbol("!b_")->try_as()); + v_not->assign_fun_ref(lookup_function("!b_")); return v_not; } @@ -98,7 +98,7 @@ struct OptimizerBooleanExpressionsReplacer final : ASTReplacerInFunctionBody { auto v_neq = createV(v->loc, "!=", tok_neq, cond_not_not, v_zero); v_neq->mutate()->assign_rvalue_true(); v_neq->mutate()->assign_inferred_type(TypeDataBool::create()); - v_neq->mutate()->assign_fun_ref(lookup_global_symbol("_!=_")->try_as()); + v_neq->mutate()->assign_fun_ref(lookup_function("_!=_")); return v_neq; } } @@ -145,6 +145,15 @@ struct OptimizerBooleanExpressionsReplacer final : ASTReplacerInFunctionBody { v_cond_istype->mutate()->assign_is_negated(!v_cond_istype->is_negated); v = createV(v->loc, !v->is_ifnot, v_cond_istype, v->get_if_body(), v->get_else_body()); } + // `if (addr1 != addr2)` -> ifnot(addr1 == addr2) + if (auto v_cond_neq = v->get_cond()->try_as()) { + if (v_cond_neq->tok == tok_neq && v_cond_neq->get_lhs()->inferred_type->unwrap_alias() == TypeDataAddress::create() && v_cond_neq->get_rhs()->inferred_type->unwrap_alias() == TypeDataAddress::create()) { + auto v_cond_eq = createV(v_cond_neq->loc, "==", tok_eq, v_cond_neq->get_lhs(), v_cond_neq->get_rhs()); + v_cond_eq->mutate()->assign_inferred_type(v_cond_neq->inferred_type); + v_cond_eq->mutate()->assign_rvalue_true(); + v = createV(v->loc, !v->is_ifnot, v_cond_eq, v->get_if_body(), v->get_else_body()); + } + } return v; } diff --git a/tolk/pipe-refine-lvalue-for-mutate.cpp b/tolk/pipe-refine-lvalue-for-mutate.cpp index 7692b0a2b2..3e02cf7738 100644 --- a/tolk/pipe-refine-lvalue-for-mutate.cpp +++ b/tolk/pipe-refine-lvalue-for-mutate.cpp @@ -65,7 +65,7 @@ class RefineLvalueForMutateArgumentsVisitor final : public ASTVisitorFunctionBod } int delta_self = v->get_self_obj() != nullptr; - tolk_assert(fun_ref->get_num_params() == delta_self + v->get_num_args()); + tolk_assert(fun_ref->get_num_params() >= delta_self + v->get_num_args()); if (delta_self && fun_ref->does_mutate_self()) { // for `b.storeInt()`, `b` should become lvalue, since `storeInt` is a method mutating self diff --git a/tolk/pipe-register-symbols.cpp b/tolk/pipe-register-symbols.cpp index bbb105e428..7eaa6ac91b 100644 --- a/tolk/pipe-register-symbols.cpp +++ b/tolk/pipe-register-symbols.cpp @@ -20,6 +20,7 @@ #include "ast.h" #include "compiler-state.h" #include "generics-helpers.h" +#include "pack-unpack-serializers.h" #include "td/utils/crypto.h" #include @@ -131,14 +132,31 @@ static StructPtr register_struct(V v, StructPtr base_str for (int i = 0; i < v_body->get_num_fields(); ++i) { auto v_field = v_body->get_field(i); std::string field_name = static_cast(v_field->get_identifier()->name); - AnyExprV default_value = v_field->has_default_value() ? v_field->get_default_value() : nullptr; for (StructFieldPtr prev : fields) { if (UNLIKELY(prev->name == field_name)) { v_field->error("redeclaration of field `" + field_name + "`"); } } - fields.emplace_back(new StructFieldData(static_cast(v_field->get_identifier()->name), v_field->loc, i, v_field->type_node, default_value)); + fields.emplace_back(new StructFieldData(field_name, v_field->loc, i, v_field->type_node, v_field->default_value)); + } + + PackOpcode opcode(0, 0); + if (v->has_opcode()) { + auto v_opcode = v->get_opcode()->as(); + if (v_opcode->intval < 0 || v_opcode->intval > (1ULL << 48)) { + v->error("opcode must not exceed 2^48"); + } + opcode.pack_prefix = v_opcode->intval->to_long(); + + std::string_view prefix_str = v_opcode->orig_str; + if (prefix_str.starts_with("0x")) { + opcode.prefix_len = static_cast(prefix_str.size() - 2) * 4; + } else if (prefix_str.starts_with("0b")) { + opcode.prefix_len = static_cast(prefix_str.size() - 2); + } else { + tolk_assert(false); + } } std::string name = std::move(override_name); @@ -146,7 +164,7 @@ static StructPtr register_struct(V v, StructPtr base_str name = v->get_identifier()->name; } const GenericsDeclaration* genericTs = nullptr; // at registering it's null; will be assigned after types resolving - StructData* s_sym = new StructData(std::move(name), v->loc, std::move(fields), genericTs, substitutedTs, v); + StructData* s_sym = new StructData(std::move(name), v->loc, std::move(fields), opcode, v->overflow1023_policy, genericTs, substitutedTs, v); s_sym->base_struct_ref = base_struct_ref; // for `Container`, here is `Container` G.symtable.add_struct(s_sym); @@ -157,7 +175,7 @@ static StructPtr register_struct(V v, StructPtr base_str static LocalVarData register_parameter(V v, int idx) { if (v->is_underscore()) { - return LocalVarData{"", v->loc, v->type_node, 0, idx}; + return LocalVarData{"", v->loc, v->type_node, v->default_value, 0, idx}; } int flags = 0; @@ -167,7 +185,7 @@ static LocalVarData register_parameter(V v, int idx) { if (!v->declared_as_mutate && idx == 0 && v->param_name == "self") { flags |= LocalVarData::flagImmutable; } - return LocalVarData(static_cast(v->param_name), v->loc, v->type_node, flags, idx); + return LocalVarData(static_cast(v->param_name), v->loc, v->type_node, v->default_value, flags, idx); } static FunctionPtr register_function(V v, FunctionPtr base_fun_ref = nullptr, std::string override_name = {}, const GenericsSubstitutions* substitutedTs = nullptr) { diff --git a/tolk/pipe-resolve-identifiers.cpp b/tolk/pipe-resolve-identifiers.cpp index 4452ea5738..2f6694469e 100644 --- a/tolk/pipe-resolve-identifiers.cpp +++ b/tolk/pipe-resolve-identifiers.cpp @@ -132,15 +132,15 @@ class AssignSymInsideFunctionVisitor final : public ASTVisitorFunctionBody { static NameAndScopeResolver current_scope; static FunctionPtr cur_f; - static LocalVarPtr create_local_var_sym(std::string_view name, SrcLocation loc, AnyTypeV declared_type_node, bool immutable) { - LocalVarData* v_sym = new LocalVarData(static_cast(name), loc, declared_type_node, immutable * LocalVarData::flagImmutable, -1); + static LocalVarPtr create_local_var_sym(std::string_view name, SrcLocation loc, AnyTypeV declared_type_node, bool immutable, bool lateinit) { + LocalVarData* v_sym = new LocalVarData(static_cast(name), loc, declared_type_node, nullptr, immutable * LocalVarData::flagImmutable + lateinit * LocalVarData::flagLateInit, -1); current_scope.add_local_var(v_sym); return v_sym; } static void process_catch_variable(AnyExprV catch_var) { if (auto v_ref = catch_var->try_as()) { - LocalVarPtr var_ref = create_local_var_sym(v_ref->get_name(), catch_var->loc, nullptr, true); + LocalVarPtr var_ref = create_local_var_sym(v_ref->get_name(), catch_var->loc, nullptr, true, false); v_ref->mutate()->assign_sym(var_ref); } } @@ -158,7 +158,7 @@ class AssignSymInsideFunctionVisitor final : public ASTVisitorFunctionBody { } v->mutate()->assign_var_ref(var_ref); } else { - LocalVarPtr var_ref = create_local_var_sym(v->get_name(), v->loc, v->type_node, v->is_immutable); + LocalVarPtr var_ref = create_local_var_sym(v->get_name(), v->loc, v->type_node, v->is_immutable, v->is_lateinit); v->mutate()->assign_var_ref(var_ref); } } @@ -274,7 +274,11 @@ class AssignSymInsideFunctionVisitor final : public ASTVisitorFunctionBody { auto v_block = v->get_body()->as(); current_scope.open_scope(v->loc); for (int i = 0; i < fun_ref->get_num_params(); ++i) { - current_scope.add_local_var(&fun_ref->parameters[i]); + LocalVarPtr param_ref = &fun_ref->parameters[i]; + current_scope.add_local_var(param_ref); + if (param_ref->has_default_value()) { + parent::visit(param_ref->default_value); + } } parent::visit(v_block); current_scope.close_scope(v_block->loc_end); diff --git a/tolk/pipe-resolve-types.cpp b/tolk/pipe-resolve-types.cpp index 915c01f72b..25d98bb46a 100644 --- a/tolk/pipe-resolve-types.cpp +++ b/tolk/pipe-resolve-types.cpp @@ -47,6 +47,9 @@ namespace tolk { * Example: `type A = Ok`, then `Ok` is not ready yet, it's left as TypeDataGenericTypeWithTs. */ +static std::unordered_map visited_structs; +static std::unordered_map visited_aliases; + GNU_ATTRIBUTE_NORETURN GNU_ATTRIBUTE_COLD static void fire_error_unknown_type_name(FunctionPtr cur_f, SrcLocation loc, std::string_view text) { if (text == "auto") { @@ -107,6 +110,7 @@ static TypePtr try_parse_predefined_type(std::string_view str) { break; case 7: if (str == "builder") return TypeDataBuilder::create(); + if (str == "address") return TypeDataAddress::create(); break; case 8: if (str == "varint16") return TypeDataIntN::create(false, true, 16); @@ -240,7 +244,7 @@ class TypeNodesVisitorResolver { if (alias_ref->is_generic_alias() && !allow_without_type_arguments) { fire_error_generic_type_used_without_T(cur_f, loc, alias_ref->as_human_readable()); } - if (!alias_ref->was_visited_by_resolver()) { + if (!visited_aliases.contains(alias_ref)) { visit_symbol(alias_ref); } return TypeDataAlias::create(alias_ref); @@ -249,7 +253,7 @@ class TypeNodesVisitorResolver { if (struct_ref->is_generic_struct() && !allow_without_type_arguments) { fire_error_generic_type_used_without_T(cur_f, loc, struct_ref->as_human_readable()); } - if (!struct_ref->was_visited_by_resolver()) { + if (!visited_structs.contains(struct_ref)) { visit_symbol(struct_ref); } return TypeDataStruct::create(struct_ref); @@ -347,7 +351,7 @@ class TypeNodesVisitorResolver { static void visit_symbol(AliasDefPtr alias_ref) { static std::vector called_stack; - // prevent recursion like `type A = B; type B = A` + // prevent recursion like `type A = B; type B = A` (we can't create TypeDataAlias without a resolved underlying type) bool contains = std::find(called_stack.begin(), called_stack.end(), alias_ref) != called_stack.end(); if (contains) { throw ParseError(alias_ref->loc, "type `" + alias_ref->name + "` circularly references itself"); @@ -362,35 +366,24 @@ class TypeNodesVisitorResolver { TypeNodesVisitorResolver visitor(nullptr, alias_ref->genericTs, alias_ref->substitutedTs, false); TypePtr underlying_type = visitor.finalize_type_node(alias_ref->underlying_type_node); alias_ref->mutate()->assign_resolved_type(underlying_type); - alias_ref->mutate()->assign_visited_by_resolver(); + visited_aliases.insert({alias_ref, 1}); called_stack.pop_back(); } static void visit_symbol(StructPtr struct_ref) { - static std::vector called_stack; - - // prevent recursion like `struct A { field: A }` - // currently, a struct is a tensor, and recursion always leads to infinite size (`A?` also, it's also on a stack) - // if there would be an annotation to store a struct in a tuple, then it has to be reconsidered - bool contains = std::find(called_stack.begin(), called_stack.end(), struct_ref) != called_stack.end(); - if (contains) { - throw ParseError(struct_ref->loc, "struct `" + struct_ref->name + "` size is infinity due to recursive fields"); - } + visited_structs.insert({struct_ref, 1}); if (auto v_genericsT_list = struct_ref->ast_root->as()->genericsT_list) { const GenericsDeclaration* genericTs = construct_genericTs(nullptr, v_genericsT_list); struct_ref->mutate()->assign_resolved_genericTs(genericTs); } - called_stack.push_back(struct_ref); TypeNodesVisitorResolver visitor(nullptr, struct_ref->genericTs, struct_ref->substitutedTs, false); for (int i = 0; i < struct_ref->get_num_fields(); ++i) { StructFieldPtr field_ref = struct_ref->get_field(i); TypePtr declared_type = visitor.finalize_type_node(field_ref->type_node); field_ref->mutate()->assign_resolved_type(declared_type); } - struct_ref->mutate()->assign_visited_by_resolver(); - called_stack.pop_back(); } static const GenericsDeclaration* construct_genericTs(TypePtr receiver_type, V v_list) { @@ -544,9 +537,12 @@ class ResolveTypesInsideFunctionVisitor final : public ASTVisitorFunctionBody { type_nodes_visitor = TypeNodesVisitorResolver(fun_ref, fun_ref->genericTs, fun_ref->substitutedTs, false); for (int i = 0; i < fun_ref->get_num_params(); ++i) { - const LocalVarData& param_var = fun_ref->parameters[i]; - TypePtr declared_type = finalize_type_node(param_var.type_node); - param_var.mutate()->assign_resolved_type(declared_type); + LocalVarPtr param_ref = &fun_ref->parameters[i]; + TypePtr declared_type = finalize_type_node(param_ref->type_node); + param_ref->mutate()->assign_resolved_type(declared_type); + if (param_ref->has_default_value()) { + parent::visit(param_ref->default_value); + } } if (fun_ref->return_type_node) { TypePtr declared_return_type = finalize_type_node(fun_ref->return_type_node); @@ -578,6 +574,46 @@ class ResolveTypesInsideFunctionVisitor final : public ASTVisitorFunctionBody { } }; +// prevent recursion like `struct A { field: A }`; +// currently, a struct is a tensor, and recursion always leads to infinite size (`A?` also, it's also on a stack); +// if there is an annotation to store a struct in a tuple, then it has to be reconsidered; +// it's crucial to detect it here; otherwise, get_width_on_stack() will silently face stack overflow +class InfiniteStructSizeDetector { + static TypePtr visit_type_deeply(TypePtr type) { + return type->replace_children_custom([](TypePtr child) { + if (const TypeDataStruct* child_struct = child->try_as()) { + check_struct_for_infinite_size(child_struct->struct_ref); + } + if (const TypeDataAlias* child_alias = child->try_as()) { + return visit_type_deeply(child_alias->underlying_type); + } + return child; + }); + }; + + static void check_struct_for_infinite_size(StructPtr struct_ref) { + static std::vector called_stack; + + bool contains = std::find(called_stack.begin(), called_stack.end(), struct_ref) != called_stack.end(); + if (contains) { + throw ParseError(struct_ref->loc, "struct `" + struct_ref->name + "` size is infinity due to recursive fields"); + } + + called_stack.push_back(struct_ref); + for (StructFieldPtr field_ref : struct_ref->fields) { + visit_type_deeply(field_ref->declared_type); + } + called_stack.pop_back(); + } + +public: + static void detect_and_fire_if_any_struct_is_infinite() { + for (auto [struct_ref, _] : visited_structs) { + check_struct_for_infinite_size(struct_ref); + } + } +}; + void pipeline_resolve_types_and_aliases() { ResolveTypesInsideFunctionVisitor visitor; @@ -599,12 +635,12 @@ void pipeline_resolve_types_and_aliases() { visitor.start_visiting_constant(v_const->const_ref); } else if (auto v_alias = v->try_as()) { - if (!v_alias->alias_ref->was_visited_by_resolver()) { + if (!visited_aliases.contains(v_alias->alias_ref)) { TypeNodesVisitorResolver::visit_symbol(v_alias->alias_ref); } } else if (auto v_struct = v->try_as()) { - if (!v_struct->struct_ref->was_visited_by_resolver()) { + if (!visited_structs.contains(v_struct->struct_ref)) { TypeNodesVisitorResolver::visit_symbol(v_struct->struct_ref); } visitor.start_visiting_struct_fields(v_struct->struct_ref); @@ -612,6 +648,10 @@ void pipeline_resolve_types_and_aliases() { } } + InfiniteStructSizeDetector::detect_and_fire_if_any_struct_is_infinite(); + visited_structs.clear(); + visited_aliases.clear(); + patch_builtins_after_stdlib_loaded(); } diff --git a/tolk/pipeline.h b/tolk/pipeline.h index a46e3e0375..4e5beca20e 100644 --- a/tolk/pipeline.h +++ b/tolk/pipeline.h @@ -41,6 +41,7 @@ void pipeline_check_inferred_types(); void pipeline_refine_lvalue_for_mutate_arguments(); void pipeline_check_rvalue_lvalue(); void pipeline_check_pure_impure_operations(); +void pipeline_check_serialized_fields(); void pipeline_constant_folding(); void pipeline_optimize_boolean_expressions(); void pipeline_convert_ast_to_legacy_Expr_Op(); diff --git a/tolk/symtable.cpp b/tolk/symtable.cpp index 1485eae75a..46d3949383 100644 --- a/tolk/symtable.cpp +++ b/tolk/symtable.cpp @@ -141,8 +141,8 @@ void LocalVarData::assign_inferred_type(TypePtr inferred_type) { this->declared_type = inferred_type; } -void AliasDefData::assign_visited_by_resolver() { - this->flags |= flagVisitedByResolver; +void LocalVarData::assign_default_value(AnyExprV default_value) { + this->default_value = default_value; } void AliasDefData::assign_resolved_genericTs(const GenericsDeclaration* genericTs) { @@ -163,10 +163,6 @@ void StructFieldData::assign_default_value(AnyExprV default_value) { this->default_value = default_value; } -void StructData::assign_visited_by_resolver() { - this->flags |= flagVisitedByResolver; -} - void StructData::assign_resolved_genericTs(const GenericsDeclaration* genericTs) { if (this->substitutedTs == nullptr) { this->genericTs = genericTs; @@ -182,6 +178,24 @@ StructFieldPtr StructData::find_field(std::string_view field_name) const { return nullptr; } +// formats opcode as "x{...}" or "b{...}" +std::string StructData::PackOpcode::format_as_slice() const { + const int base = prefix_len % 4 == 0 ? 16 : 2; + const int s_len = base == 16 ? prefix_len / 4 : prefix_len; + const char* digits = "0123456789abcdef"; + + std::string result(s_len + 3, '0'); + result[0] = base == 16 ? 'x' : 'b'; + result[1] = '{'; + result[s_len + 3 - 1] = '}'; + int64_t opcode = pack_prefix; + for (int i = s_len - 1; i >= 0 && opcode != 0; --i) { + result[2 + i] = digits[opcode % base]; + opcode /= base; + } + return result; +} + GNU_ATTRIBUTE_NORETURN GNU_ATTRIBUTE_COLD static void fire_error_redefinition_of_symbol(SrcLocation loc, const Symbol* previous) { SrcLocation prev_loc = previous->loc; @@ -234,6 +248,12 @@ void GlobalSymbolTable::add_struct(StructPtr s_sym) { } } +void GlobalSymbolTable::replace_function(FunctionPtr f_sym) { + auto key = key_hash(f_sym->name); + assert(entries.contains(key)); + entries[key] = f_sym; +} + const Symbol* lookup_global_symbol(std::string_view name) { return G.symtable.lookup(name); } @@ -242,4 +262,14 @@ FunctionPtr lookup_function(std::string_view name) { return G.symtable.lookup(name)->try_as(); } +std::vector lookup_methods_with_name(std::string_view name) { + std::vector result; + for (FunctionPtr method_ref : G.all_methods) { + if (method_ref->method_name == name) { + result.push_back(method_ref); + } + } + return result; +} + } // namespace tolk diff --git a/tolk/symtable.h b/tolk/symtable.h index 38f090e7b4..15a52d148b 100644 --- a/tolk/symtable.h +++ b/tolk/symtable.h @@ -49,24 +49,28 @@ struct LocalVarData final : Symbol { enum { flagMutateParameter = 1, // parameter was declared with `mutate` keyword flagImmutable = 2, // variable was declared via `val` (not `var`) + flagLateInit = 4, // variable was declared via `lateinit` (not assigned at declaration) }; AnyTypeV type_node; // either at declaration `var x:int`, or if omitted, from assigned value `var x=2` TypePtr declared_type = nullptr; // = resolved type_node + AnyExprV default_value = nullptr; // for function parameters, if it has a default value int flags; int param_idx; // 0...N for function parameters, -1 for local vars std::vector ir_idx; - LocalVarData(std::string name, SrcLocation loc, AnyTypeV type_node, int flags, int param_idx) + LocalVarData(std::string name, SrcLocation loc, AnyTypeV type_node, AnyExprV default_value, int flags, int param_idx) : Symbol(std::move(name), loc) , type_node(type_node) + , default_value(default_value) , flags(flags) , param_idx(param_idx) { } - LocalVarData(std::string name, SrcLocation loc, TypePtr declared_type, int flags, int param_idx) + LocalVarData(std::string name, SrcLocation loc, TypePtr declared_type, AnyExprV default_value, int flags, int param_idx) : Symbol(std::move(name), loc) , type_node(nullptr) // for built-in functions (their parameters) , declared_type(declared_type) + , default_value(default_value) , flags(flags) , param_idx(param_idx) { } @@ -74,12 +78,15 @@ struct LocalVarData final : Symbol { bool is_parameter() const { return param_idx >= 0; } bool is_immutable() const { return flags & flagImmutable; } + bool is_lateinit() const { return flags & flagLateInit; } bool is_mutate_parameter() const { return flags & flagMutateParameter; } + bool has_default_value() const { return default_value != nullptr; } LocalVarData* mutate() const { return const_cast(this); } void assign_ir_idx(std::vector&& ir_idx); void assign_resolved_type(TypePtr declared_type); void assign_inferred_type(TypePtr inferred_type); + void assign_default_value(AnyExprV default_value); }; struct FunctionBodyCode; @@ -109,7 +116,9 @@ struct FunctionData final : Symbol { flagAcceptsSelf = 512, // is a member function (has `self` first parameter) flagReturnsSelf = 1024, // return type is `self` (returns the mutated 1st argument), calls can be chainable flagReallyUsed = 2048, // calculated via dfs from used functions; declared but unused functions are not codegenerated - flagCompileTimeOnly = 4096, // calculated only at compile-time for constant arguments: `ton("0.05")`, `stringCrc32`, and others + flagCompileTimeVal = 4096, // calculated only at compile-time for constant arguments: `ton("0.05")`, `stringCrc32`, and others + flagCompileTimeGen = 8192, // at compile-time it's handled specially, not as a regular function: `T.toCell`, etc. + flagAllowAnyWidthT = 16384, // for built-in generic functions that is not restricted to be 1-slot type }; int tvm_method_id = EMPTY_TVM_METHOD_ID; @@ -194,7 +203,9 @@ struct FunctionData final : Symbol { bool does_return_self() const { return flags & flagReturnsSelf; } bool does_mutate_self() const { return (flags & flagAcceptsSelf) && parameters[0].is_mutate_parameter(); } bool is_really_used() const { return flags & flagReallyUsed; } - bool is_compile_time_only() const { return flags & flagCompileTimeOnly; } + bool is_compile_time_const_val() const { return flags & flagCompileTimeVal; } + bool is_compile_time_special_gen() const { return flags & flagCompileTimeGen; } + bool is_variadic_width_T_allowed() const { return flags & flagAllowAnyWidthT; } bool does_need_codegen() const; @@ -250,13 +261,8 @@ struct GlobalConstData final : Symbol { }; struct AliasDefData final : Symbol { - enum { - flagVisitedByResolver = 1, - }; - AnyTypeV underlying_type_node; TypePtr underlying_type = nullptr; // = resolved underlying_type_node - int flags = 0; const GenericsDeclaration* genericTs; const GenericsSubstitutions* substitutedTs; @@ -276,10 +282,7 @@ struct AliasDefData final : Symbol { bool is_generic_alias() const { return genericTs != nullptr; } bool is_instantiation_of_generic_alias() const { return substitutedTs != nullptr; } - bool was_visited_by_resolver() const { return flags & flagVisitedByResolver; } - AliasDefData* mutate() const { return const_cast(this); } - void assign_visited_by_resolver(); void assign_resolved_genericTs(const GenericsDeclaration* genericTs); void assign_resolved_type(TypePtr underlying_type); }; @@ -305,12 +308,26 @@ struct StructFieldData final : Symbol { }; struct StructData final : Symbol { - enum { - flagVisitedByResolver = 1, + enum class Overflow1023Policy { // annotation @overflow1023_policy above a struct + not_specified, + suppress, + }; + + struct PackOpcode { + int64_t pack_prefix; + int prefix_len; + + PackOpcode(int64_t pack_prefix, int prefix_len) + : pack_prefix(pack_prefix), prefix_len(prefix_len) {} + + bool exists() const { return prefix_len != 0; } + + std::string format_as_slice() const; // "x{...}" (or "b{...}") }; std::vector fields; - int flags = 0; + PackOpcode opcode; + Overflow1023Policy overflow1023_policy; const GenericsDeclaration* genericTs; const GenericsSubstitutions* substitutedTs; @@ -324,15 +341,14 @@ struct StructData final : Symbol { bool is_generic_struct() const { return genericTs != nullptr; } bool is_instantiation_of_generic_struct() const { return substitutedTs != nullptr; } - bool was_visited_by_resolver() const { return flags & flagVisitedByResolver; } - StructData* mutate() const { return const_cast(this); } - void assign_visited_by_resolver(); void assign_resolved_genericTs(const GenericsDeclaration* genericTs); - StructData(std::string name, SrcLocation loc, std::vector&& fields, const GenericsDeclaration* genericTs, const GenericsSubstitutions* substitutedTs, AnyV ast_root) + StructData(std::string name, SrcLocation loc, std::vector&& fields, PackOpcode opcode, Overflow1023Policy overflow1023_policy, const GenericsDeclaration* genericTs, const GenericsSubstitutions* substitutedTs, AnyV ast_root) : Symbol(std::move(name), loc) , fields(std::move(fields)) + , opcode(opcode) + , overflow1023_policy(overflow1023_policy) , genericTs(genericTs) , substitutedTs(substitutedTs) , ast_root(ast_root) { @@ -364,6 +380,8 @@ class GlobalSymbolTable { void add_type_alias(AliasDefPtr a_sym); void add_struct(StructPtr s_sym); + void replace_function(FunctionPtr f_sym); + const Symbol* lookup(std::string_view name) const { const auto it = entries.find(key_hash(name)); return it == entries.end() ? nullptr : it->second; @@ -372,5 +390,6 @@ class GlobalSymbolTable { const Symbol* lookup_global_symbol(std::string_view name); FunctionPtr lookup_function(std::string_view name); +std::vector lookup_methods_with_name(std::string_view name); } // namespace tolk diff --git a/tolk/tolk-version.h b/tolk/tolk-version.h index dd9134f453..5da1582d25 100644 --- a/tolk/tolk-version.h +++ b/tolk/tolk-version.h @@ -18,6 +18,6 @@ namespace tolk { -constexpr const char* TOLK_VERSION = "0.12.0"; +constexpr const char* TOLK_VERSION = "0.13.0"; } // namespace tolk diff --git a/tolk/tolk.cpp b/tolk/tolk.cpp index fc38571bc4..6ead2b149e 100644 --- a/tolk/tolk.cpp +++ b/tolk/tolk.cpp @@ -64,6 +64,7 @@ int tolk_proceed(const std::string &entrypoint_filename) { pipeline_refine_lvalue_for_mutate_arguments(); pipeline_check_rvalue_lvalue(); pipeline_check_pure_impure_operations(); + pipeline_check_serialized_fields(); pipeline_constant_folding(); pipeline_optimize_boolean_expressions(); pipeline_convert_ast_to_legacy_Expr_Op(); diff --git a/tolk/tolk.h b/tolk/tolk.h index ad2a5898d3..6e5af87ca7 100644 --- a/tolk/tolk.h +++ b/tolk/tolk.h @@ -924,6 +924,9 @@ struct Optimizer { bool is_nip_seq(int* i, int* j); bool is_pop_blkdrop(int* i, int* k); bool is_2pop_blkdrop(int* i, int* j, int* k); + + bool detect_rewrite_big_THROW(); + AsmOpConsList extract_code(); }; @@ -1100,6 +1103,7 @@ struct CodeBlob { #endif return ir_idx; } + var_idx_t create_int(SrcLocation loc, int64_t value, const char* desc); bool compute_used_code_vars(); bool compute_used_code_vars(std::unique_ptr& ops, const VarDescrList& var_info, bool edit) const; void print(std::ostream& os, int flags = 0) const; diff --git a/tolk/type-system.cpp b/tolk/type-system.cpp index b419a2e3f5..d37a657784 100644 --- a/tolk/type-system.cpp +++ b/tolk/type-system.cpp @@ -135,6 +135,7 @@ TypePtr TypeDataSlice::singleton; TypePtr TypeDataBuilder::singleton; TypePtr TypeDataTuple::singleton; TypePtr TypeDataContinuation::singleton; +TypePtr TypeDataAddress::singleton; TypePtr TypeDataNullLiteral::singleton; TypePtr TypeDataCoins::singleton; TypePtr TypeDataUnknown::singleton; @@ -149,6 +150,7 @@ void type_system_init() { TypeDataBuilder::singleton = new TypeDataBuilder; TypeDataTuple::singleton = new TypeDataTuple; TypeDataContinuation::singleton = new TypeDataContinuation; + TypeDataAddress::singleton = new TypeDataAddress; TypeDataNullLiteral::singleton = new TypeDataNullLiteral; TypeDataCoins::singleton = new TypeDataCoins; TypeDataUnknown::singleton = new TypeDataUnknown; @@ -264,11 +266,7 @@ TypePtr TypeDataStruct::create(StructPtr struct_ref) { if (TypePtr existing = hash.get_existing()) { return existing; } - int width_on_stack = 0; // if a struct is generic, it will be incorrect, it's okay - for (StructFieldPtr field_ref : struct_ref->fields) { - width_on_stack += field_ref->declared_type->get_width_on_stack(); - } - return hash.register_unique(new TypeDataStruct(width_on_stack, struct_ref)); + return hash.register_unique(new TypeDataStruct(struct_ref)); } TypePtr TypeDataTensor::create(std::vector&& items) { @@ -281,11 +279,7 @@ TypePtr TypeDataTensor::create(std::vector&& items) { if (TypePtr existing = hash.get_existing()) { return existing; } - int width_on_stack = 0; - for (TypePtr item : items) { - width_on_stack += item->get_width_on_stack(); - } - return hash.register_unique(new TypeDataTensor(hash.children_flags(), width_on_stack, std::move(items))); + return hash.register_unique(new TypeDataTensor(hash.children_flags(), std::move(items))); } TypePtr TypeDataBrackets::create(std::vector&& items) { @@ -349,7 +343,7 @@ TypePtr TypeDataUnion::create(std::vector&& variants) { or_null = variants[variants[0] == TypeDataNullLiteral::create()]; } } - return hash.register_unique(new TypeDataUnion(hash.children_flags(), -999999, or_null, std::move(variants))); + return hash.register_unique(new TypeDataUnion(hash.children_flags(), or_null, std::move(variants))); } // flatten variants and remove duplicates @@ -373,24 +367,10 @@ TypePtr TypeDataUnion::create(std::vector&& variants) { } } - int width_on_stack; - if (or_null && or_null->can_hold_tvm_null_instead()) { - width_on_stack = 1; - } else { - // `T1 | T2 | ...` occupy max(W[i]) + 1 slot for UTag (stores type_id or 0 for null) - int max_child_width = 0; - for (TypePtr i : flat_variants) { - if (i != TypeDataNullLiteral::create()) { // `Empty | () | null` totally should be 1 (0 + 1 for UTag) - max_child_width = std::max(max_child_width, i->get_width_on_stack()); - } - } - width_on_stack = max_child_width + 1; - } - if (flat_variants.size() == 1) { // `int | int` return flat_variants[0]; } - return hash.register_unique(new TypeDataUnion(hash.children_flags(), width_on_stack, or_null, std::move(flat_variants))); + return hash.register_unique(new TypeDataUnion(hash.children_flags(), or_null, std::move(flat_variants))); } TypePtr TypeDataUnion::create_nullable(TypePtr nullable) { @@ -408,6 +388,69 @@ TypePtr TypeDataUnion::create_nullable(TypePtr nullable) { } +// -------------------------------------------- +// get_width_on_stack() +// +// calculate, how many stack slots the type occupies, e.g. `int`=1, `(int,int)`=2, `(int,int)?`=3 +// it's calculated dynamically (not saved at TypeData*::create) to overcome problems with +// - recursive struct mentions (to create TypeDataStruct without knowing width of children) +// - uninitialized generics (that don't make any sense upon being instantiated) +// + +int TypeDataAlias::get_width_on_stack() const { + return underlying_type->get_width_on_stack(); +} + +int TypeDataGenericT::get_width_on_stack() const { + assert(false); + return -99999; +} + +int TypeDataGenericTypeWithTs::get_width_on_stack() const { + assert(false); + return -99999; +} + +int TypeDataStruct::get_width_on_stack() const { + int width_on_stack = 0; + for (StructFieldPtr field_ref : struct_ref->fields) { + width_on_stack += field_ref->declared_type->get_width_on_stack(); + } + return width_on_stack; +} + +int TypeDataTensor::get_width_on_stack() const { + int width_on_stack = 0; + for (TypePtr item : items) { + width_on_stack += item->get_width_on_stack(); + } + return width_on_stack; +} + +int TypeDataUnion::get_width_on_stack() const { + if (or_null && or_null->can_hold_tvm_null_instead()) { + return 1; + } + + // `T1 | T2 | ...` occupy max(W[i]) + 1 slot for UTag (stores type_id or 0 for null) + int max_child_width = 0; + for (TypePtr i : variants) { + if (i != TypeDataNullLiteral::create()) { // `Empty | () | null` totally should be 1 (0 + 1 for UTag) + max_child_width = std::max(max_child_width, i->get_width_on_stack()); + } + } + return max_child_width + 1; +} + +int TypeDataNever::get_width_on_stack() const { + return 0; +} + +int TypeDataVoid::get_width_on_stack() const { + return 0; +} + + // -------------------------------------------- // get_type_id() // @@ -678,6 +721,11 @@ bool TypeDataCell::can_rhs_be_assigned(TypePtr rhs) const { if (rhs == this) { return true; } + if (const TypeDataStruct* rhs_struct = rhs->try_as()) { + if (rhs_struct->struct_ref->is_instantiation_of_generic_struct() && rhs_struct->struct_ref->base_struct_ref->name == "Cell") { + return true; // Cell to cell, e.g. `contract.setData(obj.toCell())` + } + } if (const TypeDataAlias* rhs_alias = rhs->try_as()) { return can_rhs_be_assigned(rhs_alias->underlying_type); } @@ -691,7 +739,7 @@ bool TypeDataSlice::can_rhs_be_assigned(TypePtr rhs) const { if (const TypeDataAlias* rhs_alias = rhs->try_as()) { return can_rhs_be_assigned(rhs_alias->underlying_type); } - return rhs == TypeDataNever::create(); // note, that bytesN is NOT automatically cast to slice without `as` operator + return rhs == TypeDataNever::create(); // note, that bytesN/address is NOT automatically cast to slice without `as` operator } bool TypeDataBuilder::can_rhs_be_assigned(TypePtr rhs) const { @@ -724,6 +772,16 @@ bool TypeDataContinuation::can_rhs_be_assigned(TypePtr rhs) const { return rhs == TypeDataNever::create(); } +bool TypeDataAddress::can_rhs_be_assigned(TypePtr rhs) const { + if (rhs == this) { + return true; + } + if (const TypeDataAlias* rhs_alias = rhs->try_as()) { + return can_rhs_be_assigned(rhs_alias->underlying_type); + } + return rhs == TypeDataNever::create(); // note, that slice is NOT automatically cast to address without `as` operator +} + bool TypeDataNullLiteral::can_rhs_be_assigned(TypePtr rhs) const { if (rhs == this) { return true; @@ -942,6 +1000,9 @@ bool TypeDataCell::can_be_casted_with_as_operator(TypePtr cast_to) const { if (const TypeDataUnion* to_union = cast_to->try_as()) { return can_be_casted_to_union(this, to_union); } + if (const TypeDataStruct* to_struct = cast_to->try_as()) { // cell as Cell + return to_struct->struct_ref->is_instantiation_of_generic_struct() && to_struct->struct_ref->base_struct_ref->name == "Cell"; + } if (const TypeDataAlias* to_alias = cast_to->try_as()) { return can_be_casted_with_as_operator(to_alias->underlying_type); } @@ -952,6 +1013,9 @@ bool TypeDataSlice::can_be_casted_with_as_operator(TypePtr cast_to) const { if (cast_to->try_as()) { // `slice` to `bytes32` / `slice` to `bits8` return true; } + if (cast_to == TypeDataAddress::create()) { + return true; + } if (const TypeDataUnion* to_union = cast_to->try_as()) { return can_be_casted_to_union(this, to_union); } @@ -991,6 +1055,19 @@ bool TypeDataContinuation::can_be_casted_with_as_operator(TypePtr cast_to) const return cast_to == this; } +bool TypeDataAddress::can_be_casted_with_as_operator(TypePtr cast_to) const { + if (cast_to == TypeDataSlice::create()) { + return true; + } + if (const TypeDataUnion* to_union = cast_to->try_as()) { + return can_be_casted_to_union(this, to_union); + } + if (const TypeDataAlias* to_alias = cast_to->try_as()) { + return can_be_casted_with_as_operator(to_alias->underlying_type); + } + return cast_to == this; +} + bool TypeDataNullLiteral::can_be_casted_with_as_operator(TypePtr cast_to) const { if (const TypeDataUnion* to_union = cast_to->try_as()) { // `null` to `T?` / `null` to `... | null` return can_be_casted_to_union(this, to_union); @@ -1038,6 +1115,9 @@ bool TypeDataStruct::can_be_casted_with_as_operator(TypePtr cast_to) const { if (const TypeDataUnion* to_union = cast_to->try_as()) { return can_be_casted_to_union(this, to_union); } + if (cast_to == TypeDataCell::create()) { // Cell as cell + return struct_ref->is_instantiation_of_generic_struct() && struct_ref->base_struct_ref->name == "Cell"; + } if (const TypeDataAlias* to_alias = cast_to->try_as()) { return can_be_casted_with_as_operator(to_alias->underlying_type); } @@ -1106,7 +1186,7 @@ bool TypeDataBytesN::can_be_casted_with_as_operator(TypePtr cast_to) const { if (const TypeDataAlias* to_alias = cast_to->try_as()) { return can_be_casted_with_as_operator(to_alias->underlying_type); } - return cast_to == TypeDataSlice::create(); + return cast_to == TypeDataSlice::create() || cast_to == TypeDataAddress::create(); } bool TypeDataCoins::can_be_casted_with_as_operator(TypePtr cast_to) const { diff --git a/tolk/type-system.h b/tolk/type-system.h index 40b2910154..c1f9cbb6db 100644 --- a/tolk/type-system.h +++ b/tolk/type-system.h @@ -41,8 +41,6 @@ namespace tolk { class TypeData { // bits of flag_mask, to store often-used properties and return them without tree traversing const int flags; - // how many slots on a stack this type occupies (calculated on creation), e.g. `int`=1, `(int,int)`=2, `(int,int)?`=3 - const int width_on_stack; friend class TypeDataHasherForUnique; @@ -53,9 +51,8 @@ class TypeData { flag_contains_type_alias_inside = 1 << 3, }; - explicit TypeData(int flags_with_children, int width_on_stack) - : flags(flags_with_children) - , width_on_stack(width_on_stack) { + explicit TypeData(int flags_with_children) + : flags(flags_with_children) { } static bool equal_to_slow_path(TypePtr lhs, TypePtr rhs); @@ -69,7 +66,10 @@ class TypeData { return dynamic_cast(this); } - int get_width_on_stack() const { return width_on_stack; } + // how many slots on a stack this type occupies, e.g. `int`=1, `(int,int)`=2, `(int,int)?`=3 + virtual int get_width_on_stack() const { + return 1; // most types occupy 1 stack slot (int, cell, slice, etc.) + } bool equal_to(TypePtr rhs) const { return this == rhs || equal_to_slow_path(this, rhs); @@ -108,7 +108,7 @@ class TypeData { */ class TypeDataAlias final : public TypeData { explicit TypeDataAlias(int children_flags, AliasDefPtr alias_ref, TypePtr underlying_type) - : TypeData(children_flags | flag_contains_type_alias_inside, underlying_type->get_width_on_stack()) + : TypeData(children_flags | flag_contains_type_alias_inside) , alias_ref(alias_ref) , underlying_type(underlying_type) {} @@ -118,6 +118,7 @@ class TypeDataAlias final : public TypeData { static TypePtr create(AliasDefPtr alias_ref); + int get_width_on_stack() const override; int get_type_id() const override; std::string as_human_readable() const override; bool can_rhs_be_assigned(TypePtr rhs) const override; @@ -129,7 +130,7 @@ class TypeDataAlias final : public TypeData { * `int` is TypeDataInt, representation of TVM int. */ class TypeDataInt final : public TypeData { - TypeDataInt() : TypeData(0, 1) {} + TypeDataInt() : TypeData(0) {} static TypePtr singleton; friend void type_system_init(); @@ -148,7 +149,7 @@ class TypeDataInt final : public TypeData { * From the type system point of view, int and bool are different, not-autocastable types. */ class TypeDataBool final : public TypeData { - TypeDataBool() : TypeData(0, 1) {} + TypeDataBool() : TypeData(0) {} static TypePtr singleton; friend void type_system_init(); @@ -166,7 +167,7 @@ class TypeDataBool final : public TypeData { * `cell` is TypeDataCell, representation of TVM cell. */ class TypeDataCell final : public TypeData { - TypeDataCell() : TypeData(0, 1) {} + TypeDataCell() : TypeData(0) {} static TypePtr singleton; friend void type_system_init(); @@ -184,7 +185,7 @@ class TypeDataCell final : public TypeData { * `slice` is TypeDataSlice, representation of TVM slice. */ class TypeDataSlice final : public TypeData { - TypeDataSlice() : TypeData(0, 1) {} + TypeDataSlice() : TypeData(0) {} static TypePtr singleton; friend void type_system_init(); @@ -202,7 +203,7 @@ class TypeDataSlice final : public TypeData { * `builder` is TypeDataBuilder, representation of TVM builder. */ class TypeDataBuilder final : public TypeData { - TypeDataBuilder() : TypeData(0, 1) {} + TypeDataBuilder() : TypeData(0) {} static TypePtr singleton; friend void type_system_init(); @@ -222,7 +223,7 @@ class TypeDataBuilder final : public TypeData { * so getting its element results in TypeDataUnknown (which must be assigned/cast explicitly). */ class TypeDataTuple final : public TypeData { - TypeDataTuple() : TypeData(0, 1) {} + TypeDataTuple() : TypeData(0) {} static TypePtr singleton; friend void type_system_init(); @@ -241,7 +242,7 @@ class TypeDataTuple final : public TypeData { * It's like "untyped callable", not compatible with other types. */ class TypeDataContinuation final : public TypeData { - TypeDataContinuation() : TypeData(0, 1) {} + TypeDataContinuation() : TypeData(0) {} static TypePtr singleton; friend void type_system_init(); @@ -255,6 +256,25 @@ class TypeDataContinuation final : public TypeData { bool can_be_casted_with_as_operator(TypePtr cast_to) const override; }; +/* + * `address` is TypeDataAddress — TVM slice under the hood, but since it's a very common use case, + * it's extracted as a separate type (not as a struct with slice field, but just a dedicated type). + */ +class TypeDataAddress final : public TypeData { + TypeDataAddress() : TypeData(0) {} + + static TypePtr singleton; + friend void type_system_init(); + +public: + static TypePtr create() { return singleton; } + + int get_type_id() const override { return 8; } + std::string as_human_readable() const override { return "address"; } + bool can_rhs_be_assigned(TypePtr rhs) const override; + bool can_be_casted_with_as_operator(TypePtr cast_to) const override; +}; + /* * `null` has TypeDataNullLiteral type. * It can be assigned only to nullable types (`int?`, etc.), to ensure null safety. @@ -262,7 +282,7 @@ class TypeDataContinuation final : public TypeData { * (it's much better for user to see an error here than when he passes this variable somewhere). */ class TypeDataNullLiteral final : public TypeData { - TypeDataNullLiteral() : TypeData(0, 1) {} + TypeDataNullLiteral() : TypeData(0) {} static TypePtr singleton; friend void type_system_init(); @@ -283,7 +303,7 @@ class TypeDataNullLiteral final : public TypeData { */ class TypeDataFunCallable final : public TypeData { TypeDataFunCallable(int children_flags, std::vector&& params_types, TypePtr return_type) - : TypeData(children_flags, 1) + : TypeData(children_flags) , params_types(std::move(params_types)) , return_type(return_type) {} @@ -310,7 +330,7 @@ class TypeDataFunCallable final : public TypeData { */ class TypeDataGenericT final : public TypeData { explicit TypeDataGenericT(std::string&& nameT) - : TypeData(flag_contains_genericT_inside, -999999) // width undefined until instantiated + : TypeData(flag_contains_genericT_inside) , nameT(std::move(nameT)) {} public: @@ -318,6 +338,7 @@ class TypeDataGenericT final : public TypeData { static TypePtr create(std::string&& nameT); + int get_width_on_stack() const override; int get_type_id() const override; std::string as_human_readable() const override { return nameT; } bool can_rhs_be_assigned(TypePtr rhs) const override; @@ -332,7 +353,7 @@ class TypeDataGenericT final : public TypeData { */ class TypeDataGenericTypeWithTs final : public TypeData { TypeDataGenericTypeWithTs(int children_flags, StructPtr struct_ref, AliasDefPtr alias_ref, std::vector&& type_arguments) - : TypeData(children_flags, -999999) + : TypeData(children_flags) , struct_ref(struct_ref) , alias_ref(alias_ref) , type_arguments(std::move(type_arguments)) {} @@ -346,6 +367,7 @@ class TypeDataGenericTypeWithTs final : public TypeData { int size() const { return static_cast(type_arguments.size()); } + int get_width_on_stack() const override; int get_type_id() const override; std::string as_human_readable() const override; bool can_rhs_be_assigned(TypePtr rhs) const override; @@ -361,8 +383,8 @@ class TypeDataGenericTypeWithTs final : public TypeData { * so for `Wrapper` struct_ref points to a generic struct, and for `Wrapper` to an instantiated one. */ class TypeDataStruct final : public TypeData { - TypeDataStruct(int width_on_stack, StructPtr struct_ref) - : TypeData(0, width_on_stack) + explicit TypeDataStruct(StructPtr struct_ref) + : TypeData(0) , struct_ref(struct_ref) {} public: @@ -370,6 +392,7 @@ class TypeDataStruct final : public TypeData { static TypePtr create(StructPtr struct_ref); + int get_width_on_stack() const override; int get_type_id() const override; std::string as_human_readable() const override; bool can_rhs_be_assigned(TypePtr rhs) const override; @@ -384,8 +407,8 @@ class TypeDataStruct final : public TypeData { * A tensor can be empty. */ class TypeDataTensor final : public TypeData { - TypeDataTensor(int children_flags, int width_on_stack, std::vector&& items) - : TypeData(children_flags, width_on_stack) + TypeDataTensor(int children_flags, std::vector&& items) + : TypeData(children_flags) , items(std::move(items)) {} public: @@ -395,6 +418,7 @@ class TypeDataTensor final : public TypeData { int size() const { return static_cast(items.size()); } + int get_width_on_stack() const override; int get_type_id() const override; std::string as_human_readable() const override; bool can_rhs_be_assigned(TypePtr rhs) const override; @@ -410,7 +434,7 @@ class TypeDataTensor final : public TypeData { */ class TypeDataBrackets final : public TypeData { TypeDataBrackets(int children_flags, std::vector&& items) - : TypeData(children_flags, 1) + : TypeData(children_flags) , items(std::move(items)) {} public: @@ -435,7 +459,7 @@ class TypeDataBrackets final : public TypeData { */ class TypeDataIntN final : public TypeData { TypeDataIntN(bool is_unsigned, bool is_variadic, int n_bits) - : TypeData(0, 1) + : TypeData(0) , is_unsigned(is_unsigned) , is_variadic(is_variadic) , n_bits(n_bits) {} @@ -458,7 +482,7 @@ class TypeDataIntN final : public TypeData { * Example: `var cost = ton("0.05")` has type `coins`. */ class TypeDataCoins final : public TypeData { - TypeDataCoins() : TypeData(0, 1) {} + TypeDataCoins() : TypeData(0) {} static TypePtr singleton; friend void type_system_init(); @@ -480,7 +504,7 @@ class TypeDataCoins final : public TypeData { */ class TypeDataBytesN final : public TypeData { TypeDataBytesN(bool is_bits, int n_width) - : TypeData(0, 1) + : TypeData(0) , is_bits(is_bits) , n_width(n_width) {} @@ -506,8 +530,8 @@ class TypeDataBytesN final : public TypeData { * - `T1 | T2 | ...` is a tagged union: occupy max(T_i)+1 slots (1 for type_id) */ class TypeDataUnion final : public TypeData { - TypeDataUnion(int children_flags, int width_on_stack, TypePtr or_null, std::vector&& variants) - : TypeData(children_flags, width_on_stack) + TypeDataUnion(int children_flags, TypePtr or_null, std::vector&& variants) + : TypeData(children_flags) , or_null(or_null) , variants(std::move(variants)) {} @@ -546,6 +570,7 @@ class TypeDataUnion final : public TypeData { TypePtr calculate_exact_variant_to_fit_rhs(TypePtr rhs_type) const; bool has_all_variants_of(const TypeDataUnion* rhs_type) const; + int get_width_on_stack() const override; int get_type_id() const override; std::string as_human_readable() const override; bool can_rhs_be_assigned(TypePtr rhs) const override; @@ -561,7 +586,7 @@ class TypeDataUnion final : public TypeData { * The only thing available to do with unknown is to cast it: `catch (excNo, arg) { var i = arg as int; }` */ class TypeDataUnknown final : public TypeData { - TypeDataUnknown() : TypeData(flag_contains_unknown_inside, 1) {} + TypeDataUnknown() : TypeData(flag_contains_unknown_inside) {} static TypePtr singleton; friend void type_system_init(); @@ -582,7 +607,7 @@ class TypeDataUnknown final : public TypeData { * Such variables can not be cast to any other types, all their usage will trigger type mismatch errors. */ class TypeDataNever final : public TypeData { - TypeDataNever() : TypeData(0, 0) {} + TypeDataNever() : TypeData(0) {} static TypePtr singleton; friend void type_system_init(); @@ -590,6 +615,7 @@ class TypeDataNever final : public TypeData { public: static TypePtr create() { return singleton; } + int get_width_on_stack() const override; int get_type_id() const override { return 19; } std::string as_human_readable() const override { return "never"; } bool can_rhs_be_assigned(TypePtr rhs) const override; @@ -603,7 +629,7 @@ class TypeDataNever final : public TypeData { * Empty tensor is not compatible with void, although at IR level they are similar, 0 stack slots. */ class TypeDataVoid final : public TypeData { - TypeDataVoid() : TypeData(0, 0) {} + TypeDataVoid() : TypeData(0) {} static TypePtr singleton; friend void type_system_init(); @@ -611,6 +637,7 @@ class TypeDataVoid final : public TypeData { public: static TypePtr create() { return singleton; } + int get_width_on_stack() const override; int get_type_id() const override { return 10; } std::string as_human_readable() const override { return "void"; } bool can_rhs_be_assigned(TypePtr rhs) const override;