Skip to content

Commit 49cc6d0

Browse files
committed
[Tolk] Types int32, uint64, and fixed ints in general
Introduces fixed-size integer types: * intN for signed integers (0 < N ≤ 256) * uintN for unsigned integers (0 < N ≤ 257) The generic `int` type remains unchanged and can be implicitly cast to and from any `intN`. * arithmetic operations on `intN` degrade to `int` * numeric literals (like 0, 100) are just `int` * direct assignment between `intN` and `intM` is disallowed (as a probable error) There is no runtime overflow. A variable is declared as `int8` can hold any 257-bit value during execution. Overflow will occur when packing to a cell (in the future).
1 parent d54e708 commit 49cc6d0

12 files changed

+370
-10
lines changed

tolk-tester/tests/intN-tests.tolk

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
fun takeAnyInt(a: int) { }
2+
fun getAnyInt(): int { return 0; }
3+
fun getNullableInt32(): int32? { return 32; }
4+
fun getNullableVarInt32(): varint32? { return 32; }
5+
6+
7+
fun autoInferInt8(x: int8) {
8+
if (random()) { return x; }
9+
else if (random()) { return 0 as int8; }
10+
else if (random()) { return x!; }
11+
else { return x += x; }
12+
}
13+
14+
fun autoInferUInt16Opt(x: uint16) {
15+
if (random()) { return autoInferInt8(x as int8) as uint16; }
16+
return null;
17+
}
18+
19+
fun autoInferInt_v1() {
20+
if (random()) { return 0; }
21+
else if (random()) { return 0 as uint16; }
22+
else { return 0 as int8; }
23+
}
24+
25+
fun autoInferInt_v2() {
26+
if (random()) { return 0 as uint1; }
27+
else if (random()) { return 0 as int123; }
28+
else { return 0 as int8; }
29+
}
30+
31+
@method_id(101)
32+
fun test1(x: int8) {
33+
var y: int8 = x;
34+
y += x;
35+
x *= y;
36+
x = 1000000;
37+
y = x;
38+
__expect_type(x / y, "int");
39+
__expect_type(x & y, "int");
40+
__expect_type(x != y, "bool");
41+
if (x != y) { return -1; }
42+
if (!(y == x)) { return -1; }
43+
44+
var a1: uint8 = 0;
45+
var a2: uint8? = 0;
46+
var a3: uint8? = 0 as int?;
47+
__expect_type(a1, "uint8");
48+
__expect_type(a2, "uint8");
49+
__expect_type(a3, "uint8?");
50+
51+
var z = x + y;
52+
__expect_type(z, "int");
53+
return x / y;
54+
}
55+
56+
fun test2(): (uint8, uint8, uint8) {
57+
var x: uint1 = 1;
58+
return (1, x as uint8, x as int);
59+
}
60+
61+
fun test3(op: int32, qid: uint64) {
62+
op = qid as int32;
63+
64+
op + qid;
65+
op = op + qid;
66+
op = op & qid;
67+
op += qid;
68+
op &= qid;
69+
if (op == qid) {}
70+
if ((op as int32?)! == qid) {}
71+
if (op == (qid as uint64)) {}
72+
if ((op as uint257?)! != (qid as int256?)!) {}
73+
__expect_type(op << qid, "int");
74+
75+
takeAnyInt(op);
76+
op = getAnyInt();
77+
78+
__expect_type(autoInferInt8, "(int8) -> int8");
79+
__expect_type(autoInferUInt16Opt(0), "uint16?");
80+
__expect_type(autoInferInt_v1(), "int");
81+
__expect_type(autoInferInt_v2(), "int");
82+
83+
var amount: uint100 = 1000;
84+
var percent: uint8 = 50;
85+
var new = amount * percent / 100;
86+
amount = new;
87+
}
88+
89+
@method_id(104)
90+
fun test4(): (int32, int32?, bool, bool) {
91+
var x = getNullableInt32();
92+
__expect_type(x, "int32?");
93+
if (x! != x! || x! != 32) {
94+
return (0, null, false, false);
95+
}
96+
if (x == null) {
97+
return (-1, null, false, false);
98+
}
99+
x += x;
100+
return (x, x, x == x, x == getAnyInt());
101+
}
102+
103+
@method_id(105)
104+
fun test5() {
105+
var (x: int8, y: uint16) = (1, 2);
106+
var cell = beginCell().storeInt(x, 32).storeInt(y, 32 as int32).endCell();
107+
var slice = cell.beginParse();
108+
x = slice.loadInt(32);
109+
y = slice.loadInt(32);
110+
__expect_type(x & y, "int");
111+
return (x + y, x && y, x & y);
112+
}
113+
114+
fun test6() {
115+
var x: int11 = ~(0 as int22);
116+
while (x) {}
117+
return x ? x : 0;
118+
}
119+
120+
fun test7() {
121+
var n = getNullableVarInt32();
122+
__expect_type(n!, "varint32");
123+
__expect_type(n, "varint32?");
124+
if (n != null) {
125+
__expect_type(n, "varint32");
126+
__expect_type(n += n, "varint32");
127+
__expect_type(n += n as int8, "varint32");
128+
} else {
129+
n = 0;
130+
n = 0 as varint32?;
131+
n = 0 as varint32;
132+
}
133+
__expect_type(n, "varint32");
134+
__expect_type(n as uint32, "uint32");
135+
__expect_type(n as varint16, "varint16");
136+
__expect_type((n as varint16) as int, "int");
137+
}
138+
139+
fun test13(x1: int?, x2: int8?, x3: int, x4: int8): (int8?, int8?, int8?, int8?, int32?, int32?) {
140+
return (x1, x2, x3, x4, x4 as int32, x4 as int32?);
141+
}
142+
143+
@method_id(114)
144+
fun test14(firstComponent: int8?): (int, int16)? {
145+
return firstComponent! < 10 ? null : (firstComponent!, 2);
146+
}
147+
148+
fun assign0<T>(mutate v: T) { v = 0; }
149+
150+
fun main() {
151+
var t = createEmptyTuple();
152+
t.tuplePush(1);
153+
t.tuplePush(2);
154+
t.tuplePush(3);
155+
assign0(mutate t.0 as int8);
156+
assign0(mutate t.1 as uint16);
157+
assign0(mutate t.2 as int);
158+
__expect_type(t.0 as int1, "int1");
159+
__expect_type(t.0 as uint257, "uint257");
160+
__expect_type(0 as int32, "int32");
161+
__expect_type((0 as int32) as uint64?, "uint64?");
162+
__expect_type(null as (int8, [uint16?])?, "(int8, [uint16?])?");
163+
__expect_type(10>3 ? 0 as uint8 : 0 as uint16, "int");
164+
return t;
165+
}
166+
167+
/**
168+
@testcase | 0 | | [ 0 0 0 ]
169+
@testcase | 101 | 0 | 1
170+
@testcase | 104 | | 64 64 -1 0
171+
@testcase | 105 | | 3 -1 0
172+
@testcase | 114 | 5 | (null) (null) 0
173+
@testcase | 114 | 15 | 15 2 -1
174+
175+
@fif_codegen DECLPROC assign0<int8>
176+
@fif_codegen DECLPROC assign0<uint16>
177+
@fif_codegen DECLPROC assign0<int>
178+
*/
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
fun testCantCastBoolToUIntN(x: int) {
2+
var eq = x == 0;
3+
eq as int8; // ok
4+
eq as uint8;
5+
}
6+
7+
/**
8+
@compilation_should_fail
9+
@stderr type `bool` can not be cast to `uint8`
10+
*/
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
fun testUInt258DoesntExist() {
2+
var c = 0 as uint258;
3+
}
4+
5+
/**
6+
@compilation_should_fail
7+
@stderr unknown type name `uint258`
8+
*/
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
fun testAssignBetweenDifferentIntN(op: int32, qid: uint64) {
2+
op = qid as int32; // ok
3+
op = qid;
4+
}
5+
6+
/**
7+
@compilation_should_fail
8+
@stderr can not assign `uint64` to variable of type `int32`
9+
@stderr op = qid;
10+
*/
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
fun assign0(mutate x: uint8) {
2+
x = 0;
3+
}
4+
5+
fun testCantPassDifferentIntN(x: int8) {
6+
assign0(mutate (x as int)); // ok
7+
assign0(mutate (x as uint8)); // ok
8+
assign0(mutate ((x as int64) as int)); // ok
9+
assign0(mutate x);
10+
}
11+
12+
/**
13+
@compilation_should_fail
14+
@stderr can not pass `int8` to `uint8`
15+
@stderr assign0(mutate x);
16+
*/

