diff --git a/src/compiler.ts b/src/compiler.ts index 9cb2cb28..886323cb 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -179,11 +179,13 @@ export class Compiler extends DiagnosticEmitter { super(program.diagnostics); this.program = program; this.options = options ? options : new Options(); + this.memoryOffset = new U64(this.options.target == Target.WASM64 ? 8 : 4, 0); // leave space for `null` this.module = this.options.noEmit ? Module.createStub() : Module.create(); + + // set up start function const startFunctionTemplate: FunctionPrototype = new FunctionPrototype(program, "start", "start", null); const startFunctionInstance: Function = new Function(startFunctionTemplate, startFunctionTemplate.internalName, [], [], Type.void, null); this.currentFunction = this.startFunction = startFunctionInstance; - this.memoryOffset = new U64(this.options.target == Target.WASM64 ? 8 : 4, 0); // leave space for `null` } /** Performs compilation of the underlying {@link Program} to a {@link Module}. */ @@ -208,7 +210,9 @@ export class Compiler extends DiagnosticEmitter { if (!typeRef) typeRef = this.module.addFunctionType("v", NativeType.None, []); this.module.setStart( - this.module.addFunction(this.startFunction.prototype.internalName, typeRef, typesToNativeTypes(this.startFunction.additionalLocals), this.module.createBlock(null, this.startFunctionBody)) + this.module.addFunction(this.startFunction.prototype.internalName, typeRef, typesToNativeTypes(this.startFunction.additionalLocals), + this.module.createBlock(null, this.startFunctionBody) + ) ); } @@ -226,7 +230,7 @@ export class Compiler extends DiagnosticEmitter { initial.or32(0xffff); initial.add32(1); } - initial.shru32(16); // initial size in 64k pages + initial.shru32(16); // now is initial size in 64k pages this.module.setMemory(initial.toI32(), Module.MAX_MEMORY_WASM32 /* TODO: not WASM64 compatible yet */, this.memorySegments, this.options.target, "memory"); return this.module; @@ -296,7 +300,8 @@ export class Compiler extends DiagnosticEmitter { default: { const previousFunction: Function = this.currentFunction; this.currentFunction = this.startFunction; - this.startFunctionBody.push(this.compileStatement(statement)); + const expr: ExpressionRef = this.compileStatement(statement); + if (!this.module.noEmit) this.startFunctionBody.push(expr); this.currentFunction = previousFunction; break; } @@ -309,8 +314,8 @@ export class Compiler extends DiagnosticEmitter { compileGlobalDeclaration(declaration: VariableDeclaration, isConst: bool): Global | null { const element: Element | null = this.program.elements.get(declaration.internalName); if (!element || element.kind != ElementKind.GLOBAL) - throw new Error("unexpected missing global"); - if (!this.compileGlobal(element)) + throw new Error("global expected"); + if (!this.compileGlobal(element)) // reports return null; if (isModuleExport(element, declaration)) { if ((element).hasConstantValue) @@ -322,54 +327,58 @@ export class Compiler extends DiagnosticEmitter { } compileGlobal(global: Global): bool { - if (global.isCompiled) - return true; - if (global.isBuiltIn && compileBuiltinGetGlobal(this, global)) + if (global.isCompiled || (global.isBuiltIn && compileBuiltinGetGlobal(this, global))) return true; const declaration: VariableLikeDeclarationStatement | null = global.declaration; - if (!global.type) { - if (declaration && declaration.type) { - global.type = this.program.resolveType(declaration.type); // reports - if (!global.type) + let type: Type | null = null; + let initExpr: ExpressionRef = 0; + + if (!global.type) { // infer type + if (declaration) { + if (declaration.type) { + global.type = this.program.resolveType(declaration.type); // reports + if (!global.type) + return false; + } else if (declaration.initializer) { + initExpr = this.compileExpression(declaration.initializer, Type.void, ConversionKind.NONE); // reports and returns unreachable + if (this.currentType == Type.void) + return false; + global.type = this.currentType; + } else { + this.error(DiagnosticCode.Type_expected, declaration.name.range.atEnd); return false; - } else if (declaration && declaration.initializer) { - global.type = this.determineExpressionType(declaration.initializer); - if (!global.type) - return false; - } else if (declaration) { - this.error(DiagnosticCode.Type_expected, declaration.name.range.atEnd); - return false; + } } else - throw new Error("unable to infer type"); + throw new Error("declaration expected"); } - if (this.module.noEmit) - return true; + const nativeType: NativeType = typeToNativeType(global.type); - let initializer: ExpressionRef; let initializeInStart: bool = false; + if (global.hasConstantValue) { if (global.type.isLongInteger) - initializer = global.constantIntegerValue ? this.module.createI64(global.constantIntegerValue.lo, global.constantIntegerValue.hi) : this.module.createI64(0, 0); + initExpr = global.constantIntegerValue ? this.module.createI64(global.constantIntegerValue.lo, global.constantIntegerValue.hi) : this.module.createI64(0, 0); else if (global.type.kind == TypeKind.F32) - initializer = this.module.createF32(global.constantFloatValue); + initExpr = this.module.createF32(global.constantFloatValue); else if (global.type.kind == TypeKind.F64) - initializer = this.module.createF64(global.constantFloatValue); + initExpr = this.module.createF64(global.constantFloatValue); else if (global.type.isSmallInteger) { if (global.type.isSignedInteger) { const shift: i32 = global.type.smallIntegerShift; - initializer = this.module.createI32(global.constantIntegerValue ? global.constantIntegerValue.toI32() << shift >> shift : 0); + initExpr = this.module.createI32(global.constantIntegerValue ? global.constantIntegerValue.toI32() << shift >> shift : 0); } else - initializer = this.module.createI32(global.constantIntegerValue ? global.constantIntegerValue.toI32() & global.type.smallIntegerMask: 0); + initExpr = this.module.createI32(global.constantIntegerValue ? global.constantIntegerValue.toI32() & global.type.smallIntegerMask: 0); } else - initializer = this.module.createI32(global.constantIntegerValue ? global.constantIntegerValue.toI32() : 0); + initExpr = this.module.createI32(global.constantIntegerValue ? global.constantIntegerValue.toI32() : 0); } else if (declaration) { if (declaration.initializer) { - initializer = this.compileExpression(declaration.initializer, global.type); - if (_BinaryenExpressionGetId(initializer) != ExpressionId.Const) { + if (!initExpr) + initExpr = this.compileExpression(declaration.initializer, global.type); + if (!this.module.noEmit && _BinaryenExpressionGetId(initExpr) != ExpressionId.Const) { if (!global.isMutable) { - initializer = this.precomputeExpressionRef(initializer); - if (_BinaryenExpressionGetId(initializer) != ExpressionId.Const) { + initExpr = this.precomputeExpressionRef(initExpr); + if (_BinaryenExpressionGetId(initExpr) != ExpressionId.Const) { this.warning(DiagnosticCode.Compiling_constant_global_with_non_constant_initializer_as_mutable, declaration.range); initializeInStart = true; } @@ -377,33 +386,35 @@ export class Compiler extends DiagnosticEmitter { initializeInStart = true; } } else - initializer = typeToNativeZero(this.module, global.type); + initExpr = typeToNativeZero(this.module, global.type); } else - throw new Error("unexpected missing declaration or constant value"); + throw new Error("declaration expected"); const internalName: string = global.internalName; if (initializeInStart) { this.module.addGlobal(internalName, nativeType, true, typeToNativeZero(this.module, global.type)); - this.startFunctionBody.push(this.module.createSetGlobal(internalName, initializer)); + const setExpr: ExpressionRef = this.module.createSetGlobal(internalName, initExpr); + if (!this.module.noEmit) + this.startFunctionBody.push(setExpr); } else { - this.module.addGlobal(internalName, nativeType, global.isMutable, initializer); - if (!global.isMutable) { - const exprType: NativeType = _BinaryenExpressionGetType(initializer); + this.module.addGlobal(internalName, nativeType, global.isMutable, initExpr); + if (!global.isMutable && !this.module.noEmit) { + const exprType: NativeType = _BinaryenExpressionGetType(initExpr); switch (exprType) { case NativeType.I32: - global.constantIntegerValue = new I64(_BinaryenConstGetValueI32(initializer), 0); + global.constantIntegerValue = new I64(_BinaryenConstGetValueI32(initExpr), 0); break; case NativeType.I64: - global.constantIntegerValue = new I64(_BinaryenConstGetValueI64Low(initializer), _BinaryenConstGetValueI64High(initializer)); + global.constantIntegerValue = new I64(_BinaryenConstGetValueI64Low(initExpr), _BinaryenConstGetValueI64High(initExpr)); break; case NativeType.F32: - global.constantFloatValue = _BinaryenConstGetValueF32(initializer); + global.constantFloatValue = _BinaryenConstGetValueF32(initExpr); break; case NativeType.F64: - global.constantFloatValue = _BinaryenConstGetValueF64(initializer); + global.constantFloatValue = _BinaryenConstGetValueF64(initExpr); break; default: - throw new Error("unexpected initializer type"); + throw new Error("concrete type expected"); } global.hasConstantValue = true; } @@ -417,13 +428,14 @@ export class Compiler extends DiagnosticEmitter { compileEnumDeclaration(declaration: EnumDeclaration): void { const element: Element | null = this.program.elements.get(declaration.internalName); if (!element || element.kind != ElementKind.ENUM) - throw new Error("unexpected missing enum"); + throw new Error("enum expected"); this.compileEnum(element); } compileEnum(element: Enum): void { if (element.isCompiled) return; + let previousValue: EnumValue | null = null; if (element.members) for (let [key, member] of element.members) { @@ -434,45 +446,49 @@ export class Compiler extends DiagnosticEmitter { this.module.addGlobal(val.internalName, NativeType.I32, false, this.module.createI32(val.constantValue)); } else if (val.declaration) { const declaration: EnumValueDeclaration = val.declaration; - let initializer: ExpressionRef; - let initializeInStart: bool = false; + let initExpr: ExpressionRef; + let initInStart: bool = false; if (declaration.value) { - initializer = this.compileExpression(declaration.value, Type.i32); - if (_BinaryenExpressionGetId(initializer) != ExpressionId.Const) { - initializer = this.precomputeExpressionRef(initializer); - if (_BinaryenExpressionGetId(initializer) != ExpressionId.Const) { + initExpr = this.compileExpression(declaration.value, Type.i32); + if (!this.module.noEmit && _BinaryenExpressionGetId(initExpr) != ExpressionId.Const) { + initExpr = this.precomputeExpressionRef(initExpr); + if (_BinaryenExpressionGetId(initExpr) != ExpressionId.Const) { if (element.isConstant) this.warning(DiagnosticCode.Compiling_constant_global_with_non_constant_initializer_as_mutable, declaration.range); - initializeInStart = true; + initInStart = true; } } } else if (previousValue == null) { - initializer = this.module.createI32(0); + initExpr = this.module.createI32(0); } else if (previousValue.hasConstantValue) { - initializer = this.module.createI32(previousValue.constantValue + 1); + initExpr = this.module.createI32(previousValue.constantValue + 1); } else { // in TypeScript this errors with TS1061, but actually we can do: - initializer = this.module.createBinary(BinaryOp.AddI32, + initExpr = this.module.createBinary(BinaryOp.AddI32, this.module.createGetGlobal(previousValue.internalName, NativeType.I32), this.module.createI32(1) ); if (element.isConstant) this.warning(DiagnosticCode.Compiling_constant_global_with_non_constant_initializer_as_mutable, declaration.range); - initializeInStart = true; + initInStart = true; } - if (initializeInStart) { + if (initInStart) { this.module.addGlobal(val.internalName, NativeType.I32, true, this.module.createI32(0)); - this.startFunctionBody.push(this.module.createSetGlobal(val.internalName, initializer)); + const setExpr: ExpressionRef = this.module.createSetGlobal(val.internalName, initExpr); + if (!this.module.noEmit) + this.startFunctionBody.push(setExpr); } else { - this.module.addGlobal(val.internalName, NativeType.I32, false, initializer); - if (_BinaryenExpressionGetType(initializer) == NativeType.I32) { - val.constantValue = _BinaryenConstGetValueI32(initializer); - val.hasConstantValue = true; - } else - throw new Error("unexpected initializer type"); + this.module.addGlobal(val.internalName, NativeType.I32, false, initExpr); + if (!this.module.noEmit) { + if (_BinaryenExpressionGetType(initExpr) == NativeType.I32) { + val.constantValue = _BinaryenConstGetValueI32(initExpr); + val.hasConstantValue = true; + } else + throw new Error("i32 expected"); + } } } else - throw new Error("unexpected missing declaration or constant value"); + throw new Error("declaration expected"); previousValue = val; } element.isCompiled = true; @@ -505,7 +521,7 @@ export class Compiler extends DiagnosticEmitter { const declaration: FunctionDeclaration | null = instance.prototype.declaration; if (!declaration) - throw new Error("unexpected missing declaration"); + throw new Error("declaration expected"); // built-ins are not compiled here if (instance.isDeclared) { if (declaration.statements) { @@ -600,7 +616,9 @@ export class Compiler extends DiagnosticEmitter { } compileNamespace(ns: Namespace): void { - if (!ns.members) return; + if (!ns.members) + return; + const noTreeShaking: bool = this.options.noTreeShaking; for (let [name, element] of ns.members) { switch (element.kind) { @@ -714,20 +732,6 @@ export class Compiler extends DiagnosticEmitter { return segment; } - // types - - // TODO: try to get rid of this - determineExpressionType(expression: Expression, contextualType: Type = Type.void): Type { - const previousType: Type = this.currentType; - const previousNoEmit: bool = this.module.noEmit; - this.module.noEmit = true; - this.compileExpression(expression, contextualType, ConversionKind.NONE); // now performs a dry run - const type: Type = this.currentType; - this.currentType = previousType; - this.module.noEmit = previousNoEmit; - return type; - } - // statements compileStatement(statement: Statement): ExpressionRef { @@ -785,11 +789,11 @@ export class Compiler extends DiagnosticEmitter { } compileStatements(statements: Statement[]): ExpressionRef[] { - const k: i32 = statements.length; + let i: i32 = 0, k: i32 = statements.length; const stmts: ExpressionRef[] = new Array(k); - for (let i: i32 = 0; i < k; ++i) + for (; i < k; ++i) stmts[i] = this.compileStatement(statements[i]); - return stmts; + return stmts; // array of 0-es in noEmit-mode } compileBlockStatement(statement: BlockStatement): ExpressionRef { @@ -802,8 +806,10 @@ export class Compiler extends DiagnosticEmitter { } compileBreakStatement(statement: BreakStatement): ExpressionRef { - if (statement.label) - throw new Error("not implemented"); + if (statement.label) { + this.error(DiagnosticCode.Operation_not_supported, statement.label.range); + return this.module.createUnreachable(); + } const context: string | null = this.currentFunction.breakContext; if (context != null) return this.module.createBreak("break|" + (context)); @@ -812,8 +818,10 @@ export class Compiler extends DiagnosticEmitter { } compileContinueStatement(statement: ContinueStatement): ExpressionRef { - if (statement.label) - throw new Error("not implemented"); + if (statement.label) { + this.error(DiagnosticCode.Operation_not_supported, statement.label.range); + return this.module.createUnreachable(); + } const context: string | null = this.currentFunction.breakContext; if (context != null && !this.disallowContinue) return this.module.createBreak("continue|" + (context)); @@ -973,13 +981,16 @@ export class Compiler extends DiagnosticEmitter { const declaration: VariableDeclaration = declarations[i]; const name: string = declaration.name.name; let type: Type | null = null; + let init: ExpressionRef = 0; if (declaration.type) { type = this.program.resolveType(declaration.type, this.currentFunction.contextualTypeArguments, true); // reports if (!type) continue; - } else if (declaration.initializer) { - type = this.determineExpressionType(declaration.initializer); // reports - if (!type) + if (declaration.initializer) + init = this.compileExpression(declaration.initializer, type); // reports and returns unreachable + } else if (declaration.initializer) { // infer type + init = this.compileExpression(declaration.initializer, Type.void, ConversionKind.NONE); // reports and returns unreachable + if ((type = this.currentType) == Type.void) continue; } else { this.error(DiagnosticCode.Type_expected, declaration.name.range.atEnd); @@ -989,8 +1000,8 @@ export class Compiler extends DiagnosticEmitter { this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, name); // recoverable else { this.currentFunction.addLocal(type, name); - if (declaration.initializer) - initializers.push(this.compileAssignment(declaration.name, declaration.initializer, Type.void)); + if (init) + initializers.push(this.compileAssignmentWithValue(declaration.name, init)); } } return initializers.length ? this.module.createBlock(null, initializers, NativeType.None) : this.module.createNop(); @@ -1600,8 +1611,35 @@ export class Compiler extends DiagnosticEmitter { } compileAssignment(expression: Expression, valueExpression: Expression, contextualType: Type): ExpressionRef { - this.currentType = this.determineExpressionType(expression, contextualType); - return this.compileAssignmentWithValue(expression, this.compileExpression(valueExpression, this.currentType, ConversionKind.IMPLICIT), contextualType != Type.void); + let element: Element | null = null; + switch (expression.kind) { + case NodeKind.IDENTIFIER: + element = this.program.resolveIdentifier(expression, this.currentFunction); // reports + break; + case NodeKind.PROPERTYACCESS: + element = this.program.resolvePropertyAccess(expression, this.currentFunction); // reports + break; + default: + this.error(DiagnosticCode.Operation_not_supported, expression.range); + } + if (!element) + return this.module.createUnreachable(); + let type: Type | null = null; + switch (element.kind) { + case ElementKind.LOCAL: + type = (element).type; + break; + case ElementKind.GLOBAL: + if (this.compileGlobal(element)) + type = (element).type; + break; + default: + this.error(DiagnosticCode.Operation_not_supported, expression.range); + } + if (!type) + return this.module.createUnreachable(); + this.currentType = type; + return this.compileAssignmentWithValue(expression, this.compileExpression(valueExpression, type, ConversionKind.IMPLICIT), contextualType != Type.void); } compileAssignmentWithValue(expression: Expression, valueWithCorrectType: ExpressionRef, tee: bool = false): ExpressionRef { diff --git a/tests/compiler/infer-type.optimized-inlined.wast b/tests/compiler/infer-type.optimized-inlined.wast index 4ba61d2e..8d9af600 100644 --- a/tests/compiler/infer-type.optimized-inlined.wast +++ b/tests/compiler/infer-type.optimized-inlined.wast @@ -1,11 +1,49 @@ (module (type $v (func)) + (type $i (func (result i32))) + (type $I (func (result i64))) + (type $f (func (result f32))) + (type $F (func (result f64))) + (global $infer-type/ri (mut i32) (i32.const 0)) + (global $infer-type/rI (mut i64) (i64.const 0)) + (global $infer-type/rf (mut f32) (f32.const 0)) + (global $infer-type/rF (mut f64) (f64.const 0)) (memory $0 1) (export "memory" (memory $0)) (start $start) (func $start (; 0 ;) (type $v) - (block $__inlined_func$infer-type/locals - (nop) + (block + (block $__inlined_func$infer-type/locals + (nop) + ) + ) + (set_global $infer-type/ri + (block (result i32) + (block $__inlined_func$infer-type/reti (result i32) + (i32.const 0) + ) + ) + ) + (set_global $infer-type/rI + (block (result i64) + (block $__inlined_func$infer-type/retI (result i64) + (i64.const 0) + ) + ) + ) + (set_global $infer-type/rf + (block (result f32) + (block $__inlined_func$infer-type/retf (result f32) + (f32.const 0) + ) + ) + ) + (set_global $infer-type/rF + (block (result f64) + (block $__inlined_func$infer-type/refF (result f64) + (f64.const 0) + ) + ) ) ) ) diff --git a/tests/compiler/infer-type.optimized.wast b/tests/compiler/infer-type.optimized.wast index a0345d3a..8110dafa 100644 --- a/tests/compiler/infer-type.optimized.wast +++ b/tests/compiler/infer-type.optimized.wast @@ -1,12 +1,44 @@ (module (type $v (func)) + (type $i (func (result i32))) + (type $I (func (result i64))) + (type $f (func (result f32))) + (type $F (func (result f64))) + (global $infer-type/ri (mut i32) (i32.const 0)) + (global $infer-type/rI (mut i64) (i64.const 0)) + (global $infer-type/rf (mut f32) (f32.const 0)) + (global $infer-type/rF (mut f64) (f64.const 0)) (memory $0 1) (export "memory" (memory $0)) (start $start) (func $infer-type/locals (; 0 ;) (type $v) (nop) ) - (func $start (; 1 ;) (type $v) + (func $infer-type/reti (; 1 ;) (type $i) (result i32) + (i32.const 0) + ) + (func $infer-type/retI (; 2 ;) (type $I) (result i64) + (i64.const 0) + ) + (func $infer-type/retf (; 3 ;) (type $f) (result f32) + (f32.const 0) + ) + (func $infer-type/refF (; 4 ;) (type $F) (result f64) + (f64.const 0) + ) + (func $start (; 5 ;) (type $v) (call $infer-type/locals) + (set_global $infer-type/ri + (call $infer-type/reti) + ) + (set_global $infer-type/rI + (call $infer-type/retI) + ) + (set_global $infer-type/rf + (call $infer-type/retf) + ) + (set_global $infer-type/rF + (call $infer-type/refF) + ) ) ) diff --git a/tests/compiler/infer-type.ts b/tests/compiler/infer-type.ts index d1c86c57..fd086063 100644 --- a/tests/compiler/infer-type.ts +++ b/tests/compiler/infer-type.ts @@ -16,3 +16,27 @@ function locals(): void { let aF = F; } locals(); + +function reti(): i32 { + return 0; +} +let ri = reti(); +ri; + +function retI(): i64 { + return 0; +} +let rI = retI(); +rI; + +function retf(): f32 { + return 0; +} +let rf = retf(); +rf; + +function refF(): f64 { + return 0; +} +let rF = refF(); +rF; diff --git a/tests/compiler/infer-type.wast b/tests/compiler/infer-type.wast index 24a4d12a..4d0dda04 100644 --- a/tests/compiler/infer-type.wast +++ b/tests/compiler/infer-type.wast @@ -1,8 +1,16 @@ (module (type $v (func)) + (type $i (func (result i32))) + (type $I (func (result i64))) + (type $f (func (result f32))) + (type $F (func (result f64))) (global $infer-type/i i32 (i32.const 10)) (global $infer-type/I i64 (i64.const 4294967296)) (global $infer-type/F f64 (f64.const 1.5)) + (global $infer-type/ri (mut i32) (i32.const 0)) + (global $infer-type/rI (mut i64) (i64.const 0)) + (global $infer-type/rf (mut f32) (f32.const 0)) + (global $infer-type/rF (mut f64) (f64.const 0)) (global $HEAP_BASE i32 (i32.const 4)) (memory $0 1) (export "memory" (memory $0)) @@ -45,7 +53,27 @@ ) ) ) - (func $start (; 1 ;) (type $v) + (func $infer-type/reti (; 1 ;) (type $i) (result i32) + (return + (i32.const 0) + ) + ) + (func $infer-type/retI (; 2 ;) (type $I) (result i64) + (return + (i64.const 0) + ) + ) + (func $infer-type/retf (; 3 ;) (type $f) (result f32) + (return + (f32.const 0) + ) + ) + (func $infer-type/refF (; 4 ;) (type $F) (result f64) + (return + (f64.const 0) + ) + ) + (func $start (; 5 ;) (type $v) (drop (i32.const 10) ) @@ -56,6 +84,30 @@ (f64.const 1.5) ) (call $infer-type/locals) + (set_global $infer-type/ri + (call $infer-type/reti) + ) + (drop + (get_global $infer-type/ri) + ) + (set_global $infer-type/rI + (call $infer-type/retI) + ) + (drop + (get_global $infer-type/rI) + ) + (set_global $infer-type/rf + (call $infer-type/retf) + ) + (drop + (get_global $infer-type/rf) + ) + (set_global $infer-type/rF + (call $infer-type/refF) + ) + (drop + (get_global $infer-type/rF) + ) ) ) (; @@ -108,6 +160,14 @@ infer-type/I infer-type/F infer-type/locals + infer-type/reti + infer-type/ri + infer-type/retI + infer-type/rI + infer-type/retf + infer-type/rf + infer-type/refF + infer-type/rF [program.exports] ;)