Skip to content

Commit 9ae3328

Browse files
committed
Precompute and inline constant globals
This is necessary so that other constant globals referencing constant globals can be precomputed as well (NON_STANDALONE_FLOW in binaryen)
1 parent d63ed92 commit 9ae3328

File tree

13 files changed

+323
-198
lines changed

13 files changed

+323
-198
lines changed

src/compiler.ts

Lines changed: 93 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { compileCall as compileBuiltinCall } from "./builtins";
1+
import { compileCall as compileBuiltinCall, initialize } from "./builtins";
22
import { PATH_DELIMITER } from "./constants";
33
import { DiagnosticCode, DiagnosticEmitter } from "./diagnostics";
4-
import { Module, MemorySegment, ExpressionRef, UnaryOp, BinaryOp, NativeType, FunctionTypeRef, getExpressionId, ExpressionId } from "./module";
5-
import { Program, ClassPrototype, Class, Element, ElementKind, Enum, FunctionPrototype, Function, Global, Local, Namespace, Parameter } from "./program";
4+
import { Module, MemorySegment, ExpressionRef, UnaryOp, BinaryOp, NativeType, FunctionRef, FunctionTypeRef, getExpressionId, ExpressionId, getExpressionType, getFunctionBody, getConstValueI32, getConstValueI64Low, getConstValueI64High, getConstValueF32, getConstValueF64 } from "./module";
5+
import { Program, ClassPrototype, Class, Element, ElementKind, Enum, FunctionPrototype, Function, Global, Local, Namespace, Parameter, EnumValue } from "./program";
66
import { I64, U64, sb } from "./util";
77
import { Token } from "./tokenizer";
88
import {
@@ -298,7 +298,7 @@ export class Compiler extends DiagnosticEmitter {
298298
if (!this.compileGlobal(<Global>element))
299299
return null;
300300
if (declaration.range.source.isEntry && (<VariableStatement>declaration.parent).parent == declaration.range.source && hasModifier(ModifierKind.EXPORT, declaration.modifiers)) {
301-
if (!(<Global>element).isCompiledMutable)
301+
if ((<Global>element).hasConstantValue)
302302
this.module.addGlobalExport(element.internalName, declaration.identifier.name);
303303
else
304304
this.warning(DiagnosticCode.Cannot_export_a_mutable_global, declaration.range);
@@ -325,7 +325,7 @@ export class Compiler extends DiagnosticEmitter {
325325
return true;
326326
const nativeType: NativeType = typeToNativeType(<Type>type);
327327
let initializer: ExpressionRef;
328-
let initializeInStart: bool;
328+
let initializeInStart: bool = false;
329329
if (element.hasConstantValue) {
330330
if (type.isLongInteger)
331331
initializer = element.constantIntegerValue ? this.module.createI64(element.constantIntegerValue.lo, element.constantIntegerValue.hi) : this.module.createI64(0, 0);
@@ -341,25 +341,49 @@ export class Compiler extends DiagnosticEmitter {
341341
initializer = this.module.createI32(element.constantIntegerValue ? element.constantIntegerValue.toI32() & type.smallIntegerMask: 0);
342342
} else
343343
initializer = this.module.createI32(element.constantIntegerValue ? element.constantIntegerValue.toI32() : 0);
344-
initializeInStart = false;
345344
} else if (declaration) {
346345
if (declaration.initializer) {
347346
initializer = this.compileExpression(declaration.initializer, type);
348-
initializeInStart = getExpressionId(initializer) != ExpressionId.Const; // MVP doesn't support complex initializers
349-
} else {
347+
if (getExpressionId(initializer) != ExpressionId.Const) {
348+
if (!element.isMutable) {
349+
initializer = this.precomputeExpressionRef(initializer);
350+
if (getExpressionId(initializer) != ExpressionId.Const) {
351+
this.warning(DiagnosticCode.Compiling_constant_global_with_non_constant_initializer_as_mutable, declaration.range);
352+
initializeInStart = true;
353+
}
354+
} else
355+
initializeInStart = true;
356+
}
357+
} else
350358
initializer = typeToNativeZero(this.module, type);
351-
initializeInStart = false;
352-
}
353359
} else
354360
throw new Error("unexpected missing declaration or constant value");
355361
const internalName: string = element.internalName;
356362
if (initializeInStart) {
357363
this.module.addGlobal(internalName, nativeType, true, typeToNativeZero(this.module, type));
358364
this.startFunctionBody.push(this.module.createSetGlobal(internalName, initializer));
359-
element.isCompiledMutable = true;
360365
} else {
361366
this.module.addGlobal(internalName, nativeType, element.isMutable, initializer);
362-
element.isCompiledMutable = element.isMutable;
367+
if (!element.isMutable) {
368+
element.hasConstantValue = true;
369+
const exprType: NativeType = getExpressionType(initializer);
370+
switch (exprType) {
371+
case NativeType.I32:
372+
element.constantIntegerValue = new I64(getConstValueI32(initializer), 0);
373+
break;
374+
case NativeType.I64:
375+
element.constantIntegerValue = new I64(getConstValueI64Low(initializer), getConstValueI64High(initializer));
376+
break;
377+
case NativeType.F32:
378+
element.constantFloatValue = getConstValueF32(initializer);
379+
break;
380+
case NativeType.F64:
381+
element.constantFloatValue = getConstValueF64(initializer);
382+
break;
383+
default:
384+
throw new Error("unexpected initializer type");
385+
}
386+
}
363387
}
364388
return element.isCompiled = true;
365389
}
@@ -376,7 +400,7 @@ export class Compiler extends DiagnosticEmitter {
376400
compileEnum(element: Enum): void {
377401
if (element.isCompiled)
378402
return;
379-
let previousInternalName: string | null = null;
403+
let previousValue: EnumValue | null = null;
380404
for (let [key, val] of element.members) {
381405
if (val.hasConstantValue) {
382406
this.module.addGlobal(val.internalName, NativeType.I32, false, this.module.createI32(val.constantValue));
@@ -386,27 +410,40 @@ export class Compiler extends DiagnosticEmitter {
386410
let initializeInStart: bool = false;
387411
if (declaration.value) {
388412
initializer = this.compileExpression(<Expression>declaration.value, Type.i32);
389-
initializeInStart = getExpressionId(initializer) != ExpressionId.Const; // MVP doesn't support complex initializers
390-
} else if (previousInternalName == null) {
413+
if (getExpressionId(initializer) != ExpressionId.Const) {
414+
initializer = this.precomputeExpressionRef(initializer);
415+
if (getExpressionId(initializer) != ExpressionId.Const) {
416+
this.warning(DiagnosticCode.Compiling_constant_global_with_non_constant_initializer_as_mutable, declaration.range);
417+
initializeInStart = true;
418+
}
419+
}
420+
} else if (previousValue == null) {
391421
initializer = this.module.createI32(0);
392-
initializeInStart = false;
422+
} else if (previousValue.hasConstantValue) {
423+
initializer = this.module.createI32(previousValue.constantValue + 1);
393424
} else {
425+
// in TypeScript this errors with TS1061, but actually we can do:
394426
initializer = this.module.createBinary(BinaryOp.AddI32,
395-
this.module.createGetGlobal(previousInternalName, NativeType.I32),
427+
this.module.createGetGlobal(previousValue.internalName, NativeType.I32),
396428
this.module.createI32(1)
397429
);
430+
this.warning(DiagnosticCode.Compiling_constant_global_with_non_constant_initializer_as_mutable, val.declaration.range);
398431
initializeInStart = true;
399432
}
400433
if (initializeInStart) {
401434
this.module.addGlobal(val.internalName, NativeType.I32, true, this.module.createI32(0));
402435
this.startFunctionBody.push(this.module.createSetGlobal(val.internalName, initializer));
403436
} else {
404437
this.module.addGlobal(val.internalName, NativeType.I32, false, initializer);
405-
// TODO: check export, requires updated binaryen.js with Module#addGlobalExport
438+
if (getExpressionType(initializer) == NativeType.I32) {
439+
val.hasConstantValue = true;
440+
val.constantValue = getConstValueI32(initializer);
441+
} else
442+
throw new Error("unexpected initializer type");
406443
}
407444
} else
408445
throw new Error("unexpected missing declaration or constant value");
409-
previousInternalName = val.internalName;
446+
previousValue = val;
410447
}
411448
element.isCompiled = true;
412449
}
@@ -588,7 +625,7 @@ export class Compiler extends DiagnosticEmitter {
588625

589626
case ElementKind.GLOBAL:
590627
if (this.compileGlobal(<Global>element) && statement.range.source.isEntry) {
591-
if (!(<Global>element).isCompiledMutable)
628+
if ((<Global>element).hasConstantValue)
592629
this.module.addGlobalExport(element.internalName, member.externalIdentifier.name);
593630
else
594631
this.warning(DiagnosticCode.Cannot_export_a_mutable_global, member.range);
@@ -988,6 +1025,24 @@ export class Compiler extends DiagnosticEmitter {
9881025
return expr;
9891026
}
9901027

1028+
precomputeExpression(expression: Expression, contextualType: Type, conversionKind: ConversionKind = ConversionKind.IMPLICIT): ExpressionRef {
1029+
const expr: ExpressionRef = this.compileExpression(expression, contextualType, conversionKind);
1030+
return this.precomputeExpressionRef(expr);
1031+
}
1032+
1033+
precomputeExpressionRef(expr: ExpressionRef): ExpressionRef {
1034+
const nativeType: NativeType = typeToNativeType(this.currentType);
1035+
let typeRef: FunctionTypeRef = this.module.getFunctionTypeBySignature(nativeType, []);
1036+
if (!typeRef)
1037+
typeRef = this.module.addFunctionType(typeToSignatureNamePart(this.currentType), nativeType, []);
1038+
const funcRef: FunctionRef = this.module.addFunction("__precompute", typeRef, [], expr);
1039+
this.module.runPasses([ "precompute" ], funcRef);
1040+
const ret: ExpressionRef = getFunctionBody(funcRef);
1041+
this.module.removeFunction("__precompute");
1042+
// TODO: also remove the function type somehow if no longer used
1043+
return ret;
1044+
}
1045+
9911046
convertExpression(expr: ExpressionRef, fromType: Type, toType: Type, conversionKind: ConversionKind, reportNode: Node): ExpressionRef {
9921047
if (conversionKind == ConversionKind.NONE)
9931048
return expr;
@@ -1636,11 +1691,24 @@ export class Compiler extends DiagnosticEmitter {
16361691

16371692
// global
16381693
if (element.kind == ElementKind.GLOBAL) {
1639-
if ((<Global>element).type)
1640-
this.currentType = <Type>(<Global>element).type;
1641-
return this.compileGlobal(<Global>element) // reports
1642-
? this.module.createGetGlobal((<Global>element).internalName, typeToNativeType(this.currentType = <Type>(<Global>element).type))
1643-
: this.module.createUnreachable();
1694+
const global: Global = <Global>element;
1695+
if (global.type)
1696+
this.currentType = <Type>global.type;
1697+
if (!this.compileGlobal(global)) // reports
1698+
return this.module.createUnreachable();
1699+
if (global.hasConstantValue) {
1700+
if (global.type == Type.f32)
1701+
return this.module.createF32((<Global>element).constantFloatValue);
1702+
else if (global.type == Type.f64)
1703+
return this.module.createF64((<Global>element).constantFloatValue);
1704+
else if ((<Type>global.type).isLongInteger)
1705+
return this.module.createI64((<I64>global.constantIntegerValue).lo, (<I64>global.constantIntegerValue).hi);
1706+
else if ((<Type>global.type).isAnyInteger)
1707+
return this.module.createI32((<I64>global.constantIntegerValue).lo);
1708+
else
1709+
throw new Error("unexpected global type");
1710+
} else
1711+
return this.module.createGetGlobal((<Global>element).internalName, typeToNativeType(this.currentType = <Type>(<Global>element).type));
16441712
}
16451713

16461714
// field

src/diagnosticMessages.generated.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export enum DiagnosticCode {
66
Operation_not_supported = 102,
77
Operation_is_unsafe = 103,
88
Cannot_export_a_mutable_global = 104,
9+
Compiling_constant_global_with_non_constant_initializer_as_mutable = 105,
910
Unterminated_string_literal = 1002,
1011
Identifier_expected = 1003,
1112
_0_expected = 1005,
@@ -14,6 +15,7 @@ export enum DiagnosticCode {
1415
Unexpected_token = 1012,
1516
A_rest_parameter_must_be_last_in_a_parameter_list = 1014,
1617
A_required_parameter_cannot_follow_an_optional_parameter = 1016,
18+
Enum_member_must_have_initializer = 1061,
1719
Statements_are_not_allowed_in_ambient_contexts = 1036,
1820
Initializers_are_not_allowed_in_ambient_contexts = 1039,
1921
_0_modifier_cannot_be_used_here = 1042,
@@ -76,6 +78,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string {
7678
case 102: return "Operation not supported.";
7779
case 103: return "Operation is unsafe.";
7880
case 104: return "Cannot export a mutable global.";
81+
case 105: return "Compiling constant global with non-constant initializer as mutable.";
7982
case 1002: return "Unterminated string literal.";
8083
case 1003: return "Identifier expected.";
8184
case 1005: return "'{0}' expected.";
@@ -84,6 +87,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string {
8487
case 1012: return "Unexpected token.";
8588
case 1014: return "A rest parameter must be last in a parameter list.";
8689
case 1016: return "A required parameter cannot follow an optional parameter.";
90+
case 1061: return "Enum member must have initializer.";
8791
case 1036: return "Statements are not allowed in ambient contexts.";
8892
case 1039: return "Initializers are not allowed in ambient contexts.";
8993
case 1042: return "'{0}' modifier cannot be used here.";

src/diagnosticMessages.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"Operation not supported.": 102,
55
"Operation is unsafe.": 103,
66
"Cannot export a mutable global.": 104,
7+
"Compiling constant global with non-constant initializer as mutable.": 105,
78

89
"Unterminated string literal.": 1002,
910
"Identifier expected.": 1003,
@@ -13,6 +14,7 @@
1314
"Unexpected token.": 1012,
1415
"A rest parameter must be last in a parameter list.": 1014,
1516
"A required parameter cannot follow an optional parameter.": 1016,
17+
"Enum member must have initializer.": 1061,
1618
"Statements are not allowed in ambient contexts.": 1036,
1719
"Initializers are not allowed in ambient contexts.": 1039,
1820
"'{0}' modifier cannot be used here.": 1042,

src/module.ts

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,15 @@ export class Module {
522522
}
523523
}
524524

525+
removeFunction(name: string): void {
526+
const cStr: CString = allocString(name);
527+
try {
528+
_BinaryenRemoveFunction(this.ref, cStr);
529+
} finally {
530+
_free(cStr);
531+
}
532+
}
533+
525534
addFunctionExport(internalName: string, externalName: string): ExportRef {
526535
if (this.noEmit) return 0;
527536
const cStr1: CString = allocString(internalName);
@@ -691,9 +700,27 @@ export class Module {
691700
_BinaryenSetStart(this.ref, func);
692701
}
693702

694-
optimize(): void {
695-
if (this.noEmit) return;
696-
_BinaryenModuleOptimize(this.ref);
703+
optimize(func: FunctionRef = 0): void {
704+
if (func)
705+
_BinaryenFunctionOptimize(func, this.ref);
706+
else
707+
_BinaryenModuleOptimize(this.ref);
708+
}
709+
710+
runPasses(passes: string[], func: FunctionRef = 0): void {
711+
let i: i32, k: i32 = passes.length;
712+
const names: CString[] = new Array(k);
713+
for (i = 0; i < k; ++i) names[i] = allocString(passes[i]);
714+
const cArr: CArray<i32> = allocI32Array(names);
715+
try {
716+
if (func)
717+
_BinaryenFunctionRunPasses(func, this.ref, cArr, k);
718+
else
719+
_BinaryenModuleRunPasses(this.ref, cArr, k);
720+
} finally {
721+
_free(cArr);
722+
for (; i >= 0; --i) _free(names[i]);
723+
}
697724
}
698725

699726
validate(): bool {
@@ -773,6 +800,10 @@ export function getConstValueF64(expr: ExpressionRef): f64 {
773800
return _BinaryenConstGetValueF64(expr);
774801
}
775802

803+
export function getFunctionBody(func: FunctionRef): ExpressionRef {
804+
return _BinaryenFunctionGetBody(func);
805+
}
806+
776807
export class Relooper {
777808

778809
module: Module;

src/program.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -771,7 +771,6 @@ export class Global extends Element {
771771
hasConstantValue: bool = false;
772772
constantIntegerValue: I64 | null = null;
773773
constantFloatValue: f64 = 0;
774-
isCompiledMutable: bool = false;
775774

776775
constructor(program: Program, internalName: string, declaration: VariableLikeDeclarationStatement | null, type: Type | null) {
777776
super(program, internalName);

tests/compiler/enum.optimized.wast

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
(module
2+
(type $i (func (result i32)))
3+
(type $v (func))
4+
(global $enum/NonConstant.ZERO (mut i32) (i32.const 0))
5+
(global $enum/NonConstant.ONE (mut i32) (i32.const 0))
6+
(memory $0 1)
7+
(data (i32.const 4) "\08")
8+
(export "memory" (memory $0))
9+
(start $start)
10+
(func $enum/getZero (; 0 ;) (type $i) (result i32)
11+
(i32.const 0)
12+
)
13+
(func $start (; 1 ;) (type $v)
14+
(set_global $enum/NonConstant.ZERO
15+
(call $enum/getZero)
16+
)
17+
(set_global $enum/NonConstant.ONE
18+
(i32.add
19+
(get_global $enum/NonConstant.ZERO)
20+
(i32.const 1)
21+
)
22+
)
23+
)
24+
)

tests/compiler/enum.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
export enum Implicit {
2+
ZERO,
3+
ONE,
4+
TWO,
5+
THREE
6+
}
7+
8+
export enum Explicit {
9+
ZERO = 0,
10+
ONE = 0 + 1,
11+
TWO = 1 + 1,
12+
THREE = 3
13+
}
14+
15+
export enum Mixed {
16+
ZERO,
17+
ONE,
18+
THREE = 3,
19+
FOUR
20+
}
21+
22+
function getZero(): i32 {
23+
return 0;
24+
}
25+
26+
export enum NonConstant {
27+
ZERO = getZero(),
28+
ONE
29+
}

0 commit comments

Comments
 (0)