tolk-tester/tests/nullable-tensors.tolk

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,12 @@ fun test136(x: int?, ) {
422422
return ((t1, t1 == null, ), ); // testing trailing comma everywhere :)
423423
}
424424

425+
@method_id(140)
426+
fun test140(x: int8, y: int16) {
427+
var t1: (int32, int)? = (x, y) as (int32, int64)?;
428+
var t2: (int32, int)? = null as (int32, int64)?;
429+
return (t1, t2);
430+
}
425431

426432

427433
fun main(){}
@@ -475,6 +481,7 @@ fun main(){}
475481
@testcase | 135 | | [ 10 ] [ (null) ] (null) 777 10 -1 (null) -1 (null) 0 777 10 -1 (null) -1 (null) 0 777 0 0 -1 0 0 -1 0 0 -1 777 0 -1 0 0 -1 0
476482
@testcase | 136 | 9 | 9 0
477483
@testcase | 136 | null | (null) -1
484+
@testcase | 140 | 8 9 | 8 9 -1 (null) (null) 0
478485

479486
@fif_codegen
480487
"""

tolk/pipe-ast-to-legacy.cpp

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -535,7 +535,30 @@ static std::vector<var_idx_t> transition_expr_to_runtime_type_impl(std::vector<v
535535
// pass `bool` to `int`
536536
// in code, it's done via `as` operator, like `boolVar as int`
537537
// no changes in rvect, boolVar is guaranteed to be -1 or 0 at TVM level
538-
if (target_type == TypeDataInt::create() && original_type == TypeDataBool::create()) {
538+
if (original_type == TypeDataBool::create() && target_type == TypeDataInt::create()) {
539+
return rvect;
540+
}
541+
// pass `bool` to `int8`
542+
// same as above
543+
if (original_type == TypeDataBool::create() && target_type->try_as<TypeDataIntN>()) {
544+
return rvect;
545+
}
546+
// pass `int8` to `int`
547+
// it comes from auto cast when an integer (even a literal) is assigned to intN
548+
// to changes in rvect, intN is int at TVM level
549+
if (target_type == TypeDataInt::create() && original_type->try_as<TypeDataIntN>()) {
550+
return rvect;
551+
}
552+
// pass `int` to `int8`
553+
// in code, it's probably done with `as` operator
554+
// no changes in rvect
555+
if (original_type == TypeDataInt::create() && target_type->try_as<TypeDataIntN>()) {
556+
return rvect;
557+
}
558+
// pass `int8` to `int16` / `int8` to `uint8`
559+
// in code, it's probably done with `as` operator
560+
// no changes in rvect
561+
if (original_type->try_as<TypeDataIntN>() && target_type->try_as<TypeDataIntN>()) {
539562
return rvect;
540563
}
541564
// pass something to `unknown`

tolk/pipe-check-inferred-types.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ static void handle_possible_compiler_internal_call(FunctionPtr cur_f, V<ast_func
126126
}
127127

128128
static bool expect_integer(AnyExprV v_inferred) {
129-
return v_inferred->inferred_type == TypeDataInt::create();
129+
return v_inferred->inferred_type == TypeDataInt::create() || v_inferred->inferred_type->try_as<TypeDataIntN>();
130130
}
131131

132132
static bool expect_boolean(AnyExprV v_inferred) {
@@ -145,6 +145,7 @@ class CheckInferredTypesVisitor final : public ASTVisitorFunctionBody {
145145
parent::visit(rhs);
146146

147147
// all operators (+=, etc.) can work for integers (if both sides are integers)
148+
// for intN, they are also allowed (int16 |= int8 is ok, since int16 | int8 is ok, all arithmetic is int)
148149
bool types_ok = expect_integer(lhs) && expect_integer(rhs);
149150
// bitwise operators &= |= ^= are "overloaded" for booleans also (if both sides are booleans)
150151
if (!types_ok && (v->tok == tok_set_bitwise_and || v->tok == tok_set_bitwise_or || v->tok == tok_set_bitwise_xor)) {
@@ -181,8 +182,9 @@ class CheckInferredTypesVisitor final : public ASTVisitorFunctionBody {
181182

182183
switch (v->tok) {
183184
// == != can compare both integers and booleans, (int == bool) is NOT allowed
185+
// for intN, it also works: (int8 == int16) is ok, (int == uint32) is ok
184186
// note, that `int?` and `int?` can't be compared, since Fift `EQUAL` works with integers only
185-
// (if to allow `int?` in the future, `==` must be expressed in a complicated Fift code considering TVM NULL)
187+
// (if to allow `int?`/`int8?` in the future, `==` must be expressed in a complicated Fift code considering TVM NULL)
186188
case tok_eq:
187189
case tok_neq: {
188190
bool both_int = expect_integer(lhs) && expect_integer(rhs);
@@ -207,6 +209,7 @@ class CheckInferredTypesVisitor final : public ASTVisitorFunctionBody {
207209
}
208210
break;
209211
// & | ^ are "overloaded" both for integers and booleans, (int & bool) is NOT allowed
212+
// they are allowed for intN (int16 & int32 is ok) and always "fall back" to general int
210213
case tok_bitwise_and:
211214
case tok_bitwise_or:
212215
case tok_bitwise_xor: {
@@ -228,6 +231,7 @@ class CheckInferredTypesVisitor final : public ASTVisitorFunctionBody {
228231
break;
229232
}
230233
// others are mathematical: + * ...
234+
// they are allowed for intN (int16 + int32 is ok) and always "fall back" to general int
231235
default:
232236
if (!expect_integer(lhs) || !expect_integer(rhs)) {
233237
fire_error_cannot_apply_operator(cur_f, v->loc, v->operator_name, lhs, rhs);

tolk/pipe-infer-types-and-calls.cpp

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -457,14 +457,13 @@ class InferTypesAndCallsAndFieldsVisitor final {
457457
FlowContext rhs_flow = std::move(after_lhs.out_flow);
458458
ExprFlow after_rhs = infer_any_expr(rhs, std::move(rhs_flow), false, lhs->inferred_type);
459459

460-
// almost all operators implementation is hardcoded by built-in functions `_+_` and similar
460+
// all operators implementation is hardcoded by built-in functions `_+_` and similar
461461
std::string_view builtin_func = v->operator_name; // "+" for operator +=
462462

463463
assign_inferred_type(v, lhs);
464-
if (!builtin_func.empty()) {
465-
FunctionPtr builtin_sym = lookup_global_symbol("_" + static_cast<std::string>(builtin_func) + "_")->try_as<FunctionPtr>();
466-
v->mutate()->assign_fun_ref(builtin_sym);
467-
}
464+
465+
FunctionPtr builtin_sym = lookup_global_symbol("_" + static_cast<std::string>(builtin_func) + "_")->try_as<FunctionPtr>();
466+
v->mutate()->assign_fun_ref(builtin_sym);
468467

469468
return ExprFlow(std::move(after_rhs.out_flow), used_as_condition);
470469
}
@@ -530,7 +529,6 @@ class InferTypesAndCallsAndFieldsVisitor final {
530529
} else {
531530
assign_inferred_type(v, TypeDataInt::create());
532531
}
533-
assign_inferred_type(v, rhs); // (int & int) is int, (bool & bool) is bool
534532
break;
535533
// && || result in booleans, but building flow facts is tricky due to short-circuit
536534
case tok_logical_and: {
@@ -560,6 +558,7 @@ class InferTypesAndCallsAndFieldsVisitor final {
560558
return ExprFlow(std::move(out_flow), std::move(true_flow), std::move(false_flow));
561559
}
562560
// others are mathematical: + * ...
561+
// they are allowed for intN (int16 + int32 is ok) and always "fall back" to general int
563562
default:
564563
flow = infer_any_expr(lhs, std::move(flow), false).out_flow;
565564
flow = infer_any_expr(rhs, std::move(flow), false).out_flow;

tolk/smart-casts-cfg.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,10 @@ static TypePtr calculate_type_lca(TypePtr a, TypePtr b) {
192192
return TypeDataTypedTuple::create(std::move(types_lca));
193193
}
194194

195+
if (a->try_as<TypeDataIntN>() && b->try_as<TypeDataIntN>()) { // cond ? int32 : int64
196+
return TypeDataInt::create();
197+
}
198+
195199
return nullptr;
196200
}
197201

0 commit comments

Comments
 (0)