diff --git a/src/compiler.ts b/src/compiler.ts index 98d9a42084..947a673362 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -8090,14 +8090,13 @@ export class Compiler extends DiagnosticEmitter { return module.f64(floatValue); } case LiteralKind.INTEGER: { - let intValue = (expression).value; - if (implicitlyNegate) { - intValue = i64_sub( - i64_new(0), - intValue - ); - } - let type = this.resolver.determineIntegerLiteralType(intValue, contextualType); + let expr = expression; + let type = this.resolver.determineIntegerLiteralType(expr, implicitlyNegate, contextualType); + + let intValue = implicitlyNegate + ? i64_neg(expr.value) + : expr.value; + this.currentType = type; switch (type.kind) { case TypeKind.ISIZE: if (!this.options.isWasm64) return module.i32(i64_low(intValue)); diff --git a/src/diagnosticMessages.json b/src/diagnosticMessages.json index a69e5d608f..b25661a371 100644 --- a/src/diagnosticMessages.json +++ b/src/diagnosticMessages.json @@ -46,6 +46,7 @@ "Property '{0}' is always assigned before being used.": 233, "Expression does not compile to a value at runtime.": 234, "Only variables, functions and enums become WebAssembly module exports.": 235, + "Literal '{0}' does not fit into 'i64' or 'u64' types.": 236, "Importing the table disables some indirect call optimizations.": 901, "Exporting the table disables some indirect call optimizations.": 902, diff --git a/src/extra/ast.ts b/src/extra/ast.ts index 2ba8f449ae..fff9aaccb6 100644 --- a/src/extra/ast.ts +++ b/src/extra/ast.ts @@ -678,7 +678,9 @@ export class ASTBuilder { } visitIntegerLiteralExpression(node: IntegerLiteralExpression): void { - this.sb.push(i64_to_string(node.value)); + var range = node.range; + var hasExplicitSign = range.source.text.startsWith("-", range.start); + this.sb.push(i64_to_string(node.value, !hasExplicitSign)); } visitStringLiteral(str: string): void { diff --git a/src/extra/tsconfig.json b/src/extra/tsconfig.json index be647996f0..ddfc2bda24 100644 --- a/src/extra/tsconfig.json +++ b/src/extra/tsconfig.json @@ -1,6 +1,12 @@ { "extends": "../../std/portable.json", "include": [ - "./**/*.ts" + "../**/*.ts" + ], + "exclude": [ + "../**/node_modules/", + "../tests/**", + "../lib/**", + "./glue/wasm/**" ] } diff --git a/src/glue/js/i64.d.ts b/src/glue/js/i64.d.ts index 8b4780cde9..4b0d28bdb9 100644 --- a/src/glue/js/i64.d.ts +++ b/src/glue/js/i64.d.ts @@ -7,6 +7,9 @@ declare type i64 = { __Long__: true }; // opaque declare const i64_zero: i64; declare const i64_one: i64; +declare const i64_neg_one: i64; +declare const i64_minimum: i64; +declare const i64_maximum: i64; declare function i64_is(value: unknown): value is i64; declare function i64_new(lo: i32, hi?: i32): i64; @@ -14,6 +17,7 @@ declare function i64_low(value: i64): i32; declare function i64_high(value: i64): i32; declare function i64_not(value: i64): i64; +declare function i64_neg(value: i64): i64; declare function i64_clz(value: i64): i32; declare function i64_ctz(value: i64): i32; @@ -31,11 +35,20 @@ declare function i64_xor(left: i64, right: i64): i64; declare function i64_shl(left: i64, right: i64): i64; declare function i64_shr(left: i64, right: i64): i64; declare function i64_shr_u(left: i64, right: i64): i64; + declare function i64_eq(left: i64, right: i64): boolean; declare function i64_ne(left: i64, right: i64): boolean; +declare function i64_ge(left: i64, right: i64): boolean; +declare function i64_ge_u(left: i64, right: i64): boolean; declare function i64_gt(left: i64, right: i64): boolean; +declare function i64_gt_u(left: i64, right: i64): boolean; +declare function i64_le(left: i64, right: i64): boolean; +declare function i64_le_u(left: i64, right: i64): boolean; +declare function i64_lt(left: i64, right: i64): boolean; +declare function i64_lt_u(left: i64, right: i64): boolean; declare function i64_align(value: i64, alignment: i32): i64; +declare function i64_signbit(value): boolean; declare function i64_is_i8(value: i64): boolean; declare function i64_is_i16(value: i64): boolean; @@ -50,3 +63,4 @@ declare function i64_is_f64(value: i64): boolean; declare function i64_to_f32(value: i64): f64; declare function i64_to_f64(value: i64): f64; declare function i64_to_string(value: i64, unsigned?: boolean): string; +declare function i64_clone(value: i64): i64; diff --git a/src/glue/js/i64.js b/src/glue/js/i64.js index 78a2fc06e4..d58d34c1c9 100644 --- a/src/glue/js/i64.js +++ b/src/glue/js/i64.js @@ -10,6 +10,8 @@ import Long from "long"; globalThis.i64_zero = Long.ZERO; globalThis.i64_one = Long.ONE; globalThis.i64_neg_one = Long.fromInt(-1); +globalThis.i64_minimum = Long.MIN_VALUE; +globalThis.i64_maximum = Long.MAX_VALUE; globalThis.i64_is = function i64_is(value) { return Long.isLong(value); @@ -31,6 +33,10 @@ globalThis.i64_not = function i64_not(value) { return value.not(); }; +globalThis.i64_neg = function i64_neg(value) { + return value.neg(); +}; + globalThis.i64_clz = function i64_clz(value) { return value.clz(); }; @@ -124,16 +130,48 @@ globalThis.i64_ne = function i64_ne(left, right) { return left.ne(right); }; +globalThis.i64_ge = function i64_ge(left, right) { + return left.ge(right); +}; + +globalThis.i64_ge_u = function i64_ge_u(left, right) { + return left.toUnsigned().ge(right.toUnsigned()); +}; + globalThis.i64_gt = function i64_gt(left, right) { return left.gt(right); }; +globalThis.i64_gt_u = function i64_gt_u(left, right) { + return left.toUnsigned().gt(right.toUnsigned()); +}; + +globalThis.i64_le = function i64_le(left, right) { + return left.le(right); +}; + +globalThis.i64_le_u = function i64_le_u(left, right) { + return left.toUnsigned().le(right.toUnsigned()); +}; + +globalThis.i64_lt = function i64_lt(left, right) { + return left.lt(right); +}; + +globalThis.i64_lt_u = function i64_lt_u(left, right) { + return left.toUnsigned().lt(right.toUnsigned()); +}; + globalThis.i64_align = function i64_align(value, alignment) { assert(alignment && (alignment & (alignment - 1)) == 0); var mask = Long.fromInt(alignment - 1); return value.add(mask).and(mask.not()); }; +globalThis.i64_signbit = function i64_signbit(value) { + return Boolean(value.high >>> 31); +}; + globalThis.i64_is_i8 = function i64_is_i8(value) { return value.high === 0 && (value.low >= 0 && value.low <= i8.MAX_VALUE) || value.high === -1 && (value.low >= i8.MIN_VALUE && value.low < 0); @@ -190,3 +228,7 @@ globalThis.i64_to_f64 = function i64_to_f64(value) { globalThis.i64_to_string = function i64_to_string(value, unsigned) { return unsigned ? value.toUnsigned().toString() : value.toString(); }; + +globalThis.i64_clone = function i64_clone(value) { + return Long.fromBits(value.low, value.high, value.unsigned); +}; diff --git a/src/glue/wasm/i64.ts b/src/glue/wasm/i64.ts index 03e4d121e3..e0c10fc685 100644 --- a/src/glue/wasm/i64.ts +++ b/src/glue/wasm/i64.ts @@ -14,6 +14,12 @@ // @ts-ignore: decorator @global const i64_neg_one: i64 = -1; +// @ts-ignore: decorator +@global const i64_minimum: i64 = i64.MIN_VALUE; + +// @ts-ignore: decorator +@global const i64_maximum: i64 = i64.MAX_VALUE; + // @ts-ignore: decorator @global @inline function i64_is(value: T): bool { @@ -38,6 +44,12 @@ function i64_not(value: i64): i64 { return ~value; } +// @ts-ignore: decorator +@global @inline +function i64_neg(value: i64): i64 { + return -value; +} + // @ts-ignore: decorator @global @inline function i64_clz(value: i64): i32 { @@ -164,12 +176,54 @@ function i64_ne(left: i64, right: i64): bool { return left != right; } +// @ts-ignore: decorator +@global @inline +function i64_ge(left: i64, right: i64): bool { + return left >= right; +} + +// @ts-ignore: decorator +@global @inline +function i64_ge_u(left: i64, right: i64): bool { + return left >= right; +} + // @ts-ignore: decorator @global @inline function i64_gt(left: i64, right: i64): bool { return left > right; } +// @ts-ignore: decorator +@global @inline +function i64_gt_u(left: i64, right: i64): bool { + return left > right; +} + +// @ts-ignore: decorator +@global @inline +function i64_le(left: i64, right: i64): bool { + return left <= right; +} + +// @ts-ignore: decorator +@global @inline +function i64_le_u(left: i64, right: i64): bool { + return left <= right; +} + +// @ts-ignore: decorator +@global @inline +function i64_lt(left: i64, right: i64): bool { + return left < right; +} + +// @ts-ignore: decorator +@global @inline +function i64_lt_u(left: i64, right: i64): bool { + return left < right; +} + // @ts-ignore: decorator @global @inline function i64_align(value: i64, alignment: i64): i64 { @@ -178,22 +232,28 @@ function i64_align(value: i64, alignment: i64): i64 { return (value + mask) & ~mask; } +// @ts-ignore: decorator +@global @inline +function i64_signbit(value: i64): bool { + return (value >>> 63); +} + // @ts-ignore: decorator @global @inline function i64_is_i8(value: i64): bool { - return value >= i8.MIN_VALUE && value <= i8.MAX_VALUE; + return value >= i8.MIN_VALUE && value <= i8.MAX_VALUE; } // @ts-ignore: decorator @global @inline function i64_is_i16(value: i64): bool { - return value >= i16.MIN_VALUE && value <= i16.MAX_VALUE; + return value >= i16.MIN_VALUE && value <= i16.MAX_VALUE; } // @ts-ignore: decorator @global @inline function i64_is_i32(value: i64): bool { - return value >= i32.MIN_VALUE && value <= i32.MAX_VALUE; + return value >= i32.MIN_VALUE && value <= i32.MAX_VALUE; } // @ts-ignore: decorator @@ -249,3 +309,9 @@ function i64_to_f64(value: i64): f64 { function i64_to_string(value: i64, unsigned: bool = false): string { return unsigned ? u64(value).toString() : value.toString(); } + +// @ts-ignore: decorator +@global @inline +function i64_clone(value: i64): i64 { + return value; +} diff --git a/src/resolver.ts b/src/resolver.ts index 2cdfe69a4f..9f8afa0b1e 100644 --- a/src/resolver.ts +++ b/src/resolver.ts @@ -1512,10 +1512,24 @@ export class Resolver extends DiagnosticEmitter { /** Determines the final type of an integer literal given the specified contextual type. */ determineIntegerLiteralType( /** Integer literal value. */ - intValue: i64, + expr: IntegerLiteralExpression, + /** Has unary minus before literal. */ + negate: bool, /** Contextual type. */ ctxType: Type ): Type { + let intValue = expr.value; + if (negate) { + // x + i64.min > 0 -> underflow + if (i64_gt(i64_add(intValue, i64_minimum), i64_zero)) { + let range = expr.range; + this.error( + DiagnosticCode.Literal_0_does_not_fit_into_i64_or_u64_types, + range, range.source.text.substring(range.start - 1, range.end) + ); + } + intValue = i64_neg(intValue); + } if (ctxType.isValue) { // compile to contextual type if matching switch (ctxType.kind) { @@ -1715,7 +1729,11 @@ export class Resolver extends DiagnosticEmitter { case Token.MINUS: { // implicitly negate if an integer literal to distinguish between i32/u32/i64 if (operand.isLiteralKind(LiteralKind.INTEGER)) { - return this.determineIntegerLiteralType(i64_sub(i64_zero, (operand).value), ctxType); + return this.determineIntegerLiteralType( + operand, + true, + ctxType + ); } // fall-through } @@ -2178,7 +2196,8 @@ export class Resolver extends DiagnosticEmitter { switch (node.literalKind) { case LiteralKind.INTEGER: { let intType = this.determineIntegerLiteralType( - (node).value, + node, + false, ctxType ); return assert(intType.getClassOrWrapper(this.program)); diff --git a/src/tokenizer.ts b/src/tokenizer.ts index fab060fd5c..e1ae0fa9d3 100644 --- a/src/tokenizer.ts +++ b/src/tokenizer.ts @@ -1355,23 +1355,26 @@ export class Tokenizer extends DiagnosticEmitter { var sepEnd = start; var value = i64_new(0); var i64_4 = i64_new(4); + var nextValue = value; + var overflowOccurred = false; + while (pos < end) { let c = text.charCodeAt(pos); if (c >= CharCode._0 && c <= CharCode._9) { // value = (value << 4) + c - CharCode._0; - value = i64_add( + nextValue = i64_add( i64_shl(value, i64_4), i64_new(c - CharCode._0) ); } else if (c >= CharCode.A && c <= CharCode.F) { // value = (value << 4) + 10 + c - CharCode.A; - value = i64_add( + nextValue = i64_add( i64_shl(value, i64_4), i64_new(10 + c - CharCode.A) ); } else if (c >= CharCode.a && c <= CharCode.f) { // value = (value << 4) + 10 + c - CharCode.a; - value = i64_add( + nextValue = i64_add( i64_shl(value, i64_4), i64_new(10 + c - CharCode.a) ); @@ -1388,6 +1391,11 @@ export class Tokenizer extends DiagnosticEmitter { } else { break; } + if (i64_gt_u(value, nextValue)) { + // Unsigned overflow occurred + overflowOccurred = true; + } + value = nextValue; ++pos; } if (pos == start) { @@ -1401,6 +1409,13 @@ export class Tokenizer extends DiagnosticEmitter { this.range(sepEnd - 1) ); } + if (overflowOccurred) { + this.error( + DiagnosticCode.Literal_0_does_not_fit_into_i64_or_u64_types, + this.range(start - 2, pos), + this.source.text.substring(start - 2, pos) + ); + } this.pos = pos; return value; } @@ -1413,11 +1428,14 @@ export class Tokenizer extends DiagnosticEmitter { var sepEnd = start; var value = i64_new(0); var i64_10 = i64_new(10); + var nextValue = value; + var overflowOccurred = false; + while (pos < end) { let c = text.charCodeAt(pos); if (c >= CharCode._0 && c <= CharCode._9) { // value = value * 10 + c - CharCode._0; - value = i64_add( + nextValue = i64_add( i64_mul(value, i64_10), i64_new(c - CharCode._0) ); @@ -1439,6 +1457,11 @@ export class Tokenizer extends DiagnosticEmitter { } else { break; } + if (i64_gt_u(value, nextValue)) { + // Unsigned overflow occurred + overflowOccurred = true; + } + value = nextValue; ++pos; } if (pos == start) { @@ -1451,6 +1474,12 @@ export class Tokenizer extends DiagnosticEmitter { DiagnosticCode.Numeric_separators_are_not_allowed_here, this.range(sepEnd - 1) ); + } else if (overflowOccurred) { + this.error( + DiagnosticCode.Literal_0_does_not_fit_into_i64_or_u64_types, + this.range(start, pos), + this.source.text.substring(start, pos) + ); } this.pos = pos; return value; @@ -1464,11 +1493,14 @@ export class Tokenizer extends DiagnosticEmitter { var sepEnd = start; var value = i64_new(0); var i64_3 = i64_new(3); + var nextValue = value; + var overflowOccurred = false; + while (pos < end) { let c = text.charCodeAt(pos); if (c >= CharCode._0 && c <= CharCode._7) { // value = (value << 3) + c - CharCode._0; - value = i64_add( + nextValue = i64_add( i64_shl(value, i64_3), i64_new(c - CharCode._0) ); @@ -1485,6 +1517,11 @@ export class Tokenizer extends DiagnosticEmitter { } else { break; } + if (i64_gt_u(value, nextValue)) { + // Unsigned overflow occurred + overflowOccurred = true; + } + value = nextValue; ++pos; } if (pos == start) { @@ -1497,6 +1534,12 @@ export class Tokenizer extends DiagnosticEmitter { DiagnosticCode.Numeric_separators_are_not_allowed_here, this.range(sepEnd - 1) ); + } else if (overflowOccurred) { + this.error( + DiagnosticCode.Literal_0_does_not_fit_into_i64_or_u64_types, + this.range(start - 2, pos), + this.source.text.substring(start - 2, pos) + ); } this.pos = pos; return value; @@ -1510,14 +1553,17 @@ export class Tokenizer extends DiagnosticEmitter { var sepEnd = start; var value = i64_new(0); var i64_1 = i64_new(1); + var nextValue = value; + var overflowOccurred = false; + while (pos < end) { let c = text.charCodeAt(pos); if (c == CharCode._0) { // value = (value << 1); - value = i64_shl(value, i64_1); + nextValue = i64_shl(value, i64_1); } else if (c == CharCode._1) { // value = (value << 1) + 1; - value = i64_add( + nextValue = i64_add( i64_shl(value, i64_1), i64_1 ); @@ -1534,6 +1580,11 @@ export class Tokenizer extends DiagnosticEmitter { } else { break; } + if (i64_gt(value, nextValue)) { + // Overflow occurred + overflowOccurred = true; + } + value = nextValue; ++pos; } if (pos == start) { @@ -1546,6 +1597,12 @@ export class Tokenizer extends DiagnosticEmitter { DiagnosticCode.Numeric_separators_are_not_allowed_here, this.range(sepEnd - 1) ); + } else if (overflowOccurred) { + this.error( + DiagnosticCode.Literal_0_does_not_fit_into_i64_or_u64_types, + this.range(start - 2, pos), + this.source.text.substring(start - 2, pos) + ); } this.pos = pos; return value; diff --git a/tests/compiler/literals-errors.json b/tests/compiler/literals-errors.json new file mode 100644 index 0000000000..5a0469c44c --- /dev/null +++ b/tests/compiler/literals-errors.json @@ -0,0 +1,10 @@ +{ + "asc_flags": [ + ], + "stderr": [ + "AS236: Literal '-9223372036854775809' does not fit into 'i64' or 'u64' types.", + "AS236: Literal '-0x8000000000000001' does not fit into 'i64' or 'u64' types.", + "AS236: Literal '-0o1000000000000000000001' does not fit into 'i64' or 'u64' types.", + "EOF" + ] +} diff --git a/tests/compiler/literals-errors.ts b/tests/compiler/literals-errors.ts new file mode 100644 index 0000000000..1317db1aa4 --- /dev/null +++ b/tests/compiler/literals-errors.ts @@ -0,0 +1,6 @@ +// Signed underflow +-9223372036854775809; +-0x8000000000000001; +-0o1000000000000000000001; + +ERROR("EOF"); diff --git a/tests/compiler/literals.debug.wat b/tests/compiler/literals.debug.wat index f29febd140..238ac11537 100644 --- a/tests/compiler/literals.debug.wat +++ b/tests/compiler/literals.debug.wat @@ -97,6 +97,14 @@ drop i32.const 0 drop + i64.const -9223372036854775808 + drop + i64.const -9223372036854775808 + drop + i64.const -9223372036854775808 + drop + i64.const -9223372036854775808 + drop ) (func $~start call $start:literals diff --git a/tests/compiler/literals.ts b/tests/compiler/literals.ts index 0b3652c816..417e1a3773 100644 --- a/tests/compiler/literals.ts +++ b/tests/compiler/literals.ts @@ -42,3 +42,8 @@ 0b1; true; false; + +0x8000000000000000; +-0x8000000000000000; +0o1000000000000000000000; +-0o1000000000000000000000; diff --git a/tests/parser/literals.ts b/tests/parser/literals.ts index 1f445aeb77..086346090b 100644 --- a/tests/parser/literals.ts +++ b/tests/parser/literals.ts @@ -45,6 +45,18 @@ 0b0; 0b1; 0b1111111111111111111111111111111; +18446744073709551615; +0x7FFFFFFFFFFFFFFF; +0x8000000000000000; +0x9E19DB92B4E31BA9; +0xFFFFFFFFFFFFFFFF; +-9223372036854775807; +-9223372036854775808; +-0x7FFFFFFFFFFFFFFF; +-0x8000000000000000; +-1; +-0x1; +-123; 0.0; 0.123; .0; @@ -93,6 +105,13 @@ tag`\unicode\xGG\u\x`; // see https://tc39.es/proposal-template-literal-revision 2.0b; `\unicode\xGG\u\x`; +// doesn't fit into i64 / u64 +18446744073709551616; +0x10000000000000000; +0xFFF000000000000FFF; +0o2000000000000000000000; +0b10000000000000000000000000000000000000000000000000000000000000000; + // technically invalid, but not handled by AS yet, TS1005: ';' expected 3 4; 5 c; diff --git a/tests/parser/literals.ts.fixture.ts b/tests/parser/literals.ts.fixture.ts index 8f7d004851..3f71d572ae 100644 --- a/tests/parser/literals.ts.fixture.ts +++ b/tests/parser/literals.ts.fixture.ts @@ -45,6 +45,18 @@ 0; 1; 2147483647; +18446744073709551615; +9223372036854775807; +9223372036854775808; +11392378155556871081; +18446744073709551615; +-9223372036854775807; +-9223372036854775808; +-9223372036854775807; +-9223372036854775808; +-1; +-1; +-123; 0; 0.123; 0; @@ -89,6 +101,11 @@ a; 2; b; `icodeGx`; +0; +0; +17293822569102708735; +0; +0; 3; 4; 5; @@ -97,13 +114,18 @@ c; d; a; b; -// ERROR 1109: "Expression expected." in literals.ts(86,4+1) -// ERROR 1351: "An identifier or keyword cannot immediately follow a numeric literal." in literals.ts(87,2+0) -// ERROR 1351: "An identifier or keyword cannot immediately follow a numeric literal." in literals.ts(88,2+0) -// ERROR 1109: "Expression expected." in literals.ts(89,3+1) -// ERROR 6188: "Numeric separators are not allowed here." in literals.ts(91,2+0) -// ERROR 1351: "An identifier or keyword cannot immediately follow a numeric literal." in literals.ts(92,3+0) -// ERROR 1351: "An identifier or keyword cannot immediately follow a numeric literal." in literals.ts(93,4+0) -// ERROR 1125: "Hexadecimal digit expected." in literals.ts(94,4+1) -// ERROR 1125: "Hexadecimal digit expected." in literals.ts(94,12+1) -// ERROR 1125: "Hexadecimal digit expected." in literals.ts(94,16+1) +// ERROR 1109: "Expression expected." in literals.ts(98,4+1) +// ERROR 1351: "An identifier or keyword cannot immediately follow a numeric literal." in literals.ts(99,2+0) +// ERROR 1351: "An identifier or keyword cannot immediately follow a numeric literal." in literals.ts(100,2+0) +// ERROR 1109: "Expression expected." in literals.ts(101,3+1) +// ERROR 6188: "Numeric separators are not allowed here." in literals.ts(103,2+0) +// ERROR 1351: "An identifier or keyword cannot immediately follow a numeric literal." in literals.ts(104,3+0) +// ERROR 1351: "An identifier or keyword cannot immediately follow a numeric literal." in literals.ts(105,4+0) +// ERROR 1125: "Hexadecimal digit expected." in literals.ts(106,4+1) +// ERROR 1125: "Hexadecimal digit expected." in literals.ts(106,12+1) +// ERROR 1125: "Hexadecimal digit expected." in literals.ts(106,16+1) +// ERROR 236: "Literal '18446744073709551616' does not fit into 'i64' or 'u64' types." in literals.ts(109,1+20) +// ERROR 236: "Literal '0x10000000000000000' does not fit into 'i64' or 'u64' types." in literals.ts(110,1+19) +// ERROR 236: "Literal '0xFFF000000000000FFF' does not fit into 'i64' or 'u64' types." in literals.ts(111,1+20) +// ERROR 236: "Literal '0o2000000000000000000000' does not fit into 'i64' or 'u64' types." in literals.ts(112,1+24) +// ERROR 236: "Literal '0b10000000000000000000000000000000000000000000000000000000000000000' does not fit into 'i64' or 'u64' types." in literals.ts(113,1+67)