diff --git a/src/compiler.ts b/src/compiler.ts index c7017519..d8a26606 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -779,7 +779,7 @@ export class Compiler extends DiagnosticEmitter { if (!global.is(CommonFlags.RESOLVED)) { - // resolve now if annotated + // Resolve type if annotated if (typeNode) { let resolvedType = this.resolver.resolveType(typeNode, global.parent); // reports if (!resolvedType) return false; @@ -792,8 +792,8 @@ export class Compiler extends DiagnosticEmitter { } global.setType(resolvedType); - // infer from initializer if not annotated - } else if (initializerNode) { // infer type using void/NONE for literal inference + // Otherwise infer type from initializer + } else if (initializerNode) { let previousFlow = this.currentFlow; if (global.hasDecorator(DecoratorFlags.LAZY)) { this.currentFlow = global.file.startFunction.flow; @@ -812,7 +812,7 @@ export class Compiler extends DiagnosticEmitter { } global.setType(this.currentType); - // must either be annotated or have an initializer + // Error if there's neither a type nor an initializer } else { this.error( DiagnosticCode.Type_expected, @@ -822,7 +822,7 @@ export class Compiler extends DiagnosticEmitter { } } - // ambient builtins like 'HEAP_BASE' need to be resolved but are added explicitly + // Handle ambient builtins like 'HEAP_BASE' that need to be resolved but are added explicitly if (global.is(CommonFlags.AMBIENT) && global.hasDecorator(DecoratorFlags.BUILTIN)) { if (global.internalName == BuiltinSymbols.HEAP_BASE) this.runtimeFeatures |= RuntimeFeatures.HEAP; else if (global.internalName == BuiltinSymbols.RTTI_BASE) this.runtimeFeatures |= RuntimeFeatures.RTTI; @@ -832,11 +832,12 @@ export class Compiler extends DiagnosticEmitter { var type = global.type; var nativeType = type.toNativeType(); var isDeclaredConstant = global.is(CommonFlags.CONST) || global.is(CommonFlags.STATIC | CommonFlags.READONLY); + var isDeclaredInline = global.hasDecorator(DecoratorFlags.INLINE); - // handle imports + // Handle imports if (global.is(CommonFlags.AMBIENT)) { - // constant global + // Constant global or mutable globals enabled if (isDeclaredConstant || this.options.hasFeature(Feature.MUTABLE_GLOBAL)) { global.set(CommonFlags.MODULE_IMPORT); mangleImportName(global, global.declaration); @@ -849,7 +850,7 @@ export class Compiler extends DiagnosticEmitter { global.set(CommonFlags.COMPILED); return true; - // importing mutable globals is not supported in the MVP + // Importing mutable globals is not supported in the MVP } else { this.error( DiagnosticCode.Operation_not_supported, @@ -859,11 +860,11 @@ export class Compiler extends DiagnosticEmitter { return false; } - // the MVP does not yet support initializer expressions other than constant values (and constant + // The MVP does not yet support initializer expressions other than constant values (and constant // get_globals), hence such initializations must be performed in the start function for now. var initializeInStart = false; - // evaluate initializer if present + // Evaluate initializer if present if (initializerNode) { if (!initExpr) { let previousFlow = this.currentFlow; @@ -880,21 +881,20 @@ export class Compiler extends DiagnosticEmitter { if (getExpressionId(initExpr) != ExpressionId.Const) { if (isDeclaredConstant) { initExpr = module.precomputeExpression(initExpr); - if (getExpressionId(initExpr) != ExpressionId.Const) { - this.warning( - DiagnosticCode.Compiling_constant_with_non_constant_initializer_as_mutable, - initializerNode.range - ); - initializeInStart = true; - } + if (getExpressionId(initExpr) != ExpressionId.Const) initializeInStart = true; } else { initializeInStart = true; } } - // explicitly inline if annotated - if (global.hasDecorator(DecoratorFlags.INLINE)) { - if (!initializeInStart) { // reported above + // Explicitly inline if annotated + if (isDeclaredInline) { + if (initializeInStart) { + this.warning( + DiagnosticCode.Mutable_value_cannot_be_inlined, + initializerNode.range + ); + } else { assert(getExpressionId(initExpr) == ExpressionId.Const); let exprType = getExpressionType(initExpr); switch (exprType) { @@ -930,7 +930,7 @@ export class Compiler extends DiagnosticEmitter { } } - // initialize to zero if there's no initializer + // Initialize to zero if there's no initializer } else { initExpr = type.toNativeZero(module); } @@ -938,7 +938,7 @@ export class Compiler extends DiagnosticEmitter { var internalName = global.internalName; if (initializeInStart) { // initialize to mutable zero and set the actual value in start - if (global.hasDecorator(DecoratorFlags.INLINE)) { + if (isDeclaredInline) { this.error( DiagnosticCode.Decorator_0_is_not_valid_here, assert(findDecorator(DecoratorKind.INLINE, global.decoratorNodes)).range, "inline" @@ -949,7 +949,7 @@ export class Compiler extends DiagnosticEmitter { this.currentBody.push( module.global_set(internalName, initExpr) ); - } else if (!global.hasDecorator(DecoratorFlags.INLINE)) { // compile normally + } else if (!isDeclaredInline) { // compile normally module.addGlobal(internalName, nativeType, !isDeclaredConstant, initExpr); } return true; @@ -2406,12 +2406,15 @@ export class Compiler extends DiagnosticEmitter { var flow = this.currentFlow; var initializers = new Array(); var resolver = this.resolver; + for (let i = 0; i < numDeclarations; ++i) { let declaration = declarations[i]; let name = declaration.name.text; let type: Type | null = null; let initExpr: ExpressionRef = 0; let initAutoreleaseSkipped = false; + + // Resolve type if annotated if (declaration.type) { type = resolver.resolveType( // reports declaration.type, @@ -2425,7 +2428,9 @@ export class Compiler extends DiagnosticEmitter { ); initAutoreleaseSkipped = this.skippedAutoreleases.has(initExpr); } - } else if (declaration.initializer) { // infer type using void/NONE for proper literal inference + + // Otherwise infer type from initializer + } else if (declaration.initializer) { initExpr = this.compileExpressionRetainType(declaration.initializer, Type.void, ContextualFlags.SKIP_AUTORELEASE ); // reports @@ -2438,6 +2443,8 @@ export class Compiler extends DiagnosticEmitter { continue; } type = this.currentType; + + // Error if there's neither a type nor an initializer } else { this.error( DiagnosticCode.Type_expected, @@ -2445,8 +2452,11 @@ export class Compiler extends DiagnosticEmitter { ); continue; } - let isInlined = false; - if (declaration.is(CommonFlags.CONST)) { + + // Handle constants, and try to inline if value is static + let isConst = declaration.is(CommonFlags.CONST); + let isStatic = false; + if (isConst) { if (initExpr) { initExpr = module.precomputeExpression(initExpr); if (getExpressionId(initExpr) == ExpressionId.Const) { @@ -2496,12 +2506,7 @@ export class Compiler extends DiagnosticEmitter { return this.module.unreachable(); } scopedLocals.set(name, local); - isInlined = true; - } else { - this.warning( - DiagnosticCode.Compiling_constant_with_non_constant_initializer_as_mutable, - declaration.range - ); + isStatic = true; } } else { this.error( @@ -2510,13 +2515,16 @@ export class Compiler extends DiagnosticEmitter { ); } } - if (!isInlined) { + + // Otherwise compile as mutable + if (!isStatic) { let local: Local; if ( declaration.isAny(CommonFlags.LET | CommonFlags.CONST) || flow.is(FlowFlags.INLINE_CONTEXT) ) { // here: not top-level local = flow.addScopedLocal(name, type, declaration.name); // reports if duplicate + if (isConst) flow.setLocalFlag(local.index, LocalFlags.CONSTANT); } else { if (flow.lookupLocal(name)) { this.error( @@ -2526,6 +2534,7 @@ export class Compiler extends DiagnosticEmitter { continue; } local = flow.parentFunction.addLocal(type, name, declaration); + if (isConst) flow.setLocalFlag(local.index, LocalFlags.CONSTANT); } let isManaged = type.isManaged; if (initExpr) { @@ -5234,7 +5243,7 @@ export class Compiler extends DiagnosticEmitter { switch (target.kind) { case ElementKind.LOCAL: { - if (target.is(CommonFlags.CONST)) { + if (flow.isLocalFlag((target).index, LocalFlags.CONSTANT, true)) { this.error( DiagnosticCode.Cannot_assign_to_0_because_it_is_a_constant_or_a_read_only_property, valueExpression.range, target.internalName @@ -5246,7 +5255,7 @@ export class Compiler extends DiagnosticEmitter { } case ElementKind.GLOBAL: { if (!this.compileGlobal(target)) return module.unreachable(); - if (target.is(CommonFlags.CONST)) { + if (target.isAny(CommonFlags.CONST | CommonFlags.READONLY)) { this.error( DiagnosticCode.Cannot_assign_to_0_because_it_is_a_constant_or_a_read_only_property, valueExpression.range, @@ -7534,7 +7543,7 @@ export class Compiler extends DiagnosticEmitter { // compile value expressions and find out whether all are constant var length = expressions.length; var values = new Array(length); - var allValuesAreConstant = true; + var isStatic = true; var nativeElementType = elementType.toNativeType(); for (let i = 0; i < length; ++i) { let expression = expressions[i]; @@ -7548,19 +7557,13 @@ export class Compiler extends DiagnosticEmitter { if (getExpressionId(expr) == ExpressionId.Const) { assert(getExpressionType(expr) == nativeElementType); } else { - if (isConst) { - this.warning( - DiagnosticCode.Compiling_constant_with_non_constant_initializer_as_mutable, - reportNode.range - ); - } - allValuesAreConstant = false; + isStatic = false; } values[i] = expr; } // if the array is static, make a static arraybuffer segment - if (allValuesAreConstant) { + if (isStatic) { flow.freeTempLocal(tempThis); flow.freeTempLocal(tempDataStart); diff --git a/src/diagnosticMessages.generated.ts b/src/diagnosticMessages.generated.ts index 933c3456..d2dbab9b 100644 --- a/src/diagnosticMessages.generated.ts +++ b/src/diagnosticMessages.generated.ts @@ -16,7 +16,7 @@ export enum DiagnosticCode { Type_0_cannot_be_reinterpreted_as_type_1 = 203, Basic_type_0_cannot_be_nullable = 204, Cannot_export_a_mutable_global = 205, - Compiling_constant_with_non_constant_initializer_as_mutable = 206, + Mutable_value_cannot_be_inlined = 206, Unmanaged_classes_cannot_extend_managed_classes_and_vice_versa = 207, Unmanaged_classes_cannot_implement_interfaces = 208, Invalid_regular_expression_flags = 209, @@ -154,7 +154,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string { case 203: return "Type '{0}' cannot be reinterpreted as type '{1}'."; case 204: return "Basic type '{0}' cannot be nullable."; case 205: return "Cannot export a mutable global."; - case 206: return "Compiling constant with non-constant initializer as mutable."; + case 206: return "Mutable value cannot be inlined."; case 207: return "Unmanaged classes cannot extend managed classes and vice-versa."; case 208: return "Unmanaged classes cannot implement interfaces."; case 209: return "Invalid regular expression flags."; diff --git a/src/diagnosticMessages.json b/src/diagnosticMessages.json index 86731c5b..3502ae46 100644 --- a/src/diagnosticMessages.json +++ b/src/diagnosticMessages.json @@ -8,7 +8,7 @@ "Type '{0}' cannot be reinterpreted as type '{1}'.": 203, "Basic type '{0}' cannot be nullable.": 204, "Cannot export a mutable global.": 205, - "Compiling constant with non-constant initializer as mutable.": 206, + "Mutable value cannot be inlined.": 206, "Unmanaged classes cannot extend managed classes and vice-versa.": 207, "Unmanaged classes cannot implement interfaces.": 208, "Invalid regular expression flags.": 209, diff --git a/src/flow.ts b/src/flow.ts index e54c4599..63d00208 100644 --- a/src/flow.ts +++ b/src/flow.ts @@ -143,26 +143,29 @@ export enum LocalFlags { /** No specific conditions. */ NONE = 0, + /** Local is constant. */ + CONSTANT = 1 << 0, /** Local is properly wrapped. Relevant for small integers. */ - WRAPPED = 1 << 0, + WRAPPED = 1 << 1, /** Local is non-null. */ - NONNULL = 1 << 1, + NONNULL = 1 << 2, /** Local is read from. */ - READFROM = 1 << 2, + READFROM = 1 << 3, /** Local is written to. */ - WRITTENTO = 1 << 3, + WRITTENTO = 1 << 4, /** Local is retained. */ - RETAINED = 1 << 4, + RETAINED = 1 << 5, /** Local is conditionally read from. */ - CONDITIONALLY_READFROM = 1 << 5, + CONDITIONALLY_READFROM = 1 << 6, /** Local is conditionally written to. */ - CONDITIONALLY_WRITTENTO = 1 << 6, + CONDITIONALLY_WRITTENTO = 1 << 7, /** Local must be conditionally retained. */ - CONDITIONALLY_RETAINED = 1 << 7, + CONDITIONALLY_RETAINED = 1 << 8, /** Any categorical flag. */ - ANY_CATEGORICAL = WRAPPED + ANY_CATEGORICAL = CONSTANT + | WRAPPED | NONNULL | READFROM | WRITTENTO diff --git a/tests/compiler/constant-assign.json b/tests/compiler/constant-assign.json new file mode 100644 index 00000000..b1da366f --- /dev/null +++ b/tests/compiler/constant-assign.json @@ -0,0 +1,5 @@ +{ + "asc_flags": [ + "--runtime none" + ] +} \ No newline at end of file diff --git a/tests/compiler/constant-assign.optimized.wat b/tests/compiler/constant-assign.optimized.wat new file mode 100644 index 00000000..342a301d --- /dev/null +++ b/tests/compiler/constant-assign.optimized.wat @@ -0,0 +1,319 @@ +(module + (type $FUNCSIG$iii (func (param i32 i32) (result i32))) + (type $FUNCSIG$v (func)) + (type $FUNCSIG$vii (func (param i32 i32))) + (type $FUNCSIG$ii (func (param i32) (result i32))) + (memory $0 1) + (data (i32.const 8) "\04\00\00\00\01\00\00\00\00\00\00\00\04\00\00\00\02") + (data (i32.const 32) "\04\00\00\00\01\00\00\00\00\00\00\00\04\00\00\00\01") + (data (i32.const 56) "\04\00\00\00\01\00\00\00\00\00\00\00\04\00\00\00\02") + (global $~lib/rt/stub/startOffset (mut i32) (i32.const 0)) + (global $~lib/rt/stub/offset (mut i32) (i32.const 0)) + (export "memory" (memory $0)) + (start $start) + (func $~lib/rt/stub/__alloc (; 0 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32) + (local $2 i32) + (local $3 i32) + (local $4 i32) + (local $5 i32) + local.get $0 + i32.const 1073741808 + i32.gt_u + if + unreachable + end + global.get $~lib/rt/stub/offset + i32.const 16 + i32.add + local.tee $3 + local.get $0 + i32.const 1 + local.get $0 + i32.const 1 + i32.gt_u + select + i32.add + i32.const 15 + i32.add + i32.const -16 + i32.and + local.tee $2 + memory.size + local.tee $4 + i32.const 16 + i32.shl + i32.gt_u + if + local.get $4 + local.get $2 + local.get $3 + i32.sub + i32.const 65535 + i32.add + i32.const -65536 + i32.and + i32.const 16 + i32.shr_u + local.tee $5 + local.get $4 + local.get $5 + i32.gt_s + select + memory.grow + i32.const 0 + i32.lt_s + if + local.get $5 + memory.grow + i32.const 0 + i32.lt_s + if + unreachable + end + end + end + local.get $2 + global.set $~lib/rt/stub/offset + local.get $3 + i32.const 16 + i32.sub + local.tee $2 + local.get $1 + i32.store offset=8 + local.get $2 + local.get $0 + i32.store offset=12 + local.get $3 + ) + (func $~lib/memory/memory.copy (; 1 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32) + (local $2 i32) + (local $3 i32) + (local $4 i32) + block $~lib/util/memory/memmove|inlined.0 + i32.const 4 + local.set $2 + local.get $0 + local.get $1 + i32.eq + br_if $~lib/util/memory/memmove|inlined.0 + local.get $0 + local.get $1 + i32.lt_u + if + local.get $1 + i32.const 7 + i32.and + local.get $0 + i32.const 7 + i32.and + i32.eq + if + loop $continue|0 + local.get $0 + i32.const 7 + i32.and + if + local.get $2 + i32.eqz + br_if $~lib/util/memory/memmove|inlined.0 + local.get $2 + i32.const 1 + i32.sub + local.set $2 + local.get $0 + local.tee $3 + i32.const 1 + i32.add + local.set $0 + local.get $1 + local.tee $4 + i32.const 1 + i32.add + local.set $1 + local.get $3 + local.get $4 + i32.load8_u + i32.store8 + br $continue|0 + end + end + loop $continue|1 + local.get $2 + i32.const 8 + i32.ge_u + if + local.get $0 + local.get $1 + i64.load + i64.store + local.get $2 + i32.const 8 + i32.sub + local.set $2 + local.get $0 + i32.const 8 + i32.add + local.set $0 + local.get $1 + i32.const 8 + i32.add + local.set $1 + br $continue|1 + end + end + end + loop $continue|2 + local.get $2 + if + local.get $0 + local.tee $3 + i32.const 1 + i32.add + local.set $0 + local.get $1 + local.tee $4 + i32.const 1 + i32.add + local.set $1 + local.get $3 + local.get $4 + i32.load8_u + i32.store8 + local.get $2 + i32.const 1 + i32.sub + local.set $2 + br $continue|2 + end + end + else + local.get $1 + i32.const 7 + i32.and + local.get $0 + i32.const 7 + i32.and + i32.eq + if + loop $continue|3 + local.get $0 + local.get $2 + i32.add + i32.const 7 + i32.and + if + local.get $2 + i32.eqz + br_if $~lib/util/memory/memmove|inlined.0 + local.get $2 + i32.const 1 + i32.sub + local.tee $2 + local.get $0 + i32.add + local.get $1 + local.get $2 + i32.add + i32.load8_u + i32.store8 + br $continue|3 + end + end + loop $continue|4 + local.get $2 + i32.const 8 + i32.ge_u + if + local.get $2 + i32.const 8 + i32.sub + local.tee $2 + local.get $0 + i32.add + local.get $1 + local.get $2 + i32.add + i64.load + i64.store + br $continue|4 + end + end + end + loop $continue|5 + local.get $2 + if + local.get $2 + i32.const 1 + i32.sub + local.tee $2 + local.get $0 + i32.add + local.get $1 + local.get $2 + i32.add + i32.load8_u + i32.store8 + br $continue|5 + end + end + end + end + ) + (func $~lib/rt/__allocArray (; 2 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) + (local $1 i32) + (local $2 i32) + i32.const 16 + i32.const 3 + call $~lib/rt/stub/__alloc + local.tee $1 + i32.const 4 + i32.const 0 + call $~lib/rt/stub/__alloc + local.tee $2 + i32.store + local.get $1 + local.get $2 + i32.store offset=4 + local.get $1 + i32.const 4 + i32.store offset=8 + local.get $1 + i32.const 1 + i32.store offset=12 + local.get $0 + if + local.get $2 + local.get $0 + call $~lib/memory/memory.copy + end + local.get $1 + ) + (func $start:constant-assign (; 3 ;) (type $FUNCSIG$v) + i32.const 80 + global.set $~lib/rt/stub/startOffset + global.get $~lib/rt/stub/startOffset + global.set $~lib/rt/stub/offset + i32.const 0 + call $~lib/rt/__allocArray + i32.load offset=4 + i32.const 1 + i32.store + i32.const 24 + call $~lib/rt/__allocArray + drop + i32.const 48 + call $~lib/rt/__allocArray + drop + i32.const 72 + call $~lib/rt/__allocArray + drop + ) + (func $start (; 4 ;) (type $FUNCSIG$v) + block + call $start:constant-assign + end + ) + (func $null (; 5 ;) (type $FUNCSIG$v) + nop + ) +) diff --git a/tests/compiler/constant-assign.ts b/tests/compiler/constant-assign.ts new file mode 100644 index 00000000..af26b295 --- /dev/null +++ b/tests/compiler/constant-assign.ts @@ -0,0 +1,30 @@ +// Expect error: TS2540 +// ^ TODO: Properly handle multiple + +function localConst(a: i32): void { + const b = a + 1; + b = 3; +} +localConst(1); + +function localConstInline(): void { + const a = 1; + a = 2; +} +localConstInline(); + +function localConstArray(a: i32): void { + const b: i32[] = [ a ]; + b = [ 2 ]; +} +localConstArray(1); + +function localConstArrayInline(): void { + const a: i32[] = [ 1 ]; + a = [ 2 ]; +} +localConstArrayInline(); + +// globalConst +const a = 1; +a = 2; diff --git a/tests/compiler/constant-assign.untouched.wat b/tests/compiler/constant-assign.untouched.wat new file mode 100644 index 00000000..e69de29b