mirror of
https://github.com/fluencelabs/assemblyscript
synced 2025-04-25 23:12:19 +00:00
In order to use the new compile time type checks in generics, untaken branches must be skipped because these might be invalid.
5657 lines
188 KiB
TypeScript
5657 lines
188 KiB
TypeScript
import {
|
|
compileCall as compileBuiltinCall,
|
|
compileGetConstant as compileBuiltinGetConstant,
|
|
compileAllocate as compileBuiltinAllocate
|
|
} from "./builtins";
|
|
|
|
import {
|
|
DiagnosticCode,
|
|
DiagnosticEmitter
|
|
} from "./diagnostics";
|
|
|
|
import {
|
|
Module,
|
|
MemorySegment,
|
|
ExpressionRef,
|
|
UnaryOp,
|
|
BinaryOp,
|
|
NativeType,
|
|
FunctionRef,
|
|
ExpressionId,
|
|
FunctionTypeRef
|
|
} from "./module";
|
|
|
|
import {
|
|
Program,
|
|
ClassPrototype,
|
|
Class,
|
|
Element,
|
|
ElementKind,
|
|
Enum,
|
|
Field,
|
|
FunctionPrototype,
|
|
Function,
|
|
FunctionTarget,
|
|
Global,
|
|
Local,
|
|
Namespace,
|
|
EnumValue,
|
|
Property,
|
|
VariableLikeElement,
|
|
FlowFlags,
|
|
CommonFlags,
|
|
ConstantValueKind,
|
|
|
|
PATH_DELIMITER,
|
|
LIBRARY_PREFIX
|
|
} from "./program";
|
|
|
|
import {
|
|
Token,
|
|
operatorTokenToString
|
|
} from "./tokenizer";
|
|
|
|
import {
|
|
Node,
|
|
NodeKind,
|
|
TypeNode,
|
|
Source,
|
|
Range,
|
|
|
|
Statement,
|
|
BlockStatement,
|
|
BreakStatement,
|
|
ClassDeclaration,
|
|
ContinueStatement,
|
|
DoStatement,
|
|
EmptyStatement,
|
|
EnumDeclaration,
|
|
ExportStatement,
|
|
ExpressionStatement,
|
|
FunctionDeclaration,
|
|
ForStatement,
|
|
IfStatement,
|
|
ImportStatement,
|
|
InterfaceDeclaration,
|
|
NamespaceDeclaration,
|
|
ReturnStatement,
|
|
SwitchStatement,
|
|
ThrowStatement,
|
|
TryStatement,
|
|
VariableDeclaration,
|
|
VariableStatement,
|
|
VoidStatement,
|
|
WhileStatement,
|
|
|
|
Expression,
|
|
AssertionExpression,
|
|
BinaryExpression,
|
|
CallExpression,
|
|
CommaExpression,
|
|
ElementAccessExpression,
|
|
FloatLiteralExpression,
|
|
FunctionExpression,
|
|
IdentifierExpression,
|
|
IntegerLiteralExpression,
|
|
LiteralExpression,
|
|
LiteralKind,
|
|
NewExpression,
|
|
ParenthesizedExpression,
|
|
PropertyAccessExpression,
|
|
TernaryExpression,
|
|
ArrayLiteralExpression,
|
|
StringLiteralExpression,
|
|
UnaryPostfixExpression,
|
|
UnaryPrefixExpression
|
|
} from "./ast";
|
|
|
|
import {
|
|
Type,
|
|
TypeKind,
|
|
TypeFlags,
|
|
Signature,
|
|
|
|
typesToNativeTypes
|
|
} from "./types";
|
|
|
|
/** Compilation target. */
|
|
export enum Target {
|
|
/** WebAssembly with 32-bit pointers. */
|
|
WASM32,
|
|
/** WebAssembly with 64-bit pointers. Experimental and not supported by any runtime yet. */
|
|
WASM64
|
|
}
|
|
|
|
/** Compiler options. */
|
|
export class Options {
|
|
|
|
/** WebAssembly target. Defaults to {@link Target.WASM32}. */
|
|
target: Target = Target.WASM32;
|
|
/** If true, compiles everything instead of just reachable code. */
|
|
noTreeShaking: bool = false;
|
|
/** If true, replaces assertions with nops. */
|
|
noAssert: bool = false;
|
|
/** If true, does not set up a memory. */
|
|
noMemory: bool = false;
|
|
/** If true, imports the memory provided by the embedder. */
|
|
importMemory: bool = false;
|
|
/** Static memory start offset. */
|
|
memoryBase: u32 = 0;
|
|
/** Memory allocation implementation to use. */
|
|
allocateImpl: string = "allocate_memory";
|
|
/** Memory freeing implementation to use. */
|
|
freeImpl: string = "free_memory";
|
|
/** If true, generates information necessary for source maps. */
|
|
sourceMap: bool = false;
|
|
|
|
/** Tests if the target is WASM64 or, otherwise, WASM32. */
|
|
get isWasm64(): bool {
|
|
return this.target == Target.WASM64;
|
|
}
|
|
|
|
/** Gets the unsigned size type matching the target. */
|
|
get usizeType(): Type {
|
|
return this.target == Target.WASM64 ? Type.usize64 : Type.usize32;
|
|
}
|
|
|
|
/** Gets the signed size type matching the target. */
|
|
get isizeType(): Type {
|
|
return this.target == Target.WASM64 ? Type.isize64 : Type.isize32;
|
|
}
|
|
|
|
/** Gets the native size type matching the target. */
|
|
get nativeSizeType(): NativeType {
|
|
return this.target == Target.WASM64 ? NativeType.I64 : NativeType.I32;
|
|
}
|
|
}
|
|
|
|
/** Indicates the desired kind of a conversion. */
|
|
export const enum ConversionKind {
|
|
/** No conversion. */
|
|
NONE,
|
|
/** Implicit conversion. */
|
|
IMPLICIT,
|
|
/** Explicit conversion. */
|
|
EXPLICIT
|
|
}
|
|
|
|
/** Compiler interface. */
|
|
export class Compiler extends DiagnosticEmitter {
|
|
|
|
/** Program reference. */
|
|
program: Program;
|
|
/** Provided options. */
|
|
options: Options;
|
|
/** Module instance being compiled. */
|
|
module: Module;
|
|
|
|
/** Start function being compiled. */
|
|
startFunction: Function;
|
|
/** Start function statements. */
|
|
startFunctionBody: ExpressionRef[] = [];
|
|
|
|
/** Current function in compilation. */
|
|
currentFunction: Function;
|
|
/** Current enum in compilation. */
|
|
currentEnum: Enum | null = null;
|
|
/** Current type in compilation. */
|
|
currentType: Type = Type.void;
|
|
|
|
/** Counting memory offset. */
|
|
memoryOffset: I64;
|
|
/** Memory segments being compiled. */
|
|
memorySegments: MemorySegment[] = new Array();
|
|
/** Map of already compiled static string segments. */
|
|
stringSegments: Map<string,MemorySegment> = new Map();
|
|
|
|
/** Function table being compiled. */
|
|
functionTable: Function[] = new Array();
|
|
|
|
/** Already processed file names. */
|
|
files: Set<string> = new Set();
|
|
|
|
/** Compiles a {@link Program} to a {@link Module} using the specified options. */
|
|
static compile(program: Program, options: Options | null = null): Module {
|
|
return new Compiler(program, options).compile();
|
|
}
|
|
|
|
/** Constructs a new compiler for a {@link Program} using the specified options. */
|
|
constructor(program: Program, options: Options | null = null) {
|
|
super(program.diagnostics);
|
|
this.program = program;
|
|
if (!options) options = new Options();
|
|
this.options = options;
|
|
this.memoryOffset = i64_new(
|
|
max(options.memoryBase, options.usizeType.byteSize) // leave space for `null`
|
|
);
|
|
this.module = Module.create();
|
|
}
|
|
|
|
/** Performs compilation of the underlying {@link Program} to a {@link Module}. */
|
|
compile(): Module {
|
|
var options = this.options;
|
|
var module = this.module;
|
|
var program = this.program;
|
|
|
|
// initialize lookup maps, built-ins, imports, exports, etc.
|
|
program.initialize(options);
|
|
|
|
// set up the start function wrapping top-level statements, of all files.
|
|
var startFunctionPrototype = assert(program.elementsLookup.get("start"));
|
|
assert(startFunctionPrototype.kind == ElementKind.FUNCTION_PROTOTYPE);
|
|
var startFunctionInstance = new Function(
|
|
<FunctionPrototype>startFunctionPrototype,
|
|
startFunctionPrototype.internalName,
|
|
new Signature([], Type.void)
|
|
);
|
|
this.startFunction = startFunctionInstance;
|
|
this.currentFunction = startFunctionInstance;
|
|
|
|
// compile entry file(s) while traversing to reachable elements
|
|
var sources = program.sources;
|
|
for (let i = 0, k = sources.length; i < k; ++i) {
|
|
if (sources[i].isEntry) {
|
|
this.compileSource(sources[i]);
|
|
}
|
|
}
|
|
|
|
// compile the start function if not empty
|
|
var startFunctionBody = this.startFunctionBody;
|
|
if (startFunctionBody.length) {
|
|
let typeRef = this.ensureFunctionType(startFunctionInstance.signature);
|
|
let funcRef: FunctionRef;
|
|
module.setStart(
|
|
funcRef = module.addFunction(
|
|
startFunctionInstance.internalName,
|
|
typeRef,
|
|
typesToNativeTypes(startFunctionInstance.additionalLocals),
|
|
module.createBlock(null, startFunctionBody)
|
|
)
|
|
);
|
|
startFunctionInstance.finalize(module, funcRef);
|
|
}
|
|
|
|
// set up static memory segments and the heap base pointer
|
|
if (!options.noMemory) {
|
|
let memoryOffset = this.memoryOffset;
|
|
memoryOffset = i64_align(memoryOffset, options.usizeType.byteSize);
|
|
this.memoryOffset = memoryOffset;
|
|
if (options.isWasm64) {
|
|
module.addGlobal(
|
|
"HEAP_BASE",
|
|
NativeType.I64,
|
|
false,
|
|
module.createI64(i64_low(memoryOffset), i64_high(memoryOffset))
|
|
);
|
|
} else {
|
|
module.addGlobal(
|
|
"HEAP_BASE",
|
|
NativeType.I32,
|
|
false,
|
|
module.createI32(i64_low(memoryOffset))
|
|
);
|
|
}
|
|
|
|
// determine initial page size
|
|
let pages = i64_shr_u(i64_align(memoryOffset, 0x10000), i64_new(16, 0));
|
|
module.setMemory(
|
|
i64_low(pages),
|
|
Module.MAX_MEMORY_WASM32, // TODO: not WASM64 compatible yet
|
|
this.memorySegments,
|
|
options.target,
|
|
"memory"
|
|
);
|
|
}
|
|
|
|
// import memory if requested
|
|
if (options.importMemory) {
|
|
module.addMemoryImport("0", "env", "memory");
|
|
}
|
|
|
|
// set up function table
|
|
var functionTable = this.functionTable;
|
|
var functionTableSize = functionTable.length;
|
|
if (functionTableSize) {
|
|
let entries = new Array<FunctionRef>(functionTableSize);
|
|
for (let i = 0; i < functionTableSize; ++i) {
|
|
entries[i] = functionTable[i].ref;
|
|
}
|
|
module.setFunctionTable(entries);
|
|
}
|
|
|
|
return module;
|
|
}
|
|
|
|
// sources
|
|
|
|
compileSourceByPath(normalizedPathWithoutExtension: string, reportNode: Node): void {
|
|
var sources = this.program.sources;
|
|
|
|
// try file.ts
|
|
var expected = normalizedPathWithoutExtension + ".ts";
|
|
for (let i = 0, k = sources.length; i < k; ++i) {
|
|
let source = sources[i];
|
|
if (source.normalizedPath == expected) {
|
|
this.compileSource(source);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// try file/index.ts
|
|
expected = normalizedPathWithoutExtension + "/index.ts";
|
|
for (let i = 0, k = sources.length; i < k; ++i) {
|
|
let source = sources[i];
|
|
if (source.normalizedPath == expected) {
|
|
this.compileSource(source);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// try (lib)/file.ts
|
|
expected = LIBRARY_PREFIX + normalizedPathWithoutExtension + ".ts";
|
|
for (let i = 0, k = sources.length; i < k; ++i) {
|
|
let source = sources[i];
|
|
if (source.normalizedPath == expected) {
|
|
this.compileSource(source);
|
|
return;
|
|
}
|
|
}
|
|
|
|
this.error(
|
|
DiagnosticCode.File_0_not_found,
|
|
reportNode.range, normalizedPathWithoutExtension
|
|
);
|
|
}
|
|
|
|
compileSource(source: Source): void {
|
|
var files = this.files;
|
|
var normalizedPath = source.normalizedPath;
|
|
if (files.has(normalizedPath)) return;
|
|
files.add(normalizedPath);
|
|
|
|
// compile top-level statements
|
|
var noTreeShaking = this.options.noTreeShaking;
|
|
var isEntry = source.isEntry;
|
|
var startFunction = this.startFunction;
|
|
var startFunctionBody = this.startFunctionBody;
|
|
var statements = source.statements;
|
|
for (let i = 0, k = statements.length; i < k; ++i) {
|
|
let statement = statements[i];
|
|
switch (statement.kind) {
|
|
case NodeKind.CLASSDECLARATION: {
|
|
if (
|
|
(noTreeShaking || (isEntry && statement.is(CommonFlags.EXPORT))) &&
|
|
!(<ClassDeclaration>statement).isGeneric
|
|
) {
|
|
this.compileClassDeclaration(<ClassDeclaration>statement, []);
|
|
}
|
|
break;
|
|
}
|
|
case NodeKind.ENUMDECLARATION: {
|
|
if (noTreeShaking || (isEntry && statement.is(CommonFlags.EXPORT))) {
|
|
this.compileEnumDeclaration(<EnumDeclaration>statement);
|
|
}
|
|
break;
|
|
}
|
|
case NodeKind.FUNCTIONDECLARATION: {
|
|
if (
|
|
(noTreeShaking || (isEntry && statement.is(CommonFlags.EXPORT))) &&
|
|
!(<FunctionDeclaration>statement).isGeneric
|
|
) {
|
|
this.compileFunctionDeclaration(<FunctionDeclaration>statement, []);
|
|
}
|
|
break;
|
|
}
|
|
case NodeKind.IMPORT: {
|
|
this.compileSourceByPath(
|
|
(<ImportStatement>statement).normalizedPath,
|
|
(<ImportStatement>statement).path
|
|
);
|
|
break;
|
|
}
|
|
case NodeKind.NAMESPACEDECLARATION: {
|
|
if (noTreeShaking || (isEntry && statement.is(CommonFlags.EXPORT))) {
|
|
this.compileNamespaceDeclaration(<NamespaceDeclaration>statement);
|
|
}
|
|
break;
|
|
}
|
|
case NodeKind.VARIABLE: { // global, always compiled as initializers might have side effects
|
|
let variableInit = this.compileVariableStatement(<VariableStatement>statement);
|
|
if (variableInit) startFunctionBody.push(variableInit);
|
|
break;
|
|
}
|
|
case NodeKind.EXPORT: {
|
|
if ((<ExportStatement>statement).normalizedPath != null) {
|
|
this.compileSourceByPath(
|
|
<string>(<ExportStatement>statement).normalizedPath,
|
|
<StringLiteralExpression>(<ExportStatement>statement).path
|
|
);
|
|
}
|
|
if (noTreeShaking || isEntry) {
|
|
this.compileExportStatement(<ExportStatement>statement);
|
|
}
|
|
break;
|
|
}
|
|
default: { // otherwise a top-level statement that is part of the start function's body
|
|
let previousFunction = this.currentFunction;
|
|
this.currentFunction = startFunction;
|
|
startFunctionBody.push(this.compileStatement(statement));
|
|
this.currentFunction = previousFunction;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// globals
|
|
|
|
compileGlobalDeclaration(declaration: VariableDeclaration): Global | null {
|
|
// look up the initialized program element
|
|
var element = assert(this.program.elementsLookup.get(declaration.fileLevelInternalName));
|
|
assert(element.kind == ElementKind.GLOBAL);
|
|
if (!this.compileGlobal(<Global>element)) return null; // reports
|
|
return <Global>element;
|
|
}
|
|
|
|
compileGlobal(global: Global): bool {
|
|
if (global.is(CommonFlags.COMPILED) || global.is(CommonFlags.BUILTIN)) return true;
|
|
global.set(CommonFlags.COMPILED); // ^ built-ins are compiled on use
|
|
|
|
var module = this.module;
|
|
var declaration = global.declaration;
|
|
var initExpr: ExpressionRef = 0;
|
|
|
|
if (global.type == Type.void) { // type is void if not yet resolved or not annotated
|
|
|
|
// resolve now if annotated
|
|
if (declaration.type) {
|
|
let resolvedType = this.program.resolveType(declaration.type); // reports
|
|
if (!resolvedType) return false;
|
|
if (resolvedType == Type.void) {
|
|
this.error(
|
|
DiagnosticCode.Type_expected,
|
|
declaration.type.range
|
|
);
|
|
return false;
|
|
}
|
|
global.type = resolvedType;
|
|
|
|
// infer from initializer if not annotated
|
|
} else if (declaration.initializer) { // infer type using void/NONE for literal inference
|
|
initExpr = this.compileExpression( // reports
|
|
declaration.initializer,
|
|
Type.void,
|
|
ConversionKind.NONE
|
|
);
|
|
if (this.currentType == Type.void) {
|
|
this.error(
|
|
DiagnosticCode.Type_0_is_not_assignable_to_type_1,
|
|
declaration.initializer.range, this.currentType.toString(), "<auto>"
|
|
);
|
|
return false;
|
|
}
|
|
global.type = this.currentType;
|
|
|
|
// must either be annotated or have an initializer
|
|
} else {
|
|
this.error(
|
|
DiagnosticCode.Type_expected,
|
|
declaration.name.range.atEnd
|
|
);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
var nativeType = global.type.toNativeType();
|
|
|
|
// handle imports
|
|
if (global.is(CommonFlags.DECLARE)) {
|
|
|
|
// constant global
|
|
if (global.is(CommonFlags.CONST)) {
|
|
global.set(CommonFlags.MODULE_IMPORT);
|
|
module.addGlobalImport(
|
|
global.internalName,
|
|
global.namespace
|
|
? global.namespace.simpleName
|
|
: "env",
|
|
global.simpleName,
|
|
nativeType
|
|
);
|
|
global.set(CommonFlags.COMPILED);
|
|
return true;
|
|
|
|
// importing mutable globals is not supported in the MVP
|
|
} else {
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
declaration.range
|
|
);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// the MVP does not yet support initializer expressions other than constant values (and
|
|
// get_globals), hence such initializations must be performed in the start function for now.
|
|
var initializeInStart = false;
|
|
|
|
// inlined constant can be compiled as-is
|
|
if (global.is(CommonFlags.INLINED)) {
|
|
initExpr = this.compileInlineConstant(global, global.type, true);
|
|
|
|
} else {
|
|
|
|
// evaluate initializer if present
|
|
if (declaration.initializer) {
|
|
if (!initExpr) {
|
|
initExpr = this.compileExpression(declaration.initializer, global.type);
|
|
}
|
|
|
|
// check if the initializer is constant
|
|
if (_BinaryenExpressionGetId(initExpr) != ExpressionId.Const) {
|
|
|
|
// if a constant global, check if the initializer becomes constant after precompute
|
|
if (global.is(CommonFlags.CONST)) {
|
|
initExpr = this.precomputeExpressionRef(initExpr);
|
|
if (_BinaryenExpressionGetId(initExpr) != ExpressionId.Const) {
|
|
this.warning(
|
|
DiagnosticCode.Compiling_constant_with_non_constant_initializer_as_mutable,
|
|
declaration.range
|
|
);
|
|
initializeInStart = true;
|
|
}
|
|
} else {
|
|
initializeInStart = true;
|
|
}
|
|
}
|
|
|
|
// initialize to zero if there's no initializer
|
|
} else {
|
|
initExpr = global.type.toNativeZero(module);
|
|
}
|
|
}
|
|
|
|
var internalName = global.internalName;
|
|
|
|
if (initializeInStart) { // initialize to mutable zero and set the actual value in start
|
|
module.addGlobal(internalName, nativeType, true, global.type.toNativeZero(module));
|
|
this.startFunctionBody.push(module.createSetGlobal(internalName, initExpr));
|
|
|
|
} else { // compile as-is
|
|
|
|
if (global.is(CommonFlags.CONST)) {
|
|
let exprType = _BinaryenExpressionGetType(initExpr);
|
|
switch (exprType) {
|
|
case NativeType.I32: {
|
|
global.constantValueKind = ConstantValueKind.INTEGER;
|
|
global.constantIntegerValue = i64_new(_BinaryenConstGetValueI32(initExpr), 0);
|
|
break;
|
|
}
|
|
case NativeType.I64: {
|
|
global.constantValueKind = ConstantValueKind.INTEGER;
|
|
global.constantIntegerValue = i64_new(
|
|
_BinaryenConstGetValueI64Low(initExpr),
|
|
_BinaryenConstGetValueI64High(initExpr)
|
|
);
|
|
break;
|
|
}
|
|
case NativeType.F32: {
|
|
global.constantValueKind = ConstantValueKind.FLOAT;
|
|
global.constantFloatValue = _BinaryenConstGetValueF32(initExpr);
|
|
break;
|
|
}
|
|
case NativeType.F64: {
|
|
global.constantValueKind = ConstantValueKind.FLOAT;
|
|
global.constantFloatValue = _BinaryenConstGetValueF64(initExpr);
|
|
break;
|
|
}
|
|
default: {
|
|
assert(false);
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
global.declaration.range
|
|
);
|
|
global.constantValueKind = ConstantValueKind.INTEGER;
|
|
global.constantIntegerValue = i64_new(0);
|
|
break;
|
|
}
|
|
}
|
|
global.set(CommonFlags.INLINED); // inline the value from now on
|
|
if (declaration.isTopLevel) { // but keep the element if it might be re-exported
|
|
module.addGlobal(internalName, nativeType, false, initExpr);
|
|
}
|
|
if (declaration.range.source.isEntry && declaration.isTopLevelExport) {
|
|
module.addGlobalExport(global.internalName, declaration.programLevelInternalName);
|
|
}
|
|
|
|
} else /* mutable */ {
|
|
module.addGlobal(internalName, nativeType, !global.is(CommonFlags.CONST), initExpr);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// enums
|
|
|
|
compileEnumDeclaration(declaration: EnumDeclaration): Enum | null {
|
|
var element = assert(this.program.elementsLookup.get(declaration.fileLevelInternalName));
|
|
assert(element.kind == ElementKind.ENUM);
|
|
if (!this.compileEnum(<Enum>element)) return null;
|
|
return <Enum>element;
|
|
}
|
|
|
|
compileEnum(element: Enum): bool {
|
|
if (element.is(CommonFlags.COMPILED)) return true;
|
|
element.set(CommonFlags.COMPILED);
|
|
|
|
var module = this.module;
|
|
this.currentEnum = element;
|
|
var previousValue: EnumValue | null = null;
|
|
|
|
if (element.members) {
|
|
for (let member of element.members.values()) {
|
|
if (member.kind != ElementKind.ENUMVALUE) continue; // happens if an enum is also a namespace
|
|
let initInStart = false;
|
|
let val = <EnumValue>member;
|
|
let valueDeclaration = val.declaration;
|
|
val.set(CommonFlags.COMPILED);
|
|
if (val.is(CommonFlags.INLINED)) {
|
|
if (element.declaration.isTopLevelExport) {
|
|
module.addGlobal(
|
|
val.internalName,
|
|
NativeType.I32,
|
|
false, // constant
|
|
module.createI32(val.constantValue)
|
|
);
|
|
}
|
|
} else {
|
|
let initExpr: ExpressionRef;
|
|
if (valueDeclaration.value) {
|
|
initExpr = this.compileExpression(<Expression>valueDeclaration.value, Type.i32);
|
|
if (_BinaryenExpressionGetId(initExpr) != ExpressionId.Const) {
|
|
initExpr = this.precomputeExpressionRef(initExpr);
|
|
if (_BinaryenExpressionGetId(initExpr) != ExpressionId.Const) {
|
|
if (element.is(CommonFlags.CONST)) {
|
|
this.warning(
|
|
DiagnosticCode.Compiling_constant_with_non_constant_initializer_as_mutable,
|
|
valueDeclaration.range
|
|
);
|
|
}
|
|
initInStart = true;
|
|
}
|
|
}
|
|
} else if (previousValue == null) {
|
|
initExpr = module.createI32(0);
|
|
} else if (previousValue.is(CommonFlags.INLINED)) {
|
|
initExpr = module.createI32(previousValue.constantValue + 1);
|
|
} else {
|
|
// in TypeScript this errors with TS1061, but actually we can do:
|
|
initExpr = module.createBinary(BinaryOp.AddI32,
|
|
module.createGetGlobal(previousValue.internalName, NativeType.I32),
|
|
module.createI32(1)
|
|
);
|
|
if (element.is(CommonFlags.CONST)) {
|
|
this.warning(
|
|
DiagnosticCode.Compiling_constant_with_non_constant_initializer_as_mutable,
|
|
valueDeclaration.range
|
|
);
|
|
}
|
|
initInStart = true;
|
|
}
|
|
if (initInStart) {
|
|
module.addGlobal(
|
|
val.internalName,
|
|
NativeType.I32,
|
|
true, // mutable
|
|
module.createI32(0)
|
|
);
|
|
this.startFunctionBody.push(module.createSetGlobal(val.internalName, initExpr));
|
|
} else {
|
|
module.addGlobal(val.internalName, NativeType.I32, false, initExpr);
|
|
if (_BinaryenExpressionGetType(initExpr) == NativeType.I32) {
|
|
val.constantValue = _BinaryenConstGetValueI32(initExpr);
|
|
val.set(CommonFlags.INLINED);
|
|
} else {
|
|
assert(false);
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
valueDeclaration.range
|
|
);
|
|
val.constantValue = 0;
|
|
}
|
|
}
|
|
}
|
|
previousValue = <EnumValue>val;
|
|
|
|
// export values if the enum is exported
|
|
if (element.declaration.range.source.isEntry && element.declaration.isTopLevelExport) {
|
|
if (member.is(CommonFlags.INLINED)) {
|
|
module.addGlobalExport(member.internalName, member.internalName);
|
|
} else if (valueDeclaration) {
|
|
this.warning(
|
|
DiagnosticCode.Cannot_export_a_mutable_global,
|
|
valueDeclaration.range
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
this.currentEnum = null;
|
|
return true;
|
|
}
|
|
|
|
// functions
|
|
|
|
/** Compiles a function given its declaration. */
|
|
compileFunctionDeclaration(
|
|
declaration: FunctionDeclaration,
|
|
typeArguments: TypeNode[],
|
|
contextualTypeArguments: Map<string,Type> | null = null
|
|
): Function | null {
|
|
var element = assert(this.program.elementsLookup.get(declaration.fileLevelInternalName));
|
|
assert(element.kind == ElementKind.FUNCTION_PROTOTYPE);
|
|
return this.compileFunctionUsingTypeArguments( // reports
|
|
<FunctionPrototype>element,
|
|
typeArguments,
|
|
contextualTypeArguments,
|
|
(<FunctionPrototype>element).declaration.name
|
|
);
|
|
}
|
|
|
|
/** Resolves the specified type arguments prior to compiling the resulting function instance. */
|
|
compileFunctionUsingTypeArguments(
|
|
prototype: FunctionPrototype,
|
|
typeArguments: TypeNode[],
|
|
contextualTypeArguments: Map<string,Type> | null,
|
|
reportNode: Node
|
|
): Function | null {
|
|
var instance = prototype.resolveUsingTypeArguments( // reports
|
|
typeArguments,
|
|
contextualTypeArguments,
|
|
reportNode
|
|
);
|
|
if (!(instance && this.compileFunction(instance))) return null;
|
|
return instance;
|
|
}
|
|
|
|
/** Either reuses or creates the function type matching the specified signature. */
|
|
private ensureFunctionType(signature: Signature): FunctionTypeRef {
|
|
var parameters = signature.parameterTypes;
|
|
var numParameters = parameters.length;
|
|
var thisType = signature.thisType;
|
|
var paramTypes: NativeType[];
|
|
var index = 0;
|
|
if (thisType) {
|
|
paramTypes = new Array(1 + numParameters);
|
|
paramTypes[0] = thisType.toNativeType();
|
|
index = 1;
|
|
} else {
|
|
paramTypes = new Array(numParameters);
|
|
}
|
|
for (let i = 0; i < numParameters; ++i, ++index) {
|
|
paramTypes[index] = signature.parameterTypes[i].toNativeType();
|
|
}
|
|
var resultType = signature.returnType.toNativeType();
|
|
var module = this.module;
|
|
var typeRef = module.getFunctionTypeBySignature(resultType, paramTypes);
|
|
if (!typeRef) {
|
|
typeRef = module.addFunctionType(signature.toSignatureString(), resultType, paramTypes);
|
|
}
|
|
return typeRef;
|
|
}
|
|
|
|
/** Compiles a readily resolved function instance. */
|
|
compileFunction(instance: Function): bool {
|
|
if (instance.is(CommonFlags.COMPILED)) return true;
|
|
assert(!instance.is(CommonFlags.BUILTIN) || instance.simpleName == "abort");
|
|
instance.set(CommonFlags.COMPILED);
|
|
|
|
// check that modifiers are matching but still compile as-is
|
|
var declaration = instance.prototype.declaration;
|
|
var body = declaration.body;
|
|
if (body) {
|
|
if (instance.is(CommonFlags.DECLARE)) {
|
|
this.error(
|
|
DiagnosticCode.An_implementation_cannot_be_declared_in_ambient_contexts,
|
|
declaration.name.range
|
|
);
|
|
}
|
|
} else {
|
|
if (!instance.is(CommonFlags.DECLARE)) {
|
|
this.error(
|
|
DiagnosticCode.Function_implementation_is_missing_or_not_immediately_following_the_declaration,
|
|
declaration.name.range
|
|
);
|
|
}
|
|
}
|
|
|
|
var ref: FunctionRef;
|
|
var typeRef = this.ensureFunctionType(instance.signature);
|
|
var module = this.module;
|
|
if (body) {
|
|
|
|
// compile body
|
|
let previousFunction = this.currentFunction;
|
|
this.currentFunction = instance;
|
|
let stmt = this.compileStatement(body);
|
|
|
|
// make sure all branches return
|
|
let allBranchesReturn = instance.flow.finalize();
|
|
let returnType = instance.signature.returnType;
|
|
if (returnType != Type.void && !allBranchesReturn) {
|
|
this.error(
|
|
DiagnosticCode.A_function_whose_declared_type_is_not_void_must_return_a_value,
|
|
assert(declaration.signature.returnType, "return type expected").range
|
|
);
|
|
}
|
|
this.currentFunction = previousFunction;
|
|
|
|
// create the function
|
|
ref = module.addFunction(
|
|
instance.internalName,
|
|
typeRef,
|
|
typesToNativeTypes(instance.additionalLocals),
|
|
stmt
|
|
);
|
|
|
|
} else {
|
|
instance.set(CommonFlags.MODULE_IMPORT);
|
|
|
|
// create the function import
|
|
let namespace = instance.prototype.namespace;
|
|
ref = module.addFunctionImport(
|
|
instance.internalName,
|
|
namespace
|
|
? namespace.simpleName
|
|
: "env",
|
|
instance.simpleName,
|
|
typeRef
|
|
);
|
|
}
|
|
|
|
// check module-level export
|
|
if (declaration.range.source.isEntry && declaration.isTopLevelExport) {
|
|
module.addFunctionExport(instance.internalName, declaration.name.text);
|
|
}
|
|
|
|
instance.finalize(module, ref);
|
|
return true;
|
|
}
|
|
|
|
// namespaces
|
|
|
|
compileNamespaceDeclaration(declaration: NamespaceDeclaration): void {
|
|
var members = declaration.members;
|
|
var noTreeShaking = this.options.noTreeShaking;
|
|
for (let i = 0, k = members.length; i < k; ++i) {
|
|
let member = members[i];
|
|
switch (member.kind) {
|
|
case NodeKind.CLASSDECLARATION: {
|
|
if (
|
|
(noTreeShaking || member.is(CommonFlags.EXPORT)) &&
|
|
!(<ClassDeclaration>member).isGeneric
|
|
) {
|
|
this.compileClassDeclaration(<ClassDeclaration>member, []);
|
|
}
|
|
break;
|
|
}
|
|
case NodeKind.INTERFACEDECLARATION: {
|
|
if (
|
|
(noTreeShaking || member.is(CommonFlags.EXPORT)) &&
|
|
!(<InterfaceDeclaration>member).isGeneric
|
|
) {
|
|
this.compileInterfaceDeclaration(<InterfaceDeclaration>member, []);
|
|
}
|
|
break;
|
|
}
|
|
case NodeKind.ENUMDECLARATION: {
|
|
if (noTreeShaking || member.is(CommonFlags.EXPORT)) {
|
|
this.compileEnumDeclaration(<EnumDeclaration>member);
|
|
}
|
|
break;
|
|
}
|
|
case NodeKind.FUNCTIONDECLARATION: {
|
|
if (
|
|
(noTreeShaking || member.is(CommonFlags.EXPORT)) &&
|
|
!(<FunctionDeclaration>member).isGeneric
|
|
) {
|
|
this.compileFunctionDeclaration(<FunctionDeclaration>member, []);
|
|
}
|
|
break;
|
|
}
|
|
case NodeKind.NAMESPACEDECLARATION: {
|
|
if (noTreeShaking || member.is(CommonFlags.EXPORT)) {
|
|
this.compileNamespaceDeclaration(<NamespaceDeclaration>member);
|
|
}
|
|
break;
|
|
}
|
|
case NodeKind.VARIABLE: {
|
|
if (noTreeShaking || member.is(CommonFlags.EXPORT)) {
|
|
let variableInit = this.compileVariableStatement(<VariableStatement>member, true);
|
|
if (variableInit) this.startFunctionBody.push(variableInit);
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
assert(false);
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
member.range
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
compileNamespace(ns: Namespace): void {
|
|
if (!ns.members) return;
|
|
|
|
var noTreeShaking = this.options.noTreeShaking;
|
|
for (let element of ns.members.values()) {
|
|
switch (element.kind) {
|
|
case ElementKind.CLASS_PROTOTYPE: {
|
|
if (
|
|
(
|
|
noTreeShaking ||
|
|
(<ClassPrototype>element).is(CommonFlags.EXPORT)
|
|
) && !(<ClassPrototype>element).is(CommonFlags.GENERIC)
|
|
) {
|
|
this.compileClassUsingTypeArguments(<ClassPrototype>element, []);
|
|
}
|
|
break;
|
|
}
|
|
case ElementKind.ENUM: {
|
|
this.compileEnum(<Enum>element);
|
|
break;
|
|
}
|
|
case ElementKind.FUNCTION_PROTOTYPE: {
|
|
if (
|
|
(
|
|
noTreeShaking || (<FunctionPrototype>element).is(CommonFlags.EXPORT)
|
|
) && !(<FunctionPrototype>element).is(CommonFlags.GENERIC)
|
|
) {
|
|
this.compileFunctionUsingTypeArguments(
|
|
<FunctionPrototype>element,
|
|
[],
|
|
null,
|
|
(<FunctionPrototype>element).declaration.name
|
|
);
|
|
}
|
|
break;
|
|
}
|
|
case ElementKind.GLOBAL: {
|
|
this.compileGlobal(<Global>element);
|
|
break;
|
|
}
|
|
case ElementKind.NAMESPACE: {
|
|
this.compileNamespace(<Namespace>element);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// exports
|
|
|
|
compileExportStatement(statement: ExportStatement): void {
|
|
var module = this.module;
|
|
var exports = this.program.fileLevelExports;
|
|
var members = statement.members;
|
|
for (let i = 0, k = members.length; i < k; ++i) {
|
|
let member = members[i];
|
|
let internalExportName = (
|
|
statement.range.source.internalPath +
|
|
PATH_DELIMITER +
|
|
member.externalName.text
|
|
);
|
|
let element = exports.get(internalExportName);
|
|
if (!element) continue; // reported in Program#initialize
|
|
switch (element.kind) {
|
|
case ElementKind.CLASS_PROTOTYPE: {
|
|
if (!(<ClassPrototype>element).is(CommonFlags.GENERIC)) {
|
|
this.compileClassUsingTypeArguments(<ClassPrototype>element, []);
|
|
}
|
|
break;
|
|
}
|
|
case ElementKind.ENUM: {
|
|
this.compileEnum(<Enum>element);
|
|
break;
|
|
}
|
|
case ElementKind.FUNCTION_PROTOTYPE: {
|
|
if (
|
|
!(<FunctionPrototype>element).is(CommonFlags.GENERIC) &&
|
|
statement.range.source.isEntry
|
|
) {
|
|
let functionInstance = this.compileFunctionUsingTypeArguments(
|
|
<FunctionPrototype>element,
|
|
[],
|
|
null,
|
|
(<FunctionPrototype>element).declaration.name
|
|
);
|
|
if (functionInstance) {
|
|
let functionDeclaration = functionInstance.prototype.declaration;
|
|
if (functionDeclaration && functionDeclaration.needsExplicitExport(member)) {
|
|
module.addFunctionExport(functionInstance.internalName, member.externalName.text);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case ElementKind.GLOBAL: {
|
|
if (this.compileGlobal(<Global>element) && statement.range.source.isEntry) {
|
|
let globalDeclaration = (<Global>element).declaration;
|
|
if (globalDeclaration && globalDeclaration.needsExplicitExport(member)) {
|
|
if ((<Global>element).is(CommonFlags.INLINED)) {
|
|
module.addGlobalExport(element.internalName, member.externalName.text);
|
|
} else {
|
|
this.warning(
|
|
DiagnosticCode.Cannot_export_a_mutable_global,
|
|
member.range
|
|
);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case ElementKind.NAMESPACE: {
|
|
this.compileNamespace(<Namespace>element);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// classes
|
|
|
|
compileClassDeclaration(
|
|
declaration: ClassDeclaration,
|
|
typeArguments: TypeNode[],
|
|
contextualTypeArguments: Map<string,Type> | null = null,
|
|
alternativeReportNode: Node | null = null
|
|
): void {
|
|
var element = assert(this.program.elementsLookup.get(declaration.fileLevelInternalName));
|
|
assert(element.kind == ElementKind.CLASS_PROTOTYPE);
|
|
this.compileClassUsingTypeArguments(
|
|
<ClassPrototype>element,
|
|
typeArguments,
|
|
contextualTypeArguments,
|
|
alternativeReportNode
|
|
);
|
|
}
|
|
|
|
compileClassUsingTypeArguments(
|
|
prototype: ClassPrototype,
|
|
typeArguments: TypeNode[],
|
|
contextualTypeArguments: Map<string,Type> | null = null,
|
|
alternativeReportNode: Node | null = null
|
|
): void {
|
|
var instance = prototype.resolveUsingTypeArguments( // reports
|
|
typeArguments,
|
|
contextualTypeArguments,
|
|
alternativeReportNode
|
|
);
|
|
if (!instance) return;
|
|
this.compileClass(instance);
|
|
}
|
|
|
|
compileClass(instance: Class): bool {
|
|
if (instance.is(CommonFlags.COMPILED)) return true;
|
|
instance.set(CommonFlags.COMPILED);
|
|
return true;
|
|
}
|
|
|
|
compileInterfaceDeclaration(
|
|
declaration: InterfaceDeclaration,
|
|
typeArguments: TypeNode[],
|
|
contextualTypeArguments: Map<string,Type> | null = null,
|
|
alternativeReportNode: Node | null = null
|
|
): void {
|
|
// TODO
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
declaration.range
|
|
);
|
|
}
|
|
|
|
// memory
|
|
|
|
/** Adds a static memory segment with the specified data. */
|
|
addMemorySegment(buffer: Uint8Array, alignment: i32 = 8): MemorySegment {
|
|
var memoryOffset = i64_align(this.memoryOffset, alignment);
|
|
var segment = MemorySegment.create(buffer, memoryOffset);
|
|
this.memorySegments.push(segment);
|
|
this.memoryOffset = i64_add(memoryOffset, i64_new(buffer.length, 0));
|
|
return segment;
|
|
}
|
|
|
|
// function table
|
|
|
|
/** Ensures that a table entry exists for the specified function and returns its index. */
|
|
ensureFunctionTableEntry(func: Function): i32 {
|
|
assert(func.is(CommonFlags.COMPILED));
|
|
if (func.functionTableIndex >= 0) {
|
|
return func.functionTableIndex;
|
|
}
|
|
var functionTable = this.functionTable;
|
|
var index = functionTable.length;
|
|
functionTable.push(func);
|
|
func.functionTableIndex = index;
|
|
return index;
|
|
}
|
|
|
|
// statements
|
|
|
|
compileStatement(statement: Statement): ExpressionRef {
|
|
var module = this.module;
|
|
var expr: ExpressionRef;
|
|
switch (statement.kind) {
|
|
case NodeKind.BLOCK: {
|
|
expr = this.compileBlockStatement(<BlockStatement>statement);
|
|
break;
|
|
}
|
|
case NodeKind.BREAK: {
|
|
expr = this.compileBreakStatement(<BreakStatement>statement);
|
|
break;
|
|
}
|
|
case NodeKind.CONTINUE: {
|
|
expr = this.compileContinueStatement(<ContinueStatement>statement);
|
|
break;
|
|
}
|
|
case NodeKind.DO: {
|
|
expr = this.compileDoStatement(<DoStatement>statement);
|
|
break;
|
|
}
|
|
case NodeKind.EMPTY: {
|
|
expr = this.compileEmptyStatement(<EmptyStatement>statement);
|
|
break;
|
|
}
|
|
case NodeKind.EXPRESSION: {
|
|
expr = this.compileExpressionStatement(<ExpressionStatement>statement);
|
|
break;
|
|
}
|
|
case NodeKind.FOR: {
|
|
expr = this.compileForStatement(<ForStatement>statement);
|
|
break;
|
|
}
|
|
case NodeKind.IF: {
|
|
expr = this.compileIfStatement(<IfStatement>statement);
|
|
break;
|
|
}
|
|
case NodeKind.RETURN: {
|
|
expr = this.compileReturnStatement(<ReturnStatement>statement);
|
|
break;
|
|
}
|
|
case NodeKind.SWITCH: {
|
|
expr = this.compileSwitchStatement(<SwitchStatement>statement);
|
|
break;
|
|
}
|
|
case NodeKind.THROW: {
|
|
expr = this.compileThrowStatement(<ThrowStatement>statement);
|
|
break;
|
|
}
|
|
case NodeKind.TRY: {
|
|
expr = this.compileTryStatement(<TryStatement>statement);
|
|
break;
|
|
}
|
|
case NodeKind.VARIABLE: {
|
|
expr = this.compileVariableStatement(<VariableStatement>statement);
|
|
if (!expr) expr = module.createNop();
|
|
break;
|
|
}
|
|
case NodeKind.VOID: {
|
|
expr = this.compileVoidStatement(<VoidStatement>statement);
|
|
break;
|
|
}
|
|
case NodeKind.WHILE: {
|
|
expr = this.compileWhileStatement(<WhileStatement>statement);
|
|
break;
|
|
}
|
|
case NodeKind.TYPEDECLARATION: {
|
|
// type declarations must be top-level because function bodies are evaluated when
|
|
// reachaable only.
|
|
if (this.currentFunction == this.startFunction) {
|
|
return module.createNop();
|
|
}
|
|
// otherwise fall-through
|
|
}
|
|
default: {
|
|
assert(false);
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
statement.range
|
|
);
|
|
expr = module.createUnreachable();
|
|
break;
|
|
}
|
|
}
|
|
if (this.options.sourceMap) {
|
|
addDebugLocation(expr, statement.range, module, this.currentFunction);
|
|
}
|
|
return expr;
|
|
}
|
|
|
|
compileStatements(statements: Statement[]): ExpressionRef[] {
|
|
var numStatements = statements.length;
|
|
var stmts = new Array<ExpressionRef>(numStatements);
|
|
for (let i = 0; i < numStatements; ++i) {
|
|
stmts[i] = this.compileStatement(statements[i]);
|
|
}
|
|
return stmts; // array of 0-es in noEmit-mode
|
|
}
|
|
|
|
compileBlockStatement(statement: BlockStatement): ExpressionRef {
|
|
var statements = statement.statements;
|
|
|
|
// NOTE that we could optimize this to a NOP if empty or unwrap a single
|
|
// statement, but that's not what the source told us to do and left to the
|
|
// optimizer.
|
|
|
|
// Not actually a branch, but can contain its own scoped variables.
|
|
var flow = this.currentFunction.flow.enterBranchOrScope();
|
|
this.currentFunction.flow = flow;
|
|
|
|
var stmt = this.module.createBlock(null, this.compileStatements(statements), NativeType.None);
|
|
var stmtReturns = flow.is(FlowFlags.RETURNS);
|
|
|
|
// Switch back to the parent flow
|
|
flow = flow.leaveBranchOrScope();
|
|
this.currentFunction.flow = flow;
|
|
if (stmtReturns) {
|
|
flow.set(FlowFlags.RETURNS);
|
|
}
|
|
return stmt;
|
|
}
|
|
|
|
compileBreakStatement(statement: BreakStatement): ExpressionRef {
|
|
var module = this.module;
|
|
if (statement.label) {
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
statement.label.range
|
|
);
|
|
return module.createUnreachable();
|
|
}
|
|
var flow = this.currentFunction.flow;
|
|
var breakLabel = flow.breakLabel;
|
|
if (breakLabel == null) {
|
|
this.error(
|
|
DiagnosticCode.A_break_statement_can_only_be_used_within_an_enclosing_iteration_or_switch_statement,
|
|
statement.range
|
|
);
|
|
return module.createUnreachable();
|
|
}
|
|
flow.set(FlowFlags.POSSIBLY_BREAKS);
|
|
return module.createBreak(breakLabel);
|
|
}
|
|
|
|
compileContinueStatement(statement: ContinueStatement): ExpressionRef {
|
|
var module = this.module;
|
|
var label = statement.label;
|
|
if (label) {
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
label.range
|
|
);
|
|
return module.createUnreachable();
|
|
}
|
|
// Check if 'continue' is allowed here
|
|
var flow = this.currentFunction.flow;
|
|
var continueLabel = flow.continueLabel;
|
|
if (continueLabel == null) {
|
|
this.error(
|
|
DiagnosticCode.A_continue_statement_can_only_be_used_within_an_enclosing_iteration_statement,
|
|
statement.range
|
|
);
|
|
return module.createUnreachable();
|
|
}
|
|
flow.set(FlowFlags.POSSIBLY_CONTINUES);
|
|
return module.createBreak(continueLabel);
|
|
}
|
|
|
|
compileDoStatement(statement: DoStatement): ExpressionRef {
|
|
|
|
// A do statement does not initiate a new branch because it is executed at
|
|
// least once, but has its own break and continue labels.
|
|
var currentFunction = this.currentFunction;
|
|
var label = currentFunction.enterBreakContext();
|
|
var flow = currentFunction.flow;
|
|
var previousBreakLabel = flow.breakLabel;
|
|
var previousContinueLabel = flow.continueLabel;
|
|
|
|
var breakLabel = "break|" + label;
|
|
flow.breakLabel = breakLabel;
|
|
var continueLabel = "continue|" + label;
|
|
flow.continueLabel = continueLabel;
|
|
|
|
var body = this.compileStatement(statement.statement);
|
|
|
|
// Reset to the previous break and continue labels, if any.
|
|
flow.breakLabel = previousBreakLabel;
|
|
flow.continueLabel = previousContinueLabel;
|
|
|
|
var module = this.module;
|
|
var condExpr = makeIsTrueish(
|
|
this.compileExpression(statement.condition, Type.i32, ConversionKind.NONE),
|
|
this.currentType,
|
|
module
|
|
);
|
|
|
|
// No need to eliminate the condition in generic contexts as the statement is executed anyway.
|
|
|
|
this.currentFunction.leaveBreakContext();
|
|
|
|
return module.createBlock(breakLabel, [
|
|
module.createLoop(continueLabel,
|
|
module.createBlock(null, [
|
|
body,
|
|
module.createBreak(continueLabel, condExpr)
|
|
], NativeType.None))
|
|
], NativeType.None);
|
|
}
|
|
|
|
compileEmptyStatement(statement: EmptyStatement): ExpressionRef {
|
|
return this.module.createNop();
|
|
}
|
|
|
|
compileExpressionStatement(statement: ExpressionStatement): ExpressionRef {
|
|
var expr = this.compileExpression(statement.expression, Type.void, ConversionKind.NONE);
|
|
if (this.currentType != Type.void) {
|
|
expr = this.module.createDrop(expr);
|
|
this.currentType = Type.void;
|
|
}
|
|
return expr;
|
|
}
|
|
|
|
compileForStatement(statement: ForStatement): ExpressionRef {
|
|
|
|
// A for statement initiates a new branch with its own scoped variables
|
|
// possibly declared in its initializer, and break context.
|
|
var currentFunction = this.currentFunction;
|
|
var context = currentFunction.enterBreakContext();
|
|
var flow = currentFunction.flow.enterBranchOrScope();
|
|
currentFunction.flow = flow;
|
|
var breakLabel = flow.breakLabel = "break|" + context;
|
|
flow.breakLabel = breakLabel;
|
|
var continueLabel = "continue|" + context;
|
|
flow.continueLabel = continueLabel;
|
|
|
|
// Compile in correct order
|
|
var module = this.module;
|
|
var initializer = statement.initializer
|
|
? this.compileStatement(<Statement>statement.initializer)
|
|
: module.createNop();
|
|
var condition = statement.condition
|
|
? this.compileExpression(<Expression>statement.condition, Type.i32)
|
|
: module.createI32(1);
|
|
var incrementor = statement.incrementor
|
|
? this.compileExpression(<Expression>statement.incrementor, Type.void)
|
|
: module.createNop();
|
|
var body = this.compileStatement(statement.statement);
|
|
var alwaysReturns = !statement.condition && flow.is(FlowFlags.RETURNS);
|
|
// TODO: check other always-true conditions as well, not just omitted
|
|
|
|
// Switch back to the parent flow
|
|
flow = flow.leaveBranchOrScope();
|
|
currentFunction.flow = flow;
|
|
currentFunction.leaveBreakContext();
|
|
|
|
var expr = module.createBlock(breakLabel, [
|
|
initializer,
|
|
module.createLoop(continueLabel, module.createBlock(null, [
|
|
module.createIf(condition, module.createBlock(null, [
|
|
body,
|
|
incrementor,
|
|
module.createBreak(continueLabel)
|
|
], NativeType.None))
|
|
], NativeType.None))
|
|
], NativeType.None);
|
|
|
|
// If the loop is guaranteed to run and return, propagate that and append a hint
|
|
if (alwaysReturns) {
|
|
flow.set(FlowFlags.RETURNS);
|
|
expr = module.createBlock(null, [
|
|
expr,
|
|
module.createUnreachable()
|
|
]);
|
|
}
|
|
return expr;
|
|
}
|
|
|
|
compileIfStatement(statement: IfStatement): ExpressionRef {
|
|
var module = this.module;
|
|
var currentFunction = this.currentFunction;
|
|
var ifTrue = statement.ifTrue;
|
|
var ifFalse = statement.ifFalse;
|
|
|
|
// The condition doesn't initiate a branch yet
|
|
var condExpr = makeIsTrueish(
|
|
this.compileExpression(statement.condition, Type.i32, ConversionKind.NONE),
|
|
this.currentType,
|
|
module
|
|
);
|
|
|
|
// Eliminate unnecesssary branches in generic contexts if the condition is constant
|
|
if (
|
|
this.currentFunction.isAny(CommonFlags.GENERIC | CommonFlags.GENERIC_CONTEXT) &&
|
|
_BinaryenExpressionGetId(condExpr = this.precomputeExpressionRef(condExpr)) == ExpressionId.Const &&
|
|
_BinaryenExpressionGetType(condExpr) == NativeType.I32
|
|
) {
|
|
let ret: ExpressionRef;
|
|
if (_BinaryenConstGetValueI32(condExpr)) {
|
|
ret = this.compileStatement(ifTrue);
|
|
} else if (ifFalse) {
|
|
ret = this.compileStatement(ifFalse);
|
|
} else {
|
|
ret = module.createNop();
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
// Each arm initiates a branch
|
|
var flow = currentFunction.flow.enterBranchOrScope();
|
|
currentFunction.flow = flow;
|
|
var ifTrueExpr = this.compileStatement(ifTrue);
|
|
var ifTrueReturns = flow.is(FlowFlags.RETURNS);
|
|
flow = flow.leaveBranchOrScope();
|
|
currentFunction.flow = flow;
|
|
|
|
var ifFalseExpr: ExpressionRef = 0;
|
|
var ifFalseReturns = false;
|
|
if (ifFalse) {
|
|
flow = flow.enterBranchOrScope();
|
|
currentFunction.flow = flow;
|
|
ifFalseExpr = this.compileStatement(ifFalse);
|
|
ifFalseReturns = flow.is(FlowFlags.RETURNS);
|
|
flow = flow.leaveBranchOrScope();
|
|
currentFunction.flow = flow;
|
|
}
|
|
if (ifTrueReturns && ifFalseReturns) { // not necessary to append a hint
|
|
flow.set(FlowFlags.RETURNS);
|
|
}
|
|
return module.createIf(condExpr, ifTrueExpr, ifFalseExpr);
|
|
}
|
|
|
|
compileReturnStatement(statement: ReturnStatement): ExpressionRef {
|
|
var currentFunction = this.currentFunction;
|
|
var expression: ExpressionRef = 0;
|
|
if (statement.value) {
|
|
expression = this.compileExpression(
|
|
statement.value,
|
|
currentFunction.signature.returnType
|
|
);
|
|
}
|
|
|
|
// Remember that this flow returns
|
|
currentFunction.flow.set(FlowFlags.RETURNS);
|
|
|
|
return this.module.createReturn(expression);
|
|
}
|
|
|
|
compileSwitchStatement(statement: SwitchStatement): ExpressionRef {
|
|
var module = this.module;
|
|
var currentFunction = this.currentFunction;
|
|
|
|
// Everything within a switch uses the same break context
|
|
var context = currentFunction.enterBreakContext();
|
|
|
|
// introduce a local for evaluating the condition (exactly once)
|
|
var tempLocal = currentFunction.getTempLocal(Type.u32);
|
|
var tempLocalIndex = tempLocal.index;
|
|
var cases = statement.cases;
|
|
var numCases = cases.length;
|
|
|
|
// Prepend initializer to inner block. Does not initiate a new branch, yet.
|
|
var breaks = new Array<ExpressionRef>(1 + numCases);
|
|
breaks[0] = module.createSetLocal( // initializer
|
|
tempLocalIndex,
|
|
this.compileExpression(statement.condition, Type.u32)
|
|
);
|
|
|
|
// make one br_if per (possibly dynamic) labeled case (binaryen optimizes to br_table where possible)
|
|
var breakIndex = 1;
|
|
var defaultIndex = -1;
|
|
for (let i = 0; i < numCases; ++i) {
|
|
let case_ = cases[i];
|
|
let label = case_.label;
|
|
if (label) {
|
|
breaks[breakIndex++] = module.createBreak("case" + i.toString(10) + "|" + context,
|
|
module.createBinary(BinaryOp.EqI32,
|
|
module.createGetLocal(tempLocalIndex, NativeType.I32),
|
|
this.compileExpression(label, Type.i32)
|
|
)
|
|
);
|
|
} else {
|
|
defaultIndex = i;
|
|
}
|
|
}
|
|
|
|
currentFunction.freeTempLocal(tempLocal);
|
|
|
|
// otherwise br to default respectively out of the switch if there is no default case
|
|
breaks[breakIndex] = module.createBreak((defaultIndex >= 0
|
|
? "case" + defaultIndex.toString(10)
|
|
: "break"
|
|
) + "|" + context);
|
|
|
|
// nest blocks in order
|
|
var currentBlock = module.createBlock("case0|" + context, breaks, NativeType.None);
|
|
var alwaysReturns = true;
|
|
for (let i = 0; i < numCases; ++i) {
|
|
let case_ = cases[i];
|
|
let statements = case_.statements;
|
|
let numStatements = statements.length;
|
|
let body = new Array<ExpressionRef>(1 + numStatements);
|
|
body[0] = currentBlock;
|
|
|
|
// Each switch case initiates a new branch
|
|
let flow = currentFunction.flow.enterBranchOrScope();
|
|
currentFunction.flow = flow;
|
|
let breakLabel = "break|" + context;
|
|
flow.breakLabel = breakLabel;
|
|
|
|
let fallsThrough = i != numCases - 1;
|
|
let nextLabel = !fallsThrough ? breakLabel : "case" + (i + 1).toString(10) + "|" + context;
|
|
for (let j = 0; j < numStatements; ++j) {
|
|
body[j + 1] = this.compileStatement(statements[j]);
|
|
}
|
|
if (!(fallsThrough || flow.is(FlowFlags.RETURNS))) {
|
|
alwaysReturns = false; // ignore fall-throughs
|
|
}
|
|
|
|
// Switch back to the parent flow
|
|
currentFunction.flow = flow.leaveBranchOrScope();
|
|
|
|
currentBlock = module.createBlock(nextLabel, body, NativeType.None);
|
|
}
|
|
currentFunction.leaveBreakContext();
|
|
|
|
// If the switch has a default and always returns, propagate that
|
|
if (defaultIndex >= 0 && alwaysReturns) {
|
|
currentFunction.flow.set(FlowFlags.RETURNS);
|
|
// Binaryen understands that so we don't need a hint
|
|
}
|
|
return currentBlock;
|
|
}
|
|
|
|
compileThrowStatement(statement: ThrowStatement): ExpressionRef {
|
|
var flow = this.currentFunction.flow;
|
|
|
|
// Remember that this branch possibly throws
|
|
flow.set(FlowFlags.POSSIBLY_THROWS);
|
|
|
|
// FIXME: without try-catch it is safe to assume RETURNS as well for now
|
|
flow.set(FlowFlags.RETURNS);
|
|
|
|
// TODO: requires exception-handling spec.
|
|
return this.module.createUnreachable();
|
|
}
|
|
|
|
compileTryStatement(statement: TryStatement): ExpressionRef {
|
|
// TODO
|
|
// can't yet support something like: try { return ... } finally { ... }
|
|
// worthwhile to investigate lowering returns to block results (here)?
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
statement.range
|
|
);
|
|
return this.module.createUnreachable();
|
|
}
|
|
|
|
/**
|
|
* Compiles a variable statement. Returns `0` if an initializer is not
|
|
* necessary.
|
|
*/
|
|
compileVariableStatement(statement: VariableStatement, isKnownGlobal: bool = false): ExpressionRef {
|
|
var program = this.program;
|
|
var currentFunction = this.currentFunction;
|
|
var declarations = statement.declarations;
|
|
var numDeclarations = declarations.length;
|
|
|
|
// top-level variables and constants become globals
|
|
if (isKnownGlobal || (
|
|
currentFunction == this.startFunction &&
|
|
statement.parent && statement.parent.kind == NodeKind.SOURCE
|
|
)) {
|
|
// NOTE that the above condition also covers top-level variables declared with 'let', even
|
|
// though such variables could also become start function locals if, and only if, not used
|
|
// within any function declared in the same source, which is unknown at this point. the only
|
|
// efficient way to deal with this would be to keep track of all occasions it is used and
|
|
// replace these instructions afterwards, dynamically. (TOOD: what about a Binaryen pass?)
|
|
for (let i = 0; i < numDeclarations; ++i) {
|
|
this.compileGlobalDeclaration(declarations[i]);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// other variables become locals
|
|
var initializers = new Array<ExpressionRef>();
|
|
for (let i = 0; i < numDeclarations; ++i) {
|
|
let declaration = declarations[i];
|
|
let name = declaration.name.text;
|
|
let type: Type | null = null;
|
|
let init: ExpressionRef = 0;
|
|
if (declaration.type) {
|
|
type = program.resolveType( // reports
|
|
declaration.type,
|
|
currentFunction.contextualTypeArguments
|
|
);
|
|
if (!type) continue;
|
|
if (declaration.initializer) {
|
|
init = this.compileExpression(declaration.initializer, type); // reports
|
|
}
|
|
} else if (declaration.initializer) { // infer type using void/NONE for proper literal inference
|
|
init = this.compileExpression( // reports
|
|
declaration.initializer,
|
|
Type.void,
|
|
ConversionKind.NONE
|
|
);
|
|
if (this.currentType == Type.void) {
|
|
this.error(
|
|
DiagnosticCode.Type_0_is_not_assignable_to_type_1,
|
|
declaration.range, this.currentType.toString(), "<auto>"
|
|
);
|
|
continue;
|
|
}
|
|
type = this.currentType;
|
|
} else {
|
|
this.error(
|
|
DiagnosticCode.Type_expected,
|
|
declaration.name.range.atEnd
|
|
);
|
|
continue;
|
|
}
|
|
if (declaration.is(CommonFlags.CONST)) {
|
|
if (init) {
|
|
init = this.precomputeExpressionRef(init);
|
|
if (_BinaryenExpressionGetId(init) == ExpressionId.Const) {
|
|
let local = new Local(program, name, -1, type);
|
|
switch (_BinaryenExpressionGetType(init)) {
|
|
case NativeType.I32: {
|
|
local = local.withConstantIntegerValue(_BinaryenConstGetValueI32(init), 0);
|
|
break;
|
|
}
|
|
case NativeType.I64: {
|
|
local = local.withConstantIntegerValue(
|
|
_BinaryenConstGetValueI64Low(init),
|
|
_BinaryenConstGetValueI64High(init)
|
|
);
|
|
break;
|
|
}
|
|
case NativeType.F32: {
|
|
local = local.withConstantFloatValue(<f64>_BinaryenConstGetValueF32(init));
|
|
break;
|
|
}
|
|
case NativeType.F64: {
|
|
local = local.withConstantFloatValue(_BinaryenConstGetValueF64(init));
|
|
break;
|
|
}
|
|
default: {
|
|
assert(false);
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
declaration.range
|
|
);
|
|
return this.module.createUnreachable();
|
|
}
|
|
}
|
|
// Create a virtual local that doesn't actually exist in WebAssembly
|
|
let scopedLocals = currentFunction.flow.scopedLocals;
|
|
if (!scopedLocals) currentFunction.flow.scopedLocals = scopedLocals = new Map();
|
|
else if (scopedLocals.has(name)) {
|
|
this.error(
|
|
DiagnosticCode.Duplicate_identifier_0,
|
|
declaration.name.range, name
|
|
);
|
|
return 0;
|
|
}
|
|
scopedLocals.set(name, local);
|
|
return 0;
|
|
} else {
|
|
this.warning(
|
|
DiagnosticCode.Compiling_constant_with_non_constant_initializer_as_mutable,
|
|
declaration.range
|
|
);
|
|
}
|
|
} else {
|
|
this.error(
|
|
DiagnosticCode._const_declarations_must_be_initialized,
|
|
declaration.range
|
|
);
|
|
}
|
|
}
|
|
if (declaration.is(CommonFlags.LET)) { // here: not top-level
|
|
currentFunction.flow.addScopedLocal(name, type, declaration.name); // reports
|
|
} else {
|
|
currentFunction.addLocal(type, name); // reports
|
|
}
|
|
if (init) {
|
|
initializers.push(this.compileAssignmentWithValue(declaration.name, init));
|
|
}
|
|
}
|
|
return initializers.length // we can unwrap these here because the
|
|
? initializers.length == 1 // source didn't tell us exactly what to do
|
|
? initializers[0]
|
|
: this.module.createBlock(null, initializers, NativeType.None)
|
|
: 0;
|
|
}
|
|
|
|
compileVoidStatement(statement: VoidStatement): ExpressionRef {
|
|
return this.compileExpression(statement.expression, Type.void, ConversionKind.EXPLICIT, false);
|
|
}
|
|
|
|
compileWhileStatement(statement: WhileStatement): ExpressionRef {
|
|
var module = this.module;
|
|
|
|
// The condition does not yet initialize a branch
|
|
var condExpr = makeIsTrueish(
|
|
this.compileExpression(statement.condition, Type.i32, ConversionKind.NONE),
|
|
this.currentType,
|
|
module
|
|
);
|
|
|
|
// Eliminate unnecesssary loops in generic contexts if the condition is constant
|
|
if (
|
|
this.currentFunction.isAny(CommonFlags.GENERIC | CommonFlags.GENERIC_CONTEXT) &&
|
|
_BinaryenExpressionGetId(condExpr = this.precomputeExpressionRef(condExpr)) == ExpressionId.Const &&
|
|
_BinaryenExpressionGetType(condExpr) == NativeType.I32
|
|
) {
|
|
if (!_BinaryenConstGetValueI32(condExpr)) {
|
|
return module.createNop();
|
|
}
|
|
}
|
|
|
|
// Statements initiate a new branch with its own break context
|
|
var currentFunction = this.currentFunction;
|
|
var label = currentFunction.enterBreakContext();
|
|
var flow = currentFunction.flow.enterBranchOrScope();
|
|
currentFunction.flow = flow;
|
|
var breakLabel = "break|" + label;
|
|
flow.breakLabel = breakLabel;
|
|
var continueLabel = "continue|" + label;
|
|
flow.continueLabel = continueLabel;
|
|
|
|
var body = this.compileStatement(statement.statement);
|
|
var alwaysReturns = false && flow.is(FlowFlags.RETURNS);
|
|
// TODO: evaluate possible always-true conditions
|
|
|
|
// Switch back to the parent flow
|
|
currentFunction.flow = flow.leaveBranchOrScope();
|
|
currentFunction.leaveBreakContext();
|
|
|
|
var expr = module.createBlock(breakLabel, [
|
|
module.createLoop(continueLabel,
|
|
module.createIf(condExpr, module.createBlock(null, [
|
|
body,
|
|
module.createBreak(continueLabel)
|
|
], NativeType.None))
|
|
)
|
|
], NativeType.None);
|
|
|
|
// If the loop is guaranteed to run and return, propagate that and append a hint
|
|
if (alwaysReturns) {
|
|
expr = module.createBlock(null, [
|
|
expr,
|
|
module.createUnreachable()
|
|
]);
|
|
}
|
|
return expr;
|
|
}
|
|
|
|
// expressions
|
|
|
|
/**
|
|
* Compiles the value of an inlined constant element.
|
|
* @param retainType If true, the annotated type of the constant is retained. Otherwise, the value
|
|
* is precomputed according to context.
|
|
*/
|
|
compileInlineConstant(
|
|
element: VariableLikeElement,
|
|
contextualType: Type,
|
|
retainType: bool
|
|
): ExpressionRef {
|
|
assert(element.is(CommonFlags.INLINED));
|
|
var type = element.type;
|
|
switch (
|
|
!retainType &&
|
|
type.is(TypeFlags.INTEGER) &&
|
|
contextualType.is(TypeFlags.INTEGER) &&
|
|
type.size < contextualType.size
|
|
? (this.currentType = contextualType).kind // essentially precomputes a (sign-)extension
|
|
: (this.currentType = type).kind
|
|
) {
|
|
case TypeKind.I8:
|
|
case TypeKind.I16: {
|
|
let shift = type.computeSmallIntegerShift(Type.i32);
|
|
return this.module.createI32(
|
|
element.constantValueKind == ConstantValueKind.INTEGER
|
|
? i64_low(element.constantIntegerValue) << shift >> shift
|
|
: 0
|
|
);
|
|
}
|
|
case TypeKind.U8:
|
|
case TypeKind.U16:
|
|
case TypeKind.BOOL: {
|
|
let mask = element.type.computeSmallIntegerMask(Type.i32);
|
|
return this.module.createI32(
|
|
element.constantValueKind == ConstantValueKind.INTEGER
|
|
? i64_low(element.constantIntegerValue) & mask
|
|
: 0
|
|
);
|
|
}
|
|
case TypeKind.I32:
|
|
case TypeKind.U32: {
|
|
return this.module.createI32(
|
|
element.constantValueKind == ConstantValueKind.INTEGER
|
|
? i64_low(element.constantIntegerValue)
|
|
: 0
|
|
);
|
|
}
|
|
case TypeKind.ISIZE:
|
|
case TypeKind.USIZE: {
|
|
if (!element.program.options.isWasm64) {
|
|
return this.module.createI32(
|
|
element.constantValueKind == ConstantValueKind.INTEGER
|
|
? i64_low(element.constantIntegerValue)
|
|
: 0
|
|
);
|
|
}
|
|
// fall-through
|
|
}
|
|
case TypeKind.I64:
|
|
case TypeKind.U64: {
|
|
return element.constantValueKind == ConstantValueKind.INTEGER
|
|
? this.module.createI64(
|
|
i64_low(element.constantIntegerValue),
|
|
i64_high(element.constantIntegerValue)
|
|
)
|
|
: this.module.createI64(0);
|
|
}
|
|
case TypeKind.F32: {
|
|
return this.module.createF32((<VariableLikeElement>element).constantFloatValue);
|
|
}
|
|
case TypeKind.F64: {
|
|
return this.module.createF64((<VariableLikeElement>element).constantFloatValue);
|
|
}
|
|
default: {
|
|
assert(false);
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
element.declaration.range
|
|
);
|
|
return this.module.createUnreachable();
|
|
}
|
|
}
|
|
}
|
|
|
|
compileExpression(
|
|
expression: Expression,
|
|
contextualType: Type,
|
|
conversionKind: ConversionKind = ConversionKind.IMPLICIT,
|
|
wrapSmallIntegers: bool = true
|
|
): ExpressionRef {
|
|
this.currentType = contextualType;
|
|
|
|
var expr: ExpressionRef;
|
|
switch (expression.kind) {
|
|
case NodeKind.ASSERTION: {
|
|
expr = this.compileAssertionExpression(<AssertionExpression>expression, contextualType);
|
|
break;
|
|
}
|
|
case NodeKind.BINARY: {
|
|
expr = this.compileBinaryExpression(<BinaryExpression>expression, contextualType, wrapSmallIntegers);
|
|
break;
|
|
}
|
|
case NodeKind.CALL: {
|
|
expr = this.compileCallExpression(<CallExpression>expression, contextualType);
|
|
break;
|
|
}
|
|
case NodeKind.COMMA: {
|
|
expr = this.compileCommaExpression(<CommaExpression>expression, contextualType);
|
|
break;
|
|
}
|
|
case NodeKind.ELEMENTACCESS: {
|
|
expr = this.compileElementAccessExpression(<ElementAccessExpression>expression, contextualType);
|
|
break;
|
|
}
|
|
case NodeKind.FUNCTION: {
|
|
expr = this.compileFunctionExpression(<FunctionExpression>expression, contextualType);
|
|
break;
|
|
}
|
|
case NodeKind.IDENTIFIER:
|
|
case NodeKind.FALSE:
|
|
case NodeKind.NULL:
|
|
case NodeKind.THIS:
|
|
case NodeKind.TRUE: {
|
|
expr = this.compileIdentifierExpression(
|
|
<IdentifierExpression>expression,
|
|
contextualType,
|
|
conversionKind == ConversionKind.NONE // retain type of inlined constants
|
|
);
|
|
break;
|
|
}
|
|
case NodeKind.LITERAL: {
|
|
expr = this.compileLiteralExpression(<LiteralExpression>expression, contextualType);
|
|
break;
|
|
}
|
|
case NodeKind.NEW: {
|
|
expr = this.compileNewExpression(<NewExpression>expression, contextualType);
|
|
break;
|
|
}
|
|
case NodeKind.PARENTHESIZED: {
|
|
expr = this.compileParenthesizedExpression(
|
|
<ParenthesizedExpression>expression,
|
|
contextualType,
|
|
wrapSmallIntegers
|
|
);
|
|
break;
|
|
}
|
|
case NodeKind.PROPERTYACCESS: {
|
|
expr = this.compilePropertyAccessExpression(
|
|
<PropertyAccessExpression>expression,
|
|
contextualType,
|
|
conversionKind == ConversionKind.NONE // retain type of inlined constants
|
|
);
|
|
break;
|
|
}
|
|
case NodeKind.TERNARY: {
|
|
expr = this.compileTernaryExpression(<TernaryExpression>expression, contextualType);
|
|
break;
|
|
}
|
|
case NodeKind.UNARYPOSTFIX: {
|
|
expr = this.compileUnaryPostfixExpression(<UnaryPostfixExpression>expression, contextualType);
|
|
break;
|
|
}
|
|
case NodeKind.UNARYPREFIX: {
|
|
expr = this.compileUnaryPrefixExpression(<UnaryPrefixExpression>expression, contextualType, wrapSmallIntegers);
|
|
break;
|
|
}
|
|
default: {
|
|
assert(false);
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
expression.range
|
|
);
|
|
expr = this.module.createUnreachable();
|
|
break;
|
|
}
|
|
}
|
|
|
|
var currentType = this.currentType;
|
|
if (conversionKind != ConversionKind.NONE && currentType != contextualType) {
|
|
expr = this.convertExpression(expr, currentType, contextualType, conversionKind, expression);
|
|
this.currentType = contextualType;
|
|
}
|
|
|
|
if (this.options.sourceMap) {
|
|
addDebugLocation(expr, expression.range, this.module, this.currentFunction);
|
|
}
|
|
return expr;
|
|
}
|
|
|
|
compileExpressionRetainType(
|
|
expression: Expression,
|
|
contextualType: Type,
|
|
wrapSmallIntegers: bool = true
|
|
): ExpressionRef {
|
|
return this.compileExpression(
|
|
expression,
|
|
contextualType == Type.void
|
|
? Type.i32
|
|
: contextualType,
|
|
ConversionKind.NONE,
|
|
wrapSmallIntegers
|
|
);
|
|
}
|
|
|
|
precomputeExpression(
|
|
expression: Expression,
|
|
contextualType: Type,
|
|
conversionKind: ConversionKind = ConversionKind.IMPLICIT
|
|
): ExpressionRef {
|
|
return this.precomputeExpressionRef(this.compileExpression(expression, contextualType, conversionKind));
|
|
}
|
|
|
|
precomputeExpressionRef(expr: ExpressionRef): ExpressionRef {
|
|
var module = this.module;
|
|
var type = this.currentType;
|
|
var nativeType = type.toNativeType();
|
|
var typeRef = module.getFunctionTypeBySignature(nativeType, null);
|
|
var typeRefAdded = false;
|
|
if (!typeRef) {
|
|
typeRef = module.addFunctionType(type.toSignatureString(), nativeType, null);
|
|
typeRefAdded = true;
|
|
}
|
|
var funcRef = module.addFunction("__precompute", typeRef, null, expr);
|
|
module.runPasses([ "precompute" ], funcRef);
|
|
var ret = _BinaryenFunctionGetBody(funcRef);
|
|
module.removeFunction("__precompute");
|
|
if (typeRefAdded) {
|
|
// TODO: also remove the function type somehow if no longer used or make the C-API accept
|
|
// a `null` typeRef, using an implicit type.
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
convertExpression(
|
|
expr: ExpressionRef,
|
|
fromType: Type,
|
|
toType: Type,
|
|
conversionKind: ConversionKind,
|
|
reportNode: Node
|
|
): ExpressionRef {
|
|
assert(conversionKind != ConversionKind.NONE);
|
|
var module = this.module;
|
|
|
|
// void to any
|
|
if (fromType.kind == TypeKind.VOID) {
|
|
this.error(
|
|
DiagnosticCode.Type_0_is_not_assignable_to_type_1,
|
|
reportNode.range, fromType.toString(), toType.toString()
|
|
);
|
|
return module.createUnreachable();
|
|
}
|
|
|
|
// any to void
|
|
if (toType.kind == TypeKind.VOID) {
|
|
return module.createDrop(expr);
|
|
}
|
|
|
|
if (conversionKind == ConversionKind.IMPLICIT && !fromType.isAssignableTo(toType)) {
|
|
this.error(
|
|
DiagnosticCode.Conversion_from_type_0_to_1_requires_an_explicit_cast,
|
|
reportNode.range, fromType.toString(), toType.toString()
|
|
); // recoverable
|
|
}
|
|
|
|
// TODO: make this a proper switch?
|
|
if (fromType.is(TypeFlags.FLOAT)) {
|
|
|
|
// float to float
|
|
if (toType.is(TypeFlags.FLOAT)) {
|
|
if (fromType.kind == TypeKind.F32) {
|
|
|
|
// f32 to f64
|
|
if (toType.kind == TypeKind.F64) {
|
|
expr = module.createUnary(UnaryOp.PromoteF32, expr);
|
|
}
|
|
|
|
// otherwise f32 to f32
|
|
|
|
// f64 to f32
|
|
} else if (toType.kind == TypeKind.F32) {
|
|
expr = module.createUnary(UnaryOp.DemoteF64, expr);
|
|
}
|
|
|
|
// otherwise f64 to f64
|
|
|
|
// float to int
|
|
} else if (toType.is(TypeFlags.INTEGER)) {
|
|
|
|
// f32 to int
|
|
if (fromType.kind == TypeKind.F32) {
|
|
if (toType.is(TypeFlags.SIGNED)) {
|
|
if (toType.is(TypeFlags.LONG)) {
|
|
expr = module.createUnary(UnaryOp.TruncF32ToI64, expr);
|
|
} else {
|
|
expr = module.createUnary(UnaryOp.TruncF32ToI32, expr);
|
|
if (toType.is(TypeFlags.SMALL)) {
|
|
expr = makeSmallIntegerWrap(expr, toType, module);
|
|
}
|
|
}
|
|
} else {
|
|
if (toType.is(TypeFlags.LONG)) {
|
|
expr = module.createUnary(UnaryOp.TruncF32ToU64, expr);
|
|
} else {
|
|
expr = module.createUnary(UnaryOp.TruncF32ToU32, expr);
|
|
if (toType.is(TypeFlags.SMALL)) {
|
|
expr = makeSmallIntegerWrap(expr, toType, module);
|
|
}
|
|
}
|
|
}
|
|
|
|
// f64 to int
|
|
} else {
|
|
if (toType.is(TypeFlags.SIGNED)) {
|
|
if (toType.is(TypeFlags.LONG)) {
|
|
expr = module.createUnary(UnaryOp.TruncF64ToI64, expr);
|
|
} else {
|
|
expr = module.createUnary(UnaryOp.TruncF64ToI32, expr);
|
|
if (toType.is(TypeFlags.SMALL)) {
|
|
expr = makeSmallIntegerWrap(expr, toType, module);
|
|
}
|
|
}
|
|
} else {
|
|
if (toType.is(TypeFlags.LONG)) {
|
|
expr = module.createUnary(UnaryOp.TruncF64ToU64, expr);
|
|
} else {
|
|
expr = module.createUnary(UnaryOp.TruncF64ToU32, expr);
|
|
if (toType.is(TypeFlags.SMALL)) {
|
|
expr = makeSmallIntegerWrap(expr, toType, module);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// float to void
|
|
} else {
|
|
assert(toType.flags == TypeFlags.NONE, "void type expected");
|
|
expr = module.createDrop(expr);
|
|
}
|
|
|
|
// int to float
|
|
} else if (fromType.is(TypeFlags.INTEGER) && toType.is(TypeFlags.FLOAT)) {
|
|
|
|
// int to f32
|
|
if (toType.kind == TypeKind.F32) {
|
|
if (fromType.is(TypeFlags.LONG)) {
|
|
expr = module.createUnary(
|
|
fromType.is(TypeFlags.SIGNED)
|
|
? UnaryOp.ConvertI64ToF32
|
|
: UnaryOp.ConvertU64ToF32,
|
|
expr
|
|
);
|
|
} else {
|
|
expr = module.createUnary(
|
|
fromType.is(TypeFlags.SIGNED)
|
|
? UnaryOp.ConvertI32ToF32
|
|
: UnaryOp.ConvertU32ToF32,
|
|
expr
|
|
);
|
|
}
|
|
|
|
// int to f64
|
|
} else {
|
|
if (fromType.is(TypeFlags.LONG)) {
|
|
expr = module.createUnary(
|
|
fromType.is(TypeFlags.SIGNED)
|
|
? UnaryOp.ConvertI64ToF64
|
|
: UnaryOp.ConvertU64ToF64,
|
|
expr
|
|
);
|
|
} else {
|
|
expr = module.createUnary(
|
|
fromType.is(TypeFlags.SIGNED)
|
|
? UnaryOp.ConvertI32ToF64
|
|
: UnaryOp.ConvertU32ToF64,
|
|
expr
|
|
);
|
|
}
|
|
}
|
|
|
|
// int to int
|
|
} else {
|
|
if (fromType.is(TypeFlags.LONG)) {
|
|
|
|
// i64 to i32
|
|
if (!toType.is(TypeFlags.LONG)) {
|
|
expr = module.createUnary(UnaryOp.WrapI64, expr); // discards upper bits
|
|
if (toType.is(TypeFlags.SMALL)) {
|
|
expr = makeSmallIntegerWrap(expr, toType, module);
|
|
}
|
|
}
|
|
|
|
// i32 to i64
|
|
} else if (toType.is(TypeFlags.LONG)) {
|
|
expr = module.createUnary(toType.is(TypeFlags.SIGNED) ? UnaryOp.ExtendI32 : UnaryOp.ExtendU32, expr);
|
|
|
|
// i32 or smaller to even smaller or same size int with change of sign
|
|
} else if (
|
|
toType.is(TypeFlags.SMALL) &&
|
|
(
|
|
fromType.size > toType.size ||
|
|
(
|
|
fromType.size == toType.size &&
|
|
fromType.is(TypeFlags.SIGNED) != toType.is(TypeFlags.SIGNED)
|
|
)
|
|
)
|
|
) {
|
|
expr = makeSmallIntegerWrap(expr, toType, module);
|
|
}
|
|
|
|
// otherwise (smaller) i32/u32 to (same size) i32/u32
|
|
}
|
|
|
|
this.currentType = toType;
|
|
return expr;
|
|
}
|
|
|
|
compileAssertionExpression(expression: AssertionExpression, contextualType: Type): ExpressionRef {
|
|
var toType = this.program.resolveType( // reports
|
|
expression.toType,
|
|
this.currentFunction.contextualTypeArguments
|
|
);
|
|
if (!toType) return this.module.createUnreachable();
|
|
return this.compileExpression(expression.expression, toType, ConversionKind.EXPLICIT);
|
|
}
|
|
|
|
compileBinaryExpression(
|
|
expression: BinaryExpression,
|
|
contextualType: Type,
|
|
wrapSmallIntegers: bool = true
|
|
): ExpressionRef {
|
|
var module = this.module;
|
|
var left = expression.left;
|
|
var right = expression.right;
|
|
|
|
var leftExpr: ExpressionRef;
|
|
var leftType: Type;
|
|
var rightExpr: ExpressionRef;
|
|
var rightType: Type;
|
|
var commonType: Type | null;
|
|
|
|
var condExpr: ExpressionRef;
|
|
var expr: ExpressionRef;
|
|
var compound = false;
|
|
var possiblyOverflows = false;
|
|
var tempLocal: Local | null = null;
|
|
|
|
switch (expression.operator) {
|
|
case Token.LESSTHAN: {
|
|
leftExpr = this.compileExpressionRetainType(left, contextualType);
|
|
leftType = this.currentType;
|
|
rightExpr = this.compileExpressionRetainType(right, leftType);
|
|
rightType = this.currentType;
|
|
if (commonType = Type.commonCompatible(leftType, rightType, true)) {
|
|
leftExpr = this.convertExpression(leftExpr, leftType, commonType, ConversionKind.IMPLICIT, left);
|
|
rightExpr = this.convertExpression(rightExpr, rightType, commonType, ConversionKind.IMPLICIT, right);
|
|
} else {
|
|
this.error(
|
|
DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2,
|
|
expression.range, "<", leftType.toString(), rightType.toString()
|
|
);
|
|
this.currentType = contextualType;
|
|
return module.createUnreachable();
|
|
}
|
|
switch (commonType.kind) {
|
|
case TypeKind.I8:
|
|
case TypeKind.I16:
|
|
case TypeKind.I32: {
|
|
expr = module.createBinary(BinaryOp.LtI32, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.I64: {
|
|
expr = module.createBinary(BinaryOp.LtI64, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.ISIZE: {
|
|
expr = module.createBinary(
|
|
this.options.isWasm64
|
|
? BinaryOp.LtI64
|
|
: BinaryOp.LtI32,
|
|
leftExpr,
|
|
rightExpr
|
|
);
|
|
break;
|
|
}
|
|
case TypeKind.U8:
|
|
case TypeKind.U16:
|
|
case TypeKind.U32:
|
|
case TypeKind.BOOL: {
|
|
expr = module.createBinary(BinaryOp.LtU32, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.USIZE: { // TODO: check operator overload
|
|
expr = module.createBinary(
|
|
this.options.isWasm64
|
|
? BinaryOp.LtU64
|
|
: BinaryOp.LtU32,
|
|
leftExpr,
|
|
rightExpr
|
|
);
|
|
break;
|
|
}
|
|
case TypeKind.U64: {
|
|
expr = module.createBinary(BinaryOp.LtU64, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.F32: {
|
|
expr = module.createBinary(BinaryOp.LtF32, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.F64: {
|
|
expr = module.createBinary(BinaryOp.LtF64, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
default: {
|
|
assert(false);
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
expression.range
|
|
);
|
|
expr = module.createUnreachable();
|
|
break;
|
|
}
|
|
}
|
|
this.currentType = Type.bool;
|
|
break;
|
|
}
|
|
case Token.GREATERTHAN: {
|
|
leftExpr = this.compileExpressionRetainType(left, contextualType);
|
|
leftType = this.currentType;
|
|
rightExpr = this.compileExpressionRetainType(right, leftType);
|
|
rightType = this.currentType;
|
|
if (commonType = Type.commonCompatible(leftType, rightType, true)) {
|
|
leftExpr = this.convertExpression(leftExpr, leftType, commonType, ConversionKind.IMPLICIT, left);
|
|
rightExpr = this.convertExpression(rightExpr, rightType, commonType, ConversionKind.IMPLICIT, right);
|
|
} else {
|
|
this.error(
|
|
DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2,
|
|
expression.range, ">", leftType.toString(), rightType.toString()
|
|
);
|
|
this.currentType = contextualType;
|
|
return module.createUnreachable();
|
|
}
|
|
switch (commonType.kind) {
|
|
case TypeKind.I8:
|
|
case TypeKind.I16:
|
|
case TypeKind.I32: {
|
|
expr = module.createBinary(BinaryOp.GtI32, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.ISIZE: {
|
|
expr = module.createBinary(
|
|
this.options.isWasm64
|
|
? BinaryOp.GtI64
|
|
: BinaryOp.GtI32,
|
|
leftExpr,
|
|
rightExpr
|
|
);
|
|
break;
|
|
}
|
|
case TypeKind.I64: {
|
|
expr = module.createBinary(BinaryOp.GtI64, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.U8:
|
|
case TypeKind.U16:
|
|
case TypeKind.U32:
|
|
case TypeKind.BOOL: {
|
|
expr = module.createBinary(BinaryOp.GtU32, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.USIZE: { // TODO: check operator overload
|
|
expr = module.createBinary(
|
|
this.options.isWasm64
|
|
? BinaryOp.GtU64
|
|
: BinaryOp.GtU32,
|
|
leftExpr,
|
|
rightExpr
|
|
);
|
|
break;
|
|
}
|
|
case TypeKind.U64: {
|
|
expr = module.createBinary(BinaryOp.GtU64, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.F32: {
|
|
expr = module.createBinary(BinaryOp.GtF32, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.F64: {
|
|
expr = module.createBinary(BinaryOp.GtF64, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
default: {
|
|
assert(false);
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
expression.range
|
|
);
|
|
expr = module.createUnreachable();
|
|
break;
|
|
}
|
|
}
|
|
this.currentType = Type.bool;
|
|
break;
|
|
}
|
|
case Token.LESSTHAN_EQUALS: {
|
|
leftExpr = this.compileExpressionRetainType(left, contextualType);
|
|
leftType = this.currentType;
|
|
rightExpr = this.compileExpressionRetainType(right, leftType);
|
|
rightType = this.currentType;
|
|
if (commonType = Type.commonCompatible(leftType, rightType, true)) {
|
|
leftExpr = this.convertExpression(leftExpr, leftType, commonType, ConversionKind.IMPLICIT, left);
|
|
rightExpr = this.convertExpression(rightExpr, rightType, commonType, ConversionKind.IMPLICIT, right);
|
|
} else {
|
|
this.error(
|
|
DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2,
|
|
expression.range, "<=", leftType.toString(), rightType.toString()
|
|
);
|
|
this.currentType = contextualType;
|
|
return module.createUnreachable();
|
|
}
|
|
switch (commonType.kind) {
|
|
case TypeKind.I8:
|
|
case TypeKind.I16:
|
|
case TypeKind.I32: {
|
|
expr = module.createBinary(BinaryOp.LeI32, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.ISIZE: {
|
|
expr = module.createBinary(
|
|
this.options.isWasm64
|
|
? BinaryOp.LeI64
|
|
: BinaryOp.LeI32,
|
|
leftExpr,
|
|
rightExpr
|
|
);
|
|
break;
|
|
}
|
|
case TypeKind.I64: {
|
|
expr = module.createBinary(BinaryOp.LeI64, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.U8:
|
|
case TypeKind.U16:
|
|
case TypeKind.U32:
|
|
case TypeKind.BOOL: {
|
|
expr = module.createBinary(BinaryOp.LeU32, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.USIZE: { // TODO: check operator overload
|
|
expr = module.createBinary(
|
|
this.options.isWasm64
|
|
? BinaryOp.LeU64
|
|
: BinaryOp.LeU32,
|
|
leftExpr,
|
|
rightExpr
|
|
);
|
|
break;
|
|
}
|
|
case TypeKind.U64: {
|
|
expr = module.createBinary(BinaryOp.LeU64, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.F32: {
|
|
expr = module.createBinary(BinaryOp.LeF32, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.F64: {
|
|
expr = module.createBinary(BinaryOp.LeF64, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
default: {
|
|
assert(false);
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
expression.range
|
|
);
|
|
expr = module.createUnreachable();
|
|
break;
|
|
}
|
|
}
|
|
this.currentType = Type.bool;
|
|
break;
|
|
}
|
|
case Token.GREATERTHAN_EQUALS: {
|
|
leftExpr = this.compileExpressionRetainType(left, contextualType);
|
|
leftType = this.currentType;
|
|
rightExpr = this.compileExpressionRetainType(right, leftType);
|
|
rightType = this.currentType;
|
|
if (commonType = Type.commonCompatible(leftType, rightType, true)) {
|
|
leftExpr = this.convertExpression(leftExpr, leftType, commonType, ConversionKind.IMPLICIT, left);
|
|
rightExpr = this.convertExpression(rightExpr, rightType, commonType, ConversionKind.IMPLICIT, right);
|
|
} else {
|
|
this.error(
|
|
DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2,
|
|
expression.range, ">=", leftType.toString(), rightType.toString()
|
|
);
|
|
this.currentType = contextualType;
|
|
return module.createUnreachable();
|
|
}
|
|
switch (commonType.kind) {
|
|
case TypeKind.I8:
|
|
case TypeKind.I16:
|
|
case TypeKind.I32: {
|
|
expr = module.createBinary(BinaryOp.GeI32, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.ISIZE: {
|
|
expr = module.createBinary(
|
|
this.options.isWasm64
|
|
? BinaryOp.GeI64
|
|
: BinaryOp.GeI32,
|
|
leftExpr,
|
|
rightExpr
|
|
);
|
|
break;
|
|
}
|
|
case TypeKind.I64: {
|
|
expr = module.createBinary(BinaryOp.GeI64, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.U8:
|
|
case TypeKind.U16:
|
|
case TypeKind.U32:
|
|
case TypeKind.BOOL: {
|
|
expr = module.createBinary(BinaryOp.GeU32, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.USIZE: { // TODO: check operator overload
|
|
expr = module.createBinary(
|
|
this.options.isWasm64
|
|
? BinaryOp.GeU64
|
|
: BinaryOp.GeU32,
|
|
leftExpr,
|
|
rightExpr
|
|
);
|
|
break;
|
|
}
|
|
case TypeKind.U64: {
|
|
expr = module.createBinary(BinaryOp.GeU64, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.F32: {
|
|
expr = module.createBinary(BinaryOp.GeF32, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.F64: {
|
|
expr = module.createBinary(BinaryOp.GeF64, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
default: {
|
|
assert(false);
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
expression.range
|
|
);
|
|
expr = module.createUnreachable();
|
|
break;
|
|
}
|
|
}
|
|
this.currentType = Type.bool;
|
|
break;
|
|
}
|
|
|
|
case Token.EQUALS_EQUALS_EQUALS:
|
|
// TODO?
|
|
case Token.EQUALS_EQUALS: {
|
|
|
|
// NOTE that this favors correctness, in terms of emitting a binary expression, over
|
|
// checking for a possible use of unary EQZ. while the most classic of all optimizations,
|
|
// that's not what the source told us to do. for reference, `!left` emits unary EQZ.
|
|
|
|
leftExpr = this.compileExpressionRetainType(left, contextualType);
|
|
leftType = this.currentType;
|
|
rightExpr = this.compileExpressionRetainType(right, leftType);
|
|
rightType = this.currentType;
|
|
if (commonType = Type.commonCompatible(leftType, rightType, false)) {
|
|
leftExpr = this.convertExpression(leftExpr, leftType, commonType, ConversionKind.IMPLICIT, left);
|
|
rightExpr = this.convertExpression(rightExpr, rightType, commonType, ConversionKind.IMPLICIT, right);
|
|
} else {
|
|
this.error(
|
|
DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2,
|
|
expression.range, operatorTokenToString(expression.operator), leftType.toString(), rightType.toString()
|
|
);
|
|
this.currentType = contextualType;
|
|
return module.createUnreachable();
|
|
}
|
|
switch (commonType.kind) {
|
|
case TypeKind.I8:
|
|
case TypeKind.I16:
|
|
case TypeKind.I32:
|
|
case TypeKind.U8:
|
|
case TypeKind.U16:
|
|
case TypeKind.U32:
|
|
case TypeKind.BOOL: {
|
|
expr = module.createBinary(BinaryOp.EqI32, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.USIZE: // TODO: check operator overload
|
|
case TypeKind.ISIZE: {
|
|
expr = module.createBinary(
|
|
this.options.isWasm64
|
|
? BinaryOp.EqI64
|
|
: BinaryOp.EqI32,
|
|
leftExpr,
|
|
rightExpr
|
|
);
|
|
break;
|
|
}
|
|
case TypeKind.I64:
|
|
case TypeKind.U64: {
|
|
expr = module.createBinary(BinaryOp.EqI64, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.F32: {
|
|
expr = module.createBinary(BinaryOp.EqF32, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.F64: {
|
|
expr = module.createBinary(BinaryOp.EqF64, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
default: {
|
|
assert(false);
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
expression.range
|
|
);
|
|
expr = module.createUnreachable();
|
|
break;
|
|
}
|
|
}
|
|
this.currentType = Type.bool;
|
|
break;
|
|
}
|
|
case Token.EXCLAMATION_EQUALS_EQUALS:
|
|
// TODO?
|
|
case Token.EXCLAMATION_EQUALS: {
|
|
leftExpr = this.compileExpressionRetainType(left, contextualType);
|
|
leftType = this.currentType;
|
|
rightExpr = this.compileExpressionRetainType(right, leftType);
|
|
rightType = this.currentType;
|
|
if (commonType = Type.commonCompatible(leftType, rightType, false)) {
|
|
leftExpr = this.convertExpression(leftExpr, leftType, commonType, ConversionKind.IMPLICIT, left);
|
|
rightExpr = this.convertExpression(rightExpr, rightType, commonType, ConversionKind.IMPLICIT, right);
|
|
} else {
|
|
this.error(
|
|
DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2,
|
|
expression.range, operatorTokenToString(expression.operator), leftType.toString(), rightType.toString()
|
|
);
|
|
this.currentType = contextualType;
|
|
return module.createUnreachable();
|
|
}
|
|
switch (commonType.kind) {
|
|
case TypeKind.I8:
|
|
case TypeKind.I16:
|
|
case TypeKind.I32:
|
|
case TypeKind.U8:
|
|
case TypeKind.U16:
|
|
case TypeKind.U32:
|
|
case TypeKind.BOOL: {
|
|
expr = module.createBinary(BinaryOp.NeI32, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.USIZE: // TODO: check operator overload
|
|
case TypeKind.ISIZE: {
|
|
expr = module.createBinary(
|
|
this.options.isWasm64
|
|
? BinaryOp.NeI64
|
|
: BinaryOp.NeI32,
|
|
leftExpr,
|
|
rightExpr
|
|
);
|
|
break;
|
|
}
|
|
case TypeKind.I64:
|
|
case TypeKind.U64: {
|
|
expr = module.createBinary(BinaryOp.NeI64, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.F32: {
|
|
expr = module.createBinary(BinaryOp.NeF32, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.F64: {
|
|
expr = module.createBinary(BinaryOp.NeF64, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
default: {
|
|
assert(false);
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
expression.range
|
|
);
|
|
expr = module.createUnreachable();
|
|
}
|
|
}
|
|
this.currentType = Type.bool;
|
|
break;
|
|
}
|
|
case Token.EQUALS: {
|
|
return this.compileAssignment(left, right, contextualType);
|
|
}
|
|
case Token.PLUS_EQUALS: compound = true;
|
|
case Token.PLUS: {
|
|
leftExpr = this.compileExpressionRetainType(
|
|
left,
|
|
contextualType,
|
|
false // retains low bits of small integers
|
|
);
|
|
if (compound) {
|
|
rightExpr = this.compileExpression(
|
|
right,
|
|
this.currentType,
|
|
ConversionKind.IMPLICIT,
|
|
false // ^
|
|
);
|
|
} else {
|
|
leftType = this.currentType;
|
|
rightExpr = this.compileExpressionRetainType(
|
|
right,
|
|
leftType,
|
|
false // ^
|
|
);
|
|
rightType = this.currentType;
|
|
if (commonType = Type.commonCompatible(leftType, rightType, false)) {
|
|
leftExpr = this.convertExpression(leftExpr, leftType, commonType, ConversionKind.IMPLICIT, left);
|
|
rightExpr = this.convertExpression(rightExpr, rightType, commonType, ConversionKind.IMPLICIT, right);
|
|
} else {
|
|
this.error(
|
|
DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2,
|
|
expression.range, "+", leftType.toString(), rightType.toString()
|
|
);
|
|
this.currentType = contextualType;
|
|
return module.createUnreachable();
|
|
}
|
|
}
|
|
switch (this.currentType.kind) {
|
|
case TypeKind.I8:
|
|
case TypeKind.I16:
|
|
case TypeKind.U8:
|
|
case TypeKind.U16:
|
|
case TypeKind.BOOL: possiblyOverflows = true;
|
|
case TypeKind.I32:
|
|
case TypeKind.U32: {
|
|
expr = module.createBinary(BinaryOp.AddI32, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.USIZE: // TODO: check operator overload
|
|
case TypeKind.ISIZE: {
|
|
expr = module.createBinary(
|
|
this.options.isWasm64
|
|
? BinaryOp.AddI64
|
|
: BinaryOp.AddI32,
|
|
leftExpr,
|
|
rightExpr
|
|
);
|
|
break;
|
|
}
|
|
case TypeKind.I64:
|
|
case TypeKind.U64: {
|
|
expr = module.createBinary(BinaryOp.AddI64, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.F32: {
|
|
expr = module.createBinary(BinaryOp.AddF32, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.F64: {
|
|
expr = module.createBinary(BinaryOp.AddF64, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
default: {
|
|
assert(false);
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
expression.range
|
|
);
|
|
expr = module.createUnreachable();
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case Token.MINUS_EQUALS: compound = true;
|
|
case Token.MINUS: {
|
|
leftExpr = this.compileExpressionRetainType(
|
|
left,
|
|
contextualType,
|
|
false // retains low bits of small integers
|
|
);
|
|
if (compound) {
|
|
rightExpr = this.compileExpression(
|
|
right,
|
|
this.currentType,
|
|
ConversionKind.IMPLICIT,
|
|
false // ^
|
|
);
|
|
} else {
|
|
leftType = this.currentType;
|
|
rightExpr = this.compileExpressionRetainType(
|
|
right,
|
|
leftType,
|
|
false // ^
|
|
);
|
|
rightType = this.currentType;
|
|
if (commonType = Type.commonCompatible(leftType, rightType, false)) {
|
|
leftExpr = this.convertExpression(leftExpr, leftType, commonType, ConversionKind.IMPLICIT, left);
|
|
rightExpr = this.convertExpression(rightExpr, rightType, commonType, ConversionKind.IMPLICIT, right);
|
|
} else {
|
|
this.error(
|
|
DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2,
|
|
expression.range, "-", leftType.toString(), rightType.toString()
|
|
);
|
|
this.currentType = contextualType;
|
|
return module.createUnreachable();
|
|
}
|
|
}
|
|
switch (this.currentType.kind) {
|
|
case TypeKind.I8:
|
|
case TypeKind.I16:
|
|
case TypeKind.U8:
|
|
case TypeKind.U16:
|
|
case TypeKind.BOOL: possiblyOverflows = true;
|
|
case TypeKind.I32:
|
|
case TypeKind.U32: {
|
|
expr = module.createBinary(BinaryOp.SubI32, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.USIZE: // TODO: check operator overload
|
|
case TypeKind.ISIZE: {
|
|
expr = module.createBinary(
|
|
this.options.isWasm64
|
|
? BinaryOp.SubI64
|
|
: BinaryOp.SubI32,
|
|
leftExpr,
|
|
rightExpr
|
|
);
|
|
break;
|
|
}
|
|
case TypeKind.I64:
|
|
case TypeKind.U64: {
|
|
expr = module.createBinary(BinaryOp.SubI64, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.F32: {
|
|
expr = module.createBinary(BinaryOp.SubF32, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.F64: {
|
|
expr = module.createBinary(BinaryOp.SubF64, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
default: {
|
|
assert(false);
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
expression.range
|
|
);
|
|
expr = module.createUnreachable();
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case Token.ASTERISK_EQUALS: compound = true;
|
|
case Token.ASTERISK: {
|
|
leftExpr = this.compileExpressionRetainType(
|
|
left,
|
|
contextualType,
|
|
false // retains low bits of small integers
|
|
);
|
|
if (compound) {
|
|
rightExpr = this.compileExpression(
|
|
right,
|
|
this.currentType,
|
|
ConversionKind.IMPLICIT,
|
|
false // ^
|
|
);
|
|
} else {
|
|
leftType = this.currentType;
|
|
rightExpr = this.compileExpressionRetainType(
|
|
right,
|
|
leftType,
|
|
false // ^
|
|
);
|
|
rightType = this.currentType;
|
|
if (commonType = Type.commonCompatible(leftType, rightType, false)) {
|
|
leftExpr = this.convertExpression(leftExpr, leftType, commonType, ConversionKind.IMPLICIT, left);
|
|
rightExpr = this.convertExpression(rightExpr, rightType, commonType, ConversionKind.IMPLICIT, right);
|
|
} else {
|
|
this.error(
|
|
DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2,
|
|
expression.range, "*", leftType.toString(), rightType.toString()
|
|
);
|
|
this.currentType = contextualType;
|
|
return module.createUnreachable();
|
|
}
|
|
}
|
|
switch (this.currentType.kind) {
|
|
case TypeKind.I8:
|
|
case TypeKind.I16:
|
|
case TypeKind.U8:
|
|
case TypeKind.U16:
|
|
case TypeKind.BOOL: possiblyOverflows = true;
|
|
case TypeKind.I32:
|
|
case TypeKind.U32: {
|
|
expr = module.createBinary(BinaryOp.MulI32, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.USIZE: // TODO: check operator overload
|
|
case TypeKind.ISIZE: {
|
|
expr = module.createBinary(
|
|
this.options.isWasm64
|
|
? BinaryOp.MulI64
|
|
: BinaryOp.MulI32,
|
|
leftExpr,
|
|
rightExpr
|
|
);
|
|
break;
|
|
}
|
|
case TypeKind.I64:
|
|
case TypeKind.U64: {
|
|
expr = module.createBinary(BinaryOp.MulI64, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.F32: {
|
|
expr = module.createBinary(BinaryOp.MulF32, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.F64: {
|
|
expr = module.createBinary(BinaryOp.MulF64, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
default: {
|
|
assert(false);
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
expression.range
|
|
);
|
|
expr = module.createUnreachable();
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case Token.SLASH_EQUALS: compound = true;
|
|
case Token.SLASH: {
|
|
leftExpr = this.compileExpressionRetainType(
|
|
left,
|
|
contextualType,
|
|
true // TODO: when can division remain unwrapped? does it overflow?
|
|
);
|
|
if (compound) {
|
|
rightExpr = this.compileExpression(
|
|
right,
|
|
this.currentType,
|
|
ConversionKind.IMPLICIT,
|
|
false // ^
|
|
);
|
|
} else {
|
|
leftType = this.currentType;
|
|
rightExpr = this.compileExpressionRetainType(
|
|
right,
|
|
leftType,
|
|
false // ^
|
|
);
|
|
rightType = this.currentType;
|
|
if (commonType = Type.commonCompatible(leftType, rightType, false)) {
|
|
leftExpr = this.convertExpression(leftExpr, leftType, commonType, ConversionKind.IMPLICIT, left);
|
|
rightExpr = this.convertExpression(rightExpr, rightType, commonType, ConversionKind.IMPLICIT, right);
|
|
} else {
|
|
this.error(
|
|
DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2,
|
|
expression.range, "/", leftType.toString(), rightType.toString()
|
|
);
|
|
this.currentType = contextualType;
|
|
return module.createUnreachable();
|
|
}
|
|
}
|
|
switch (this.currentType.kind) {
|
|
case TypeKind.I8:
|
|
case TypeKind.I16: possiblyOverflows = true;
|
|
case TypeKind.I32: {
|
|
expr = module.createBinary(BinaryOp.DivI32, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.ISIZE: {
|
|
expr = module.createBinary(
|
|
this.options.isWasm64
|
|
? BinaryOp.DivI64
|
|
: BinaryOp.DivI32,
|
|
leftExpr,
|
|
rightExpr
|
|
);
|
|
break;
|
|
}
|
|
case TypeKind.I64: {
|
|
expr = module.createBinary(BinaryOp.DivI64, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.U8:
|
|
case TypeKind.U16:
|
|
case TypeKind.BOOL: possiblyOverflows = true;
|
|
case TypeKind.U32: {
|
|
expr = module.createBinary(BinaryOp.DivU32, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.USIZE: { // TODO: check operator overload
|
|
expr = module.createBinary(
|
|
this.options.isWasm64
|
|
? BinaryOp.DivU64
|
|
: BinaryOp.DivU32,
|
|
leftExpr,
|
|
rightExpr
|
|
);
|
|
break;
|
|
}
|
|
case TypeKind.U64: {
|
|
expr = module.createBinary(BinaryOp.DivU64, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.F32: {
|
|
expr = module.createBinary(BinaryOp.DivF32, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.F64: {
|
|
expr = module.createBinary(BinaryOp.DivF64, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
default: {
|
|
assert(false);
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
expression.range
|
|
);
|
|
expr = module.createUnreachable();
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case Token.PERCENT_EQUALS: compound = true;
|
|
case Token.PERCENT: {
|
|
leftExpr = this.compileExpressionRetainType(
|
|
left,
|
|
contextualType,
|
|
true // TODO: when can remainder remain unwrapped? does it overflow?
|
|
);
|
|
if (compound) {
|
|
rightExpr = this.compileExpression(
|
|
right,
|
|
this.currentType,
|
|
ConversionKind.IMPLICIT,
|
|
false // ^
|
|
);
|
|
} else {
|
|
leftType = this.currentType;
|
|
rightExpr = this.compileExpressionRetainType(
|
|
right,
|
|
leftType,
|
|
false // ^
|
|
);
|
|
rightType = this.currentType;
|
|
if (commonType = Type.commonCompatible(leftType, rightType, false)) {
|
|
leftExpr = this.convertExpression(leftExpr, leftType, commonType, ConversionKind.IMPLICIT, left);
|
|
rightExpr = this.convertExpression(rightExpr, rightType, commonType, ConversionKind.IMPLICIT, right);
|
|
} else {
|
|
this.error(
|
|
DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2,
|
|
expression.range, "%", leftType.toString(), rightType.toString()
|
|
);
|
|
this.currentType = contextualType;
|
|
return module.createUnreachable();
|
|
}
|
|
}
|
|
switch (this.currentType.kind) {
|
|
case TypeKind.I8:
|
|
case TypeKind.I16:
|
|
case TypeKind.I32: {
|
|
expr = module.createBinary(BinaryOp.RemI32, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.ISIZE: {
|
|
expr = module.createBinary(
|
|
this.options.isWasm64
|
|
? BinaryOp.RemI64
|
|
: BinaryOp.RemI32,
|
|
leftExpr,
|
|
rightExpr
|
|
);
|
|
break;
|
|
}
|
|
case TypeKind.I64: {
|
|
expr = module.createBinary(BinaryOp.RemI64, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.U8:
|
|
case TypeKind.U16:
|
|
case TypeKind.U32:
|
|
case TypeKind.BOOL: {
|
|
expr = module.createBinary(BinaryOp.RemU32, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.USIZE: { // TODO: check operator overload
|
|
expr = module.createBinary(
|
|
this.options.isWasm64
|
|
? BinaryOp.RemU64
|
|
: BinaryOp.RemU32,
|
|
leftExpr,
|
|
rightExpr
|
|
);
|
|
break;
|
|
}
|
|
case TypeKind.U64: {
|
|
expr = module.createBinary(BinaryOp.RemU64, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.F32:
|
|
case TypeKind.F64: {
|
|
// TODO: internal fmod, possibly simply imported from JS
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
expression.range
|
|
);
|
|
expr = module.createUnreachable();
|
|
break;
|
|
}
|
|
default: {
|
|
assert(false);
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
expression.range
|
|
);
|
|
expr = module.createUnreachable();
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case Token.LESSTHAN_LESSTHAN_EQUALS: compound = true;
|
|
case Token.LESSTHAN_LESSTHAN: {
|
|
leftExpr = this.compileExpressionRetainType(
|
|
left,
|
|
contextualType,
|
|
false // retains low bits of small integers
|
|
);
|
|
rightExpr = this.compileExpression(
|
|
right,
|
|
this.currentType,
|
|
ConversionKind.IMPLICIT,
|
|
false // ^
|
|
);
|
|
switch (this.currentType.kind) {
|
|
case TypeKind.I8:
|
|
case TypeKind.I16:
|
|
case TypeKind.U8:
|
|
case TypeKind.U16:
|
|
case TypeKind.BOOL: possiblyOverflows = true;
|
|
default: {
|
|
expr = module.createBinary(BinaryOp.ShlI32, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.I64:
|
|
case TypeKind.U64: {
|
|
expr = module.createBinary(BinaryOp.ShlI64, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.USIZE: // TODO: check operator overload
|
|
case TypeKind.ISIZE: {
|
|
expr = module.createBinary(
|
|
this.options.isWasm64
|
|
? BinaryOp.ShlI64
|
|
: BinaryOp.ShlI32,
|
|
leftExpr,
|
|
rightExpr
|
|
);
|
|
break;
|
|
}
|
|
case TypeKind.F32:
|
|
case TypeKind.F64: {
|
|
this.error(
|
|
DiagnosticCode.The_0_operator_cannot_be_applied_to_type_1,
|
|
expression.range, operatorTokenToString(expression.operator), this.currentType.toString()
|
|
);
|
|
return module.createUnreachable();
|
|
}
|
|
case TypeKind.VOID: {
|
|
assert(false);
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
expression.range
|
|
);
|
|
expr = module.createUnreachable();
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case Token.GREATERTHAN_GREATERTHAN_EQUALS: compound = true;
|
|
case Token.GREATERTHAN_GREATERTHAN: {
|
|
leftExpr = this.compileExpressionRetainType(
|
|
left,
|
|
contextualType,
|
|
true // must wrap small integers
|
|
);
|
|
rightExpr = this.compileExpression(
|
|
right,
|
|
this.currentType,
|
|
ConversionKind.IMPLICIT,
|
|
true // ^
|
|
);
|
|
switch (this.currentType.kind) {
|
|
default: {
|
|
// assumes signed shr on signed small integers does not overflow
|
|
expr = module.createBinary(BinaryOp.ShrI32, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.I64: {
|
|
expr = module.createBinary(BinaryOp.ShrI64, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.ISIZE: {
|
|
expr = module.createBinary(
|
|
this.options.isWasm64
|
|
? BinaryOp.ShrI64
|
|
: BinaryOp.ShrI32,
|
|
leftExpr,
|
|
rightExpr
|
|
);
|
|
break;
|
|
}
|
|
case TypeKind.U8:
|
|
case TypeKind.U16:
|
|
case TypeKind.BOOL: // assumes unsigned shr on unsigned small integers does not overflow
|
|
case TypeKind.U32: {
|
|
expr = module.createBinary(BinaryOp.ShrU32, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.U64: {
|
|
expr = module.createBinary(BinaryOp.ShrU64, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.USIZE: { // TODO: check operator overload
|
|
expr = module.createBinary(
|
|
this.options.isWasm64
|
|
? BinaryOp.ShrU64
|
|
: BinaryOp.ShrU32,
|
|
leftExpr,
|
|
rightExpr
|
|
);
|
|
break;
|
|
}
|
|
case TypeKind.F32:
|
|
case TypeKind.F64: {
|
|
this.error(
|
|
DiagnosticCode.The_0_operator_cannot_be_applied_to_type_1,
|
|
expression.range, operatorTokenToString(expression.operator), this.currentType.toString()
|
|
);
|
|
return module.createUnreachable();
|
|
}
|
|
case TypeKind.VOID: {
|
|
assert(false);
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
expression.range
|
|
);
|
|
expr = module.createUnreachable();
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case Token.GREATERTHAN_GREATERTHAN_GREATERTHAN_EQUALS: compound = true;
|
|
case Token.GREATERTHAN_GREATERTHAN_GREATERTHAN: {
|
|
leftExpr = this.compileExpressionRetainType(
|
|
left,
|
|
contextualType,
|
|
true // modifies low bits of small integers if unsigned
|
|
);
|
|
rightExpr = this.compileExpression(
|
|
right,
|
|
this.currentType,
|
|
ConversionKind.IMPLICIT,
|
|
true // ^
|
|
);
|
|
switch (this.currentType.kind) {
|
|
case TypeKind.I8:
|
|
case TypeKind.I16: possiblyOverflows = true;
|
|
default: {
|
|
// assumes that unsigned shr on unsigned small integers does not overflow
|
|
expr = module.createBinary(BinaryOp.ShrU32, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.I64:
|
|
case TypeKind.U64: {
|
|
expr = module.createBinary(BinaryOp.ShrU64, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.USIZE: // TODO: check operator overload
|
|
case TypeKind.ISIZE: {
|
|
expr = module.createBinary(
|
|
this.options.isWasm64
|
|
? BinaryOp.ShrU64
|
|
: BinaryOp.ShrU32,
|
|
leftExpr,
|
|
rightExpr
|
|
);
|
|
break;
|
|
}
|
|
case TypeKind.VOID: {
|
|
assert(false);
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
expression.range
|
|
);
|
|
expr = module.createUnreachable();
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case Token.AMPERSAND_EQUALS: compound = true;
|
|
case Token.AMPERSAND: {
|
|
leftExpr = this.compileExpressionRetainType(
|
|
left,
|
|
contextualType,
|
|
false // retains low bits of small integers
|
|
);
|
|
if (compound) {
|
|
rightExpr = this.compileExpression(
|
|
right,
|
|
this.currentType,
|
|
ConversionKind.IMPLICIT,
|
|
false // ^
|
|
);
|
|
} else {
|
|
leftType = this.currentType;
|
|
rightExpr = this.compileExpressionRetainType(
|
|
right,
|
|
leftType,
|
|
false // ^
|
|
);
|
|
rightType = this.currentType;
|
|
if (commonType = Type.commonCompatible(leftType, rightType, false)) {
|
|
leftExpr = this.convertExpression(leftExpr, leftType, commonType, ConversionKind.IMPLICIT, left);
|
|
rightExpr = this.convertExpression(rightExpr, rightType, commonType, ConversionKind.IMPLICIT, right);
|
|
} else {
|
|
this.error(
|
|
DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2,
|
|
expression.range, "&", leftType.toString(), rightType.toString()
|
|
);
|
|
this.currentType = contextualType;
|
|
return module.createUnreachable();
|
|
}
|
|
}
|
|
switch (this.currentType.kind) {
|
|
case TypeKind.I8:
|
|
case TypeKind.I16:
|
|
case TypeKind.U8:
|
|
case TypeKind.U16:
|
|
case TypeKind.BOOL: possiblyOverflows = true; // if left or right already did
|
|
default: {
|
|
expr = module.createBinary(BinaryOp.AndI32, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.I64:
|
|
case TypeKind.U64: {
|
|
expr = module.createBinary(BinaryOp.AndI64, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.USIZE: // TODO: check operator overload
|
|
case TypeKind.ISIZE: {
|
|
expr = module.createBinary(
|
|
this.options.isWasm64
|
|
? BinaryOp.AndI64
|
|
: BinaryOp.AndI32,
|
|
leftExpr,
|
|
rightExpr
|
|
);
|
|
break;
|
|
}
|
|
case TypeKind.VOID: {
|
|
assert(false);
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
expression.range
|
|
);
|
|
expr = module.createUnreachable();
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case Token.BAR_EQUALS: compound = true;
|
|
case Token.BAR: {
|
|
leftExpr = this.compileExpressionRetainType(
|
|
left,
|
|
contextualType,
|
|
false // retains low bits of small integers
|
|
);
|
|
if (compound) {
|
|
rightExpr = this.compileExpression(
|
|
right,
|
|
this.currentType,
|
|
ConversionKind.IMPLICIT,
|
|
false // ^
|
|
);
|
|
} else {
|
|
leftType = this.currentType;
|
|
rightExpr = this.compileExpressionRetainType(
|
|
right,
|
|
leftType,
|
|
false // ^
|
|
);
|
|
rightType = this.currentType;
|
|
if (commonType = Type.commonCompatible(leftType, rightType, false)) {
|
|
leftExpr = this.convertExpression(leftExpr, leftType, commonType, ConversionKind.IMPLICIT, left);
|
|
rightExpr = this.convertExpression(rightExpr, rightType, commonType, ConversionKind.IMPLICIT, right);
|
|
} else {
|
|
this.error(
|
|
DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2,
|
|
expression.range, "|", leftType.toString(), rightType.toString()
|
|
);
|
|
this.currentType = contextualType;
|
|
return module.createUnreachable();
|
|
}
|
|
}
|
|
switch (this.currentType.kind) {
|
|
case TypeKind.I8:
|
|
case TypeKind.I16:
|
|
case TypeKind.U8:
|
|
case TypeKind.U16:
|
|
case TypeKind.BOOL: possiblyOverflows = true; // if left or right already did
|
|
default: {
|
|
expr = module.createBinary(BinaryOp.OrI32, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.I64:
|
|
case TypeKind.U64: {
|
|
expr = module.createBinary(BinaryOp.OrI64, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.USIZE: // TODO: check operator overload
|
|
case TypeKind.ISIZE: {
|
|
expr = module.createBinary(
|
|
this.options.isWasm64
|
|
? BinaryOp.OrI64
|
|
: BinaryOp.OrI32,
|
|
leftExpr,
|
|
rightExpr
|
|
);
|
|
break;
|
|
}
|
|
case TypeKind.VOID: {
|
|
assert(false);
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
expression.range
|
|
);
|
|
expr = module.createUnreachable();
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case Token.CARET_EQUALS: compound = true;
|
|
case Token.CARET: {
|
|
leftExpr = this.compileExpressionRetainType(
|
|
left,
|
|
contextualType,
|
|
false // retains low bits of small integers
|
|
);
|
|
if (compound) {
|
|
rightExpr = this.compileExpression(
|
|
right,
|
|
this.currentType,
|
|
ConversionKind.IMPLICIT,
|
|
false // ^
|
|
);
|
|
} else {
|
|
leftType = this.currentType;
|
|
rightExpr = this.compileExpressionRetainType(
|
|
right,
|
|
leftType,
|
|
false // ^
|
|
);
|
|
rightType = this.currentType;
|
|
if (commonType = Type.commonCompatible(leftType, rightType, false)) {
|
|
leftExpr = this.convertExpression(leftExpr, leftType, commonType, ConversionKind.IMPLICIT, left);
|
|
rightExpr = this.convertExpression(rightExpr, rightType, commonType, ConversionKind.IMPLICIT, right);
|
|
} else {
|
|
this.error(
|
|
DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2,
|
|
expression.range, "^", leftType.toString(), rightType.toString()
|
|
);
|
|
this.currentType = contextualType;
|
|
return module.createUnreachable();
|
|
}
|
|
}
|
|
switch (this.currentType.kind) {
|
|
case TypeKind.I8:
|
|
case TypeKind.I16:
|
|
case TypeKind.U8:
|
|
case TypeKind.U16:
|
|
case TypeKind.BOOL: possiblyOverflows = true; // if left or right already did
|
|
default: {
|
|
expr = module.createBinary(BinaryOp.XorI32, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.I64:
|
|
case TypeKind.U64: {
|
|
expr = module.createBinary(BinaryOp.XorI64, leftExpr, rightExpr);
|
|
break;
|
|
}
|
|
case TypeKind.USIZE: // TODO: check operator overload
|
|
case TypeKind.ISIZE: {
|
|
expr = module.createBinary(
|
|
this.options.isWasm64
|
|
? BinaryOp.XorI64
|
|
: BinaryOp.XorI32,
|
|
leftExpr,
|
|
rightExpr
|
|
);
|
|
break;
|
|
}
|
|
case TypeKind.VOID: {
|
|
assert(false);
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
expression.range
|
|
);
|
|
expr = module.createUnreachable();
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
// logical (no overloading)
|
|
|
|
case Token.AMPERSAND_AMPERSAND: { // left && right
|
|
leftExpr = this.compileExpressionRetainType(
|
|
left,
|
|
contextualType
|
|
);
|
|
rightExpr = this.compileExpression(
|
|
right,
|
|
this.currentType,
|
|
ConversionKind.IMPLICIT,
|
|
false
|
|
);
|
|
|
|
// clone left if free of side effects
|
|
expr = module.cloneExpression(leftExpr, true, 0);
|
|
|
|
// if not possible, tee left to a temp. local
|
|
if (!expr) {
|
|
tempLocal = this.currentFunction.getAndFreeTempLocal(this.currentType);
|
|
leftExpr = module.createTeeLocal(tempLocal.index, leftExpr);
|
|
}
|
|
|
|
possiblyOverflows = this.currentType.is(TypeFlags.SMALL | TypeFlags.INTEGER);
|
|
condExpr = makeIsTrueish(leftExpr, this.currentType, module);
|
|
|
|
// simplify when cloning left without side effects was successful
|
|
if (expr) {
|
|
expr = module.createIf(
|
|
condExpr, // left
|
|
rightExpr, // ? right
|
|
expr // : cloned left
|
|
);
|
|
}
|
|
|
|
// otherwise make use of the temp. local
|
|
else {
|
|
expr = module.createIf(
|
|
condExpr,
|
|
rightExpr,
|
|
module.createGetLocal(
|
|
assert(tempLocal).index, // to be sure
|
|
this.currentType.toNativeType()
|
|
)
|
|
);
|
|
}
|
|
break;
|
|
}
|
|
case Token.BAR_BAR: { // left || right
|
|
leftExpr = this.compileExpressionRetainType(
|
|
left,
|
|
contextualType
|
|
);
|
|
rightExpr = this.compileExpression(
|
|
right,
|
|
this.currentType,
|
|
ConversionKind.IMPLICIT,
|
|
false
|
|
);
|
|
|
|
// clone left if free of side effects
|
|
expr = this.module.cloneExpression(leftExpr, true, 0);
|
|
|
|
// if not possible, tee left to a temp. local
|
|
if (!expr) {
|
|
tempLocal = this.currentFunction.getAndFreeTempLocal(this.currentType);
|
|
leftExpr = module.createTeeLocal(tempLocal.index, leftExpr);
|
|
}
|
|
|
|
possiblyOverflows = this.currentType.is(TypeFlags.SMALL | TypeFlags.INTEGER); // if right did
|
|
condExpr = makeIsTrueish(leftExpr, this.currentType, module);
|
|
|
|
// simplify when cloning left without side effects was successful
|
|
if (expr) {
|
|
expr = this.module.createIf(
|
|
condExpr, // left
|
|
expr, // ? cloned left
|
|
rightExpr // : right
|
|
);
|
|
}
|
|
|
|
// otherwise make use of the temp. local
|
|
else {
|
|
expr = module.createIf(
|
|
condExpr,
|
|
module.createGetLocal(
|
|
assert(tempLocal).index, // to be sure
|
|
this.currentType.toNativeType()
|
|
),
|
|
rightExpr
|
|
);
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
assert(false);
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
expression.range
|
|
);
|
|
expr = this.module.createUnreachable();
|
|
break;
|
|
}
|
|
}
|
|
if (possiblyOverflows && wrapSmallIntegers) {
|
|
assert(this.currentType.is(TypeFlags.SMALL | TypeFlags.INTEGER)); // must be a small int
|
|
expr = makeSmallIntegerWrap(expr, this.currentType, module);
|
|
}
|
|
return compound
|
|
? this.compileAssignmentWithValue(left, expr, contextualType != Type.void)
|
|
: expr;
|
|
}
|
|
|
|
compileAssignment(expression: Expression, valueExpression: Expression, contextualType: Type): ExpressionRef {
|
|
var currentFunction = this.currentFunction;
|
|
var resolved = this.program.resolveExpression(expression, currentFunction); // reports
|
|
if (!resolved) return this.module.createUnreachable();
|
|
|
|
// to compile just the value, we need to know the target's type
|
|
var element = resolved.element;
|
|
var elementType: Type;
|
|
switch (element.kind) {
|
|
case ElementKind.GLOBAL: {
|
|
if (!this.compileGlobal(<Global>element)) { // reports; not yet compiled if a static field compiled as a global
|
|
return this.module.createUnreachable();
|
|
}
|
|
assert((<Global>element).type != Type.void, "concrete type expected");
|
|
// fall-through
|
|
}
|
|
case ElementKind.LOCAL:
|
|
case ElementKind.FIELD: {
|
|
elementType = (<VariableLikeElement>element).type;
|
|
break;
|
|
}
|
|
case ElementKind.PROPERTY: {
|
|
let prototype = (<Property>element).setterPrototype;
|
|
if (prototype) {
|
|
let instance = prototype.resolve(); // reports
|
|
if (!instance) return this.module.createUnreachable();
|
|
assert(instance.signature.parameterTypes.length == 1);
|
|
elementType = instance.signature.parameterTypes[0];
|
|
break;
|
|
}
|
|
this.error(
|
|
DiagnosticCode.Cannot_assign_to_0_because_it_is_a_constant_or_a_read_only_property,
|
|
expression.range, (<Property>element).internalName
|
|
);
|
|
return this.module.createUnreachable();
|
|
}
|
|
case ElementKind.FUNCTION_PROTOTYPE: {
|
|
if (expression.kind == NodeKind.ELEMENTACCESS) { // @operator("[]")
|
|
if (resolved.target && resolved.target.kind == ElementKind.CLASS) {
|
|
if (element.simpleName == (<Class>resolved.target).prototype.fnIndexedGet) {
|
|
let resolvedIndexedSet = (<FunctionPrototype>element).resolve(null); // reports
|
|
if (resolvedIndexedSet) {
|
|
elementType = resolvedIndexedSet.signature.returnType;
|
|
break;
|
|
}
|
|
} else {
|
|
this.error(
|
|
DiagnosticCode.Index_signature_is_missing_in_type_0,
|
|
expression.range, (<Class>resolved.target).toString()
|
|
);
|
|
return this.module.createUnreachable();
|
|
}
|
|
}
|
|
}
|
|
// fall-through
|
|
}
|
|
default: {
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
expression.range
|
|
);
|
|
return this.module.createUnreachable();
|
|
}
|
|
}
|
|
|
|
// compile the value and do the assignment
|
|
var valueExpr = this.compileExpression(valueExpression, elementType);
|
|
return this.compileAssignmentWithValue(
|
|
expression,
|
|
valueExpr,
|
|
contextualType != Type.void
|
|
);
|
|
}
|
|
|
|
compileAssignmentWithValue(
|
|
expression: Expression,
|
|
valueWithCorrectType: ExpressionRef,
|
|
tee: bool = false
|
|
): ExpressionRef {
|
|
var module = this.module;
|
|
var resolved = this.program.resolveExpression(expression, this.currentFunction); // reports
|
|
if (!resolved) return module.createUnreachable();
|
|
|
|
var element = resolved.element;
|
|
switch (element.kind) {
|
|
case ElementKind.LOCAL: {
|
|
this.currentType = tee ? (<Local>element).type : Type.void;
|
|
if ((<Local>element).is(CommonFlags.CONST)) {
|
|
this.error(
|
|
DiagnosticCode.Cannot_assign_to_0_because_it_is_a_constant_or_a_read_only_property,
|
|
expression.range, (<Local>element).internalName
|
|
);
|
|
return module.createUnreachable();
|
|
}
|
|
return tee
|
|
? module.createTeeLocal((<Local>element).index, valueWithCorrectType)
|
|
: module.createSetLocal((<Local>element).index, valueWithCorrectType);
|
|
}
|
|
case ElementKind.GLOBAL: {
|
|
if (!this.compileGlobal(<Global>element)) return module.createUnreachable();
|
|
let type = (<Global>element).type;
|
|
assert(type != Type.void);
|
|
this.currentType = tee ? type : Type.void;
|
|
if ((<Local>element).is(CommonFlags.CONST)) {
|
|
this.error(
|
|
DiagnosticCode.Cannot_assign_to_0_because_it_is_a_constant_or_a_read_only_property,
|
|
expression.range,
|
|
(<Local>element).internalName
|
|
);
|
|
return module.createUnreachable();
|
|
}
|
|
if (tee) {
|
|
let nativeType = type.toNativeType();
|
|
let internalName = (<Global>element).internalName;
|
|
return module.createBlock(null, [ // emulated teeGlobal
|
|
module.createSetGlobal(internalName, valueWithCorrectType),
|
|
module.createGetGlobal(internalName, nativeType)
|
|
], nativeType);
|
|
} else {
|
|
return module.createSetGlobal((<Global>element).internalName, valueWithCorrectType);
|
|
}
|
|
}
|
|
case ElementKind.FIELD: {
|
|
if ((<Field>element).is(CommonFlags.READONLY)) {
|
|
this.error(
|
|
DiagnosticCode.Cannot_assign_to_0_because_it_is_a_constant_or_a_read_only_property,
|
|
expression.range, (<Field>element).internalName
|
|
);
|
|
return module.createUnreachable();
|
|
}
|
|
assert(resolved.isInstanceTarget);
|
|
let targetExpr = this.compileExpression(
|
|
<Expression>resolved.targetExpression,
|
|
(<Class>resolved.target).type
|
|
);
|
|
let type = (<Field>element).type;
|
|
this.currentType = tee ? type : Type.void;
|
|
let nativeType = type.toNativeType();
|
|
if (tee) {
|
|
let tempLocal = this.currentFunction.getAndFreeTempLocal(type);
|
|
let tempLocalIndex = tempLocal.index;
|
|
// TODO: simplify if valueWithCorrectType has no side effects
|
|
return module.createBlock(null, [
|
|
module.createSetLocal(tempLocalIndex, valueWithCorrectType),
|
|
module.createStore(
|
|
type.size >> 3,
|
|
targetExpr,
|
|
module.createGetLocal(tempLocalIndex, nativeType),
|
|
nativeType,
|
|
(<Field>element).memoryOffset
|
|
),
|
|
module.createGetLocal(tempLocalIndex, nativeType)
|
|
], nativeType);
|
|
} else {
|
|
return module.createStore(
|
|
type.size >> 3,
|
|
targetExpr,
|
|
valueWithCorrectType,
|
|
nativeType,
|
|
(<Field>element).memoryOffset
|
|
);
|
|
}
|
|
}
|
|
case ElementKind.PROPERTY: {
|
|
let setterPrototype = (<Property>element).setterPrototype;
|
|
if (setterPrototype) {
|
|
let setterInstance = setterPrototype.resolve(); // reports
|
|
if (!setterInstance) return module.createUnreachable();
|
|
|
|
// call just the setter if the return value isn't of interest
|
|
if (!tee) {
|
|
if (setterInstance.is(CommonFlags.INSTANCE)) {
|
|
assert(resolved.isInstanceTarget);
|
|
let thisArg = this.compileExpression(
|
|
<Expression>resolved.targetExpression,
|
|
(<Class>resolved.target).type
|
|
);
|
|
return this.makeCallDirect(setterInstance, [ thisArg, valueWithCorrectType ]);
|
|
} else {
|
|
return this.makeCallDirect(setterInstance, [ valueWithCorrectType ]);
|
|
}
|
|
}
|
|
|
|
// otherwise call the setter first, then the getter
|
|
let getterPrototype = (<Property>element).getterPrototype;
|
|
assert(getterPrototype != null); // must have one if there is a setter
|
|
let getterInstance = (<FunctionPrototype>getterPrototype).resolve(); // reports
|
|
if (!getterInstance) return module.createUnreachable();
|
|
let returnType = getterInstance.signature.returnType;
|
|
let nativeReturnType = returnType.toNativeType();
|
|
if (setterInstance.is(CommonFlags.INSTANCE)) {
|
|
assert(resolved.isInstanceTarget);
|
|
let thisArg = this.compileExpression(
|
|
<Expression>resolved.targetExpression,
|
|
(<Class>resolved.target).type
|
|
);
|
|
let tempLocal = this.currentFunction.getAndFreeTempLocal(returnType);
|
|
let tempLocalIndex = tempLocal.index;
|
|
return module.createBlock(null, [
|
|
this.makeCallDirect(setterInstance, [ // set and remember the target
|
|
module.createTeeLocal(tempLocalIndex, thisArg),
|
|
valueWithCorrectType
|
|
]),
|
|
this.makeCallDirect(getterInstance, [ // get from remembered target
|
|
module.createGetLocal(tempLocalIndex, nativeReturnType)
|
|
])
|
|
], nativeReturnType);
|
|
} else {
|
|
// note that this must be performed here because `resolved` is shared
|
|
return module.createBlock(null, [
|
|
this.makeCallDirect(setterInstance, [ valueWithCorrectType ]),
|
|
this.makeCallDirect(getterInstance)
|
|
], nativeReturnType);
|
|
}
|
|
} else {
|
|
this.error(
|
|
DiagnosticCode.Cannot_assign_to_0_because_it_is_a_constant_or_a_read_only_property,
|
|
expression.range, (<Property>element).internalName
|
|
);
|
|
}
|
|
return module.createUnreachable();
|
|
}
|
|
case ElementKind.FUNCTION_PROTOTYPE: { // @operator("[]") ?
|
|
if (expression.kind == NodeKind.ELEMENTACCESS) {
|
|
assert(resolved.isInstanceTarget);
|
|
let getterInstance = (<FunctionPrototype>element).resolve(); // reports
|
|
if (!getterInstance) return module.createUnreachable();
|
|
// obtain @operator("[]=")
|
|
let setElementName = (<Class>resolved.target).prototype.fnIndexedSet;
|
|
let setElement: Element | null;
|
|
if (
|
|
setElementName != null &&
|
|
(<Class>resolved.target).members &&
|
|
(setElement = (<Map<string,Element>>(<Class>resolved.target).members).get(setElementName)) &&
|
|
setElement.kind == ElementKind.FUNCTION_PROTOTYPE
|
|
) {
|
|
let setterInstance = (<FunctionPrototype>setElement).resolve(); // reports
|
|
if (!setterInstance) return module.createUnreachable();
|
|
let targetType = (<Class>resolved.target).type;
|
|
let targetExpr = this.compileExpression(
|
|
<Expression>resolved.targetExpression,
|
|
targetType
|
|
);
|
|
let elementExpr = this.compileExpression(
|
|
(<ElementAccessExpression>expression).elementExpression,
|
|
Type.i32
|
|
);
|
|
if (tee) {
|
|
let tempLocalTarget = this.currentFunction.getTempLocal(targetType);
|
|
let tempLocalElement = this.currentFunction.getAndFreeTempLocal(this.currentType);
|
|
let returnType = getterInstance.signature.returnType;
|
|
this.currentFunction.freeTempLocal(tempLocalTarget);
|
|
return module.createBlock(null, [
|
|
this.makeCallDirect(setterInstance, [
|
|
module.createTeeLocal(tempLocalTarget.index, targetExpr),
|
|
module.createTeeLocal(tempLocalElement.index, elementExpr),
|
|
valueWithCorrectType
|
|
]),
|
|
this.makeCallDirect(getterInstance, [
|
|
module.createGetLocal(tempLocalTarget.index, tempLocalTarget.type.toNativeType()),
|
|
module.createGetLocal(tempLocalElement.index, tempLocalElement.type.toNativeType())
|
|
])
|
|
], returnType.toNativeType());
|
|
} else {
|
|
return this.makeCallDirect(setterInstance, [
|
|
targetExpr,
|
|
elementExpr,
|
|
valueWithCorrectType
|
|
]);
|
|
}
|
|
} else {
|
|
this.error(
|
|
DiagnosticCode.Index_signature_in_type_0_only_permits_reading,
|
|
expression.range, (<Class>resolved.target).internalName
|
|
);
|
|
return module.createUnreachable();
|
|
}
|
|
}
|
|
// fall-through
|
|
}
|
|
}
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
expression.range
|
|
);
|
|
return module.createUnreachable();
|
|
}
|
|
|
|
compileCallExpression(expression: CallExpression, contextualType: Type): ExpressionRef {
|
|
var module = this.module;
|
|
var currentFunction = this.currentFunction;
|
|
var resolved = this.program.resolveExpression(expression.expression, currentFunction); // reports
|
|
if (!resolved) return module.createUnreachable();
|
|
|
|
var element = resolved.element;
|
|
var signature: Signature | null;
|
|
var indexArg: ExpressionRef;
|
|
switch (element.kind) {
|
|
|
|
// direct call: concrete function
|
|
case ElementKind.FUNCTION_PROTOTYPE: {
|
|
let prototype = <FunctionPrototype>element;
|
|
|
|
// builtins are compiled on the fly
|
|
if (prototype.is(CommonFlags.BUILTIN)) {
|
|
let expr = compileBuiltinCall( // reports
|
|
this,
|
|
prototype,
|
|
prototype.resolveBuiltinTypeArguments(
|
|
expression.typeArguments,
|
|
currentFunction.contextualTypeArguments
|
|
),
|
|
expression.arguments,
|
|
contextualType,
|
|
expression
|
|
);
|
|
if (!expr) {
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
expression.range
|
|
);
|
|
return module.createUnreachable();
|
|
}
|
|
return expr;
|
|
|
|
// otherwise compile to a call
|
|
} else {
|
|
let instance = prototype.resolveUsingTypeArguments( // reports
|
|
expression.typeArguments,
|
|
currentFunction.contextualTypeArguments,
|
|
expression
|
|
);
|
|
if (!instance) return module.createUnreachable();
|
|
let thisArg: ExpressionRef = 0;
|
|
if (instance.is(CommonFlags.INSTANCE)) {
|
|
assert(resolved.isInstanceTarget);
|
|
thisArg = this.compileExpression(
|
|
<Expression>resolved.targetExpression,
|
|
(<Class>resolved.target).type
|
|
);
|
|
if (!thisArg) return module.createUnreachable();
|
|
} else {
|
|
assert(!resolved.isInstanceTarget);
|
|
}
|
|
return this.compileCallDirect(instance, expression.arguments, expression, thisArg);
|
|
}
|
|
}
|
|
|
|
// indirect call: index argument with signature
|
|
case ElementKind.LOCAL: {
|
|
if (signature = (<Local>element).type.functionType) {
|
|
indexArg = module.createGetLocal((<Local>element).index, NativeType.I32);
|
|
break;
|
|
} else {
|
|
this.error(
|
|
DiagnosticCode.Cannot_invoke_an_expression_whose_type_lacks_a_call_signature_Type_0_has_no_compatible_call_signatures,
|
|
expression.range, (<Local>element).type.toString()
|
|
);
|
|
return module.createUnreachable();
|
|
}
|
|
}
|
|
case ElementKind.GLOBAL: {
|
|
if (signature = (<Global>element).type.functionType) {
|
|
indexArg = module.createGetGlobal((<Global>element).internalName, (<Global>element).type.toNativeType());
|
|
break;
|
|
} else {
|
|
this.error(
|
|
DiagnosticCode.Cannot_invoke_an_expression_whose_type_lacks_a_call_signature_Type_0_has_no_compatible_call_signatures,
|
|
expression.range, (<Global>element).type.toString()
|
|
);
|
|
return module.createUnreachable();
|
|
}
|
|
}
|
|
case ElementKind.FIELD: {
|
|
let type = (<Field>element).type;
|
|
if (signature = type.functionType) {
|
|
let targetExpr = this.compileExpression(assert(resolved.targetExpression), type);
|
|
indexArg = module.createLoad(
|
|
4,
|
|
false,
|
|
targetExpr,
|
|
NativeType.I32,
|
|
(<Field>element).memoryOffset
|
|
);
|
|
break;
|
|
} else {
|
|
this.error(
|
|
DiagnosticCode.Cannot_invoke_an_expression_whose_type_lacks_a_call_signature_Type_0_has_no_compatible_call_signatures,
|
|
expression.range, (<Field>element).type.toString()
|
|
);
|
|
return module.createUnreachable();
|
|
}
|
|
}
|
|
case ElementKind.FUNCTION_TARGET: {
|
|
signature = (<FunctionTarget>element).signature;
|
|
indexArg = this.compileExpression(expression.expression, (<FunctionTarget>element).type);
|
|
break;
|
|
}
|
|
case ElementKind.PROPERTY: // TODO
|
|
|
|
// not supported
|
|
default: {
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
expression.range
|
|
);
|
|
return module.createUnreachable();
|
|
}
|
|
}
|
|
return this.compileCallIndirect(
|
|
signature,
|
|
indexArg,
|
|
expression.arguments,
|
|
expression
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Checks that a call with the given number as arguments can be performed according to the
|
|
* specified signature.
|
|
*/
|
|
checkCallSignature(
|
|
signature: Signature,
|
|
numArguments: i32,
|
|
hasThis: bool,
|
|
reportNode: Node
|
|
): bool {
|
|
|
|
// cannot call an instance method without a `this` argument (TODO: `.call`?)
|
|
var thisType = signature.thisType;
|
|
if (hasThis != (thisType != null)) {
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported, // TODO: better message?
|
|
reportNode.range
|
|
);
|
|
return false;
|
|
}
|
|
|
|
// not yet implemented (TODO: maybe some sort of an unmanaged/lightweight array?)
|
|
var hasRest = signature.hasRest;
|
|
if (hasRest) {
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
reportNode.range
|
|
);
|
|
return false;
|
|
}
|
|
|
|
var minimum = signature.requiredParameters;
|
|
var maximum = signature.parameterTypes.length;
|
|
|
|
// must at least be called with required arguments
|
|
if (numArguments < minimum) {
|
|
this.error(
|
|
minimum < maximum
|
|
? DiagnosticCode.Expected_at_least_0_arguments_but_got_1
|
|
: DiagnosticCode.Expected_0_arguments_but_got_1,
|
|
reportNode.range, minimum.toString(), numArguments.toString()
|
|
);
|
|
return false;
|
|
}
|
|
|
|
// must not be called with more than the maximum arguments
|
|
if (numArguments > maximum && !hasRest) {
|
|
this.error(
|
|
DiagnosticCode.Expected_0_arguments_but_got_1,
|
|
reportNode.range, maximum.toString(), numArguments.toString()
|
|
);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/** Compiles a direct call to a concrete function. */
|
|
compileCallDirect(
|
|
instance: Function,
|
|
argumentExpressions: Expression[],
|
|
reportNode: Node,
|
|
thisArg: ExpressionRef = 0
|
|
): ExpressionRef {
|
|
var numArguments = argumentExpressions.length;
|
|
var signature = instance.signature;
|
|
|
|
if (!this.checkCallSignature( // reports
|
|
signature,
|
|
numArguments,
|
|
thisArg != 0,
|
|
reportNode
|
|
)) {
|
|
return this.module.createUnreachable();
|
|
}
|
|
|
|
var numArgumentsInclThis = thisArg ? numArguments + 1 : numArguments;
|
|
var operands = new Array<ExpressionRef>(numArgumentsInclThis);
|
|
var index = 0;
|
|
if (thisArg) {
|
|
operands[0] = thisArg;
|
|
index = 1;
|
|
}
|
|
var parameterTypes = signature.parameterTypes;
|
|
for (let i = 0; i < numArguments; ++i, ++index) {
|
|
operands[index] = this.compileExpression(
|
|
argumentExpressions[i],
|
|
parameterTypes[i]
|
|
);
|
|
}
|
|
assert(index == numArgumentsInclThis);
|
|
return this.makeCallDirect(instance, operands);
|
|
}
|
|
|
|
/** Gets the trampoline for the specified function. */
|
|
ensureTrampoline(original: Function): Function {
|
|
var trampoline = original.trampoline;
|
|
if (trampoline) return trampoline;
|
|
|
|
var originalSignature = original.signature;
|
|
var originalName = original.internalName;
|
|
var originalParameterTypes = originalSignature.parameterTypes;
|
|
var originalParameterDeclarations = original.prototype.declaration.signature.parameterTypes;
|
|
var commonReturnType = originalSignature.returnType;
|
|
var commonThisType = originalSignature.thisType;
|
|
var isInstance = original.is(CommonFlags.INSTANCE);
|
|
|
|
// arguments excl. `this`, operands incl. `this`
|
|
var minArguments = originalSignature.requiredParameters;
|
|
var minOperands = minArguments;
|
|
var maxArguments = originalParameterTypes.length;
|
|
var maxOperands = maxArguments;
|
|
if (isInstance) {
|
|
++minOperands;
|
|
++maxOperands;
|
|
}
|
|
var numOptional = maxOperands - minOperands;
|
|
assert(numOptional);
|
|
|
|
var forwardedOperands = new Array<ExpressionRef>(minOperands);
|
|
var operandIndex = 0;
|
|
|
|
// forward `this` if applicable
|
|
var module = this.module;
|
|
if (isInstance) {
|
|
forwardedOperands[0] = module.createGetLocal(0, this.options.nativeSizeType);
|
|
operandIndex = 1;
|
|
}
|
|
|
|
// forward required arguments
|
|
for (let i = 0; i < minArguments; ++i, ++operandIndex) {
|
|
forwardedOperands[operandIndex] = module.createGetLocal(operandIndex, originalParameterTypes[i].toNativeType());
|
|
}
|
|
assert(operandIndex == minOperands);
|
|
|
|
// append an additional parameter taking the number of optional arguments provided
|
|
var trampolineParameterTypes = new Array<Type>(maxArguments + 1);
|
|
for (let i = 0; i < maxArguments; ++i) {
|
|
trampolineParameterTypes[i] = originalParameterTypes[i];
|
|
}
|
|
trampolineParameterTypes[maxArguments] = Type.i32;
|
|
|
|
// create the trampoline element
|
|
var trampolineSignature = new Signature(trampolineParameterTypes, commonReturnType, commonThisType);
|
|
var trampolineName = originalName + "|trampoline";
|
|
trampolineSignature.requiredParameters = maxArguments + 1;
|
|
trampoline = new Function(original.prototype, trampolineName, trampolineSignature, original.instanceMethodOf);
|
|
trampoline.flags = original.flags;
|
|
trampoline.set(CommonFlags.COMPILED);
|
|
original.trampoline = trampoline;
|
|
|
|
// compile initializers of omitted arguments in scope of the trampoline function
|
|
// this is necessary because initializers might need additional locals and a proper this context
|
|
var previousFunction = this.currentFunction;
|
|
this.currentFunction = trampoline;
|
|
|
|
// create a br_table switching over the number of optional parameters provided
|
|
var numNames = numOptional + 1; // incl. 'with0'
|
|
var names = new Array<string>(numNames);
|
|
for (let i = 0; i < numNames; ++i) {
|
|
let label = "N=" + i.toString();
|
|
names[i] = label;
|
|
}
|
|
var body = module.createBlock(names[0], [
|
|
module.createBlock("N=invalid", [
|
|
module.createSwitch(names, "N=invalid",
|
|
module.createGetLocal(maxOperands, NativeType.I32)
|
|
)
|
|
]),
|
|
module.createUnreachable()
|
|
]);
|
|
for (let i = 0; i < numOptional; ++i, ++operandIndex) {
|
|
let type = originalParameterTypes[minArguments + i];
|
|
body = module.createBlock(names[i + 1], [
|
|
body,
|
|
module.createSetLocal(operandIndex,
|
|
this.compileExpression(
|
|
assert(originalParameterDeclarations[minArguments + i].initializer),
|
|
type
|
|
)
|
|
)
|
|
]);
|
|
forwardedOperands[operandIndex] = module.createGetLocal(operandIndex, type.toNativeType());
|
|
}
|
|
this.currentFunction = previousFunction;
|
|
assert(operandIndex == maxOperands);
|
|
|
|
var typeRef = this.ensureFunctionType(trampolineSignature);
|
|
var funcRef = module.addFunction(trampolineName, typeRef, typesToNativeTypes(trampoline.additionalLocals),
|
|
module.createBlock(null, [
|
|
body,
|
|
module.createCall(
|
|
originalName,
|
|
forwardedOperands,
|
|
commonReturnType.toNativeType()
|
|
)
|
|
], commonReturnType.toNativeType())
|
|
);
|
|
trampoline.finalize(module, funcRef);
|
|
return trampoline;
|
|
}
|
|
|
|
/** Creates a direct call to the specified function. */
|
|
makeCallDirect(instance: Function, operands: ExpressionRef[] | null = null): ExpressionRef {
|
|
var numOperands = operands ? operands.length : 0;
|
|
var numArguments = numOperands;
|
|
var minArguments = instance.signature.requiredParameters;
|
|
var minOperands = minArguments;
|
|
var maxArguments = instance.signature.parameterTypes.length;
|
|
var maxOperands = maxArguments;
|
|
if (instance.is(CommonFlags.INSTANCE)) {
|
|
++minOperands;
|
|
++maxOperands;
|
|
--numArguments;
|
|
}
|
|
assert(numOperands >= minOperands);
|
|
var module = this.module;
|
|
if (!this.compileFunction(instance)) return module.createUnreachable();
|
|
if (numOperands < maxOperands) {
|
|
instance = this.ensureTrampoline(instance);
|
|
if (!this.compileFunction(instance)) return module.createUnreachable();
|
|
if (!operands) {
|
|
operands = new Array(maxOperands + 1);
|
|
operands.length = 0;
|
|
}
|
|
for (let i = numArguments; i < maxArguments; ++i) {
|
|
operands.push(instance.signature.parameterTypes[i].toNativeZero(module));
|
|
}
|
|
operands.push(module.createI32(numOperands - minOperands));
|
|
}
|
|
var returnType = instance.signature.returnType;
|
|
this.currentType = returnType;
|
|
if (instance.is(CommonFlags.MODULE_IMPORT)) {
|
|
return module.createCallImport(instance.internalName, operands, returnType.toNativeType());
|
|
} else {
|
|
return module.createCall(instance.internalName, operands, returnType.toNativeType());
|
|
}
|
|
}
|
|
|
|
/** Compiles an indirect call using an index argument and a signature. */
|
|
compileCallIndirect(
|
|
signature: Signature,
|
|
indexArg: ExpressionRef,
|
|
argumentExpressions: Expression[],
|
|
reportNode: Node,
|
|
thisArg: ExpressionRef = 0
|
|
): ExpressionRef {
|
|
var numArguments = argumentExpressions.length;
|
|
|
|
if (!this.checkCallSignature( // reports
|
|
signature,
|
|
numArguments,
|
|
thisArg != 0,
|
|
reportNode
|
|
)) {
|
|
return this.module.createUnreachable();
|
|
}
|
|
|
|
var numArgumentsInclThis = thisArg ? numArguments + 1 : numArguments;
|
|
var operands = new Array<ExpressionRef>(numArgumentsInclThis);
|
|
var index = 0;
|
|
if (thisArg) {
|
|
operands[0] = thisArg;
|
|
index = 1;
|
|
}
|
|
var parameterTypes = signature.parameterTypes;
|
|
for (let i = 0; i < numArguments; ++i, ++index) {
|
|
operands[index] = this.compileExpression(
|
|
argumentExpressions[i],
|
|
parameterTypes[i]
|
|
);
|
|
}
|
|
assert(index == numArgumentsInclThis);
|
|
return this.makeCallIndirect(signature, indexArg, operands);
|
|
}
|
|
|
|
/** Creates an indirect call to the function at `indexArg` in the function table. */
|
|
makeCallIndirect(signature: Signature, indexArg: ExpressionRef, operands: ExpressionRef[]): ExpressionRef {
|
|
var returnType = signature.returnType;
|
|
this.currentType = returnType;
|
|
this.ensureFunctionType(signature);
|
|
return this.module.createCallIndirect(indexArg, operands, signature.toSignatureString());
|
|
}
|
|
|
|
compileCommaExpression(expression: CommaExpression, contextualType: Type): ExpressionRef {
|
|
var expressions = expression.expressions;
|
|
var numExpressions = expressions.length;
|
|
var exprs = new Array<ExpressionRef>(numExpressions--);
|
|
for (let i = 0; i < numExpressions; ++i) {
|
|
exprs[i] = this.compileExpression(expressions[i], Type.void); // drop all
|
|
}
|
|
exprs[numExpressions] = this.compileExpression(expressions[numExpressions], contextualType); // except last
|
|
return this.module.createBlock(null, exprs, this.currentType.toNativeType());
|
|
}
|
|
|
|
compileElementAccessExpression(expression: ElementAccessExpression, contextualType: Type): ExpressionRef {
|
|
var resolved = this.program.resolveElementAccess(expression, this.currentFunction); // reports
|
|
if (!resolved) return this.module.createUnreachable();
|
|
|
|
assert( // should be guaranteed by resolveElementAccess
|
|
resolved.element.kind == ElementKind.FUNCTION_PROTOTYPE &&
|
|
resolved.target &&
|
|
resolved.target.kind == ElementKind.CLASS
|
|
);
|
|
var target = <Class>resolved.target;
|
|
var instance = (<FunctionPrototype>resolved.element).resolve( // reports
|
|
null,
|
|
target.contextualTypeArguments
|
|
);
|
|
if (!instance) return this.module.createUnreachable();
|
|
var thisArg = this.compileExpression(expression.expression, target.type);
|
|
return this.compileCallDirect(instance, [
|
|
expression.elementExpression
|
|
], expression, thisArg);
|
|
}
|
|
|
|
compileFunctionExpression(expression: FunctionExpression, contextualType: Type): ExpressionRef {
|
|
var declaration = expression.declaration;
|
|
var name = declaration.name;
|
|
var simpleName = (name.text.length
|
|
? name.text
|
|
: "anonymous") + "|" + this.functionTable.length.toString(10);
|
|
var currentFunction = this.currentFunction;
|
|
var prototype = new FunctionPrototype(
|
|
this.program,
|
|
simpleName,
|
|
currentFunction.internalName + "~" + simpleName,
|
|
declaration
|
|
);
|
|
var instance = this.compileFunctionUsingTypeArguments(
|
|
prototype,
|
|
[],
|
|
currentFunction.contextualTypeArguments,
|
|
declaration
|
|
);
|
|
if (!instance) return this.module.createUnreachable();
|
|
this.currentType = Type.u32.asFunction(instance.signature); // TODO: get cached type?
|
|
// NOTE that, in order to make this work in every case, the function must be represented by a
|
|
// value, so we add it and rely on the optimizer to figure out where it can be called directly.
|
|
var index = this.ensureFunctionTableEntry(instance); // reports
|
|
return index < 0
|
|
? this.module.createUnreachable()
|
|
: this.module.createI32(index);
|
|
}
|
|
|
|
/**
|
|
* Compiles an identifier in the specified context.
|
|
* @param retainConstantType Retains the type of inlined constants if `true`, otherwise
|
|
* precomputes them according to context.
|
|
*/
|
|
compileIdentifierExpression(
|
|
expression: IdentifierExpression,
|
|
contextualType: Type,
|
|
retainConstantType: bool
|
|
): ExpressionRef {
|
|
var module = this.module;
|
|
// check special keywords first
|
|
switch (expression.kind) {
|
|
case NodeKind.NULL: {
|
|
let options = this.options;
|
|
if (!contextualType.classType) {
|
|
this.currentType = options.usizeType;
|
|
}
|
|
return options.isWasm64
|
|
? module.createI64(0)
|
|
: module.createI32(0);
|
|
}
|
|
case NodeKind.TRUE: {
|
|
this.currentType = Type.bool;
|
|
return module.createI32(1);
|
|
}
|
|
case NodeKind.FALSE: {
|
|
this.currentType = Type.bool;
|
|
return module.createI32(0);
|
|
}
|
|
case NodeKind.THIS: {
|
|
let currentFunction = this.currentFunction;
|
|
if (currentFunction.is(CommonFlags.INSTANCE)) {
|
|
let thisType = assert(currentFunction.instanceMethodOf).type;
|
|
this.currentType = thisType;
|
|
return module.createGetLocal(0, thisType.toNativeType());
|
|
}
|
|
this.error(
|
|
DiagnosticCode._this_cannot_be_referenced_in_current_location,
|
|
expression.range
|
|
);
|
|
this.currentType = this.options.usizeType;
|
|
return module.createUnreachable();
|
|
}
|
|
case NodeKind.SUPER: {
|
|
let currentFunction = this.currentFunction;
|
|
if (currentFunction.is(CommonFlags.INSTANCE)) {
|
|
let base = assert(currentFunction.instanceMethodOf).base;
|
|
if (base) {
|
|
let superType = base.type;
|
|
this.currentType = superType;
|
|
return module.createGetLocal(0, superType.toNativeType());
|
|
}
|
|
}
|
|
this.error(
|
|
DiagnosticCode._super_can_only_be_referenced_in_a_derived_class,
|
|
expression.range
|
|
);
|
|
this.currentType = this.options.usizeType;
|
|
return module.createUnreachable();
|
|
}
|
|
}
|
|
|
|
// otherwise resolve
|
|
var resolved = this.program.resolveIdentifier( // reports
|
|
expression,
|
|
this.currentFunction,
|
|
this.currentEnum
|
|
);
|
|
if (!resolved) return module.createUnreachable();
|
|
|
|
var element = resolved.element;
|
|
switch (element.kind) {
|
|
case ElementKind.LOCAL: {
|
|
if ((<Local>element).is(CommonFlags.INLINED)) {
|
|
return this.compileInlineConstant(<Local>element, contextualType, retainConstantType);
|
|
}
|
|
let localType = (<Local>element).type;
|
|
let localIndex = (<Local>element).index;
|
|
assert(localIndex >= 0);
|
|
this.currentType = localType;
|
|
return this.module.createGetLocal(localIndex, localType.toNativeType());
|
|
}
|
|
case ElementKind.GLOBAL: {
|
|
if (element.is(CommonFlags.BUILTIN)) {
|
|
return compileBuiltinGetConstant(this, <Global>element, expression);
|
|
}
|
|
if (!this.compileGlobal(<Global>element)) { // reports; not yet compiled if a static field
|
|
return this.module.createUnreachable();
|
|
}
|
|
let globalType = (<Global>element).type;
|
|
assert(globalType != Type.void);
|
|
if ((<Global>element).is(CommonFlags.INLINED)) {
|
|
return this.compileInlineConstant(<Global>element, contextualType, retainConstantType);
|
|
}
|
|
this.currentType = globalType;
|
|
return this.module.createGetGlobal((<Global>element).internalName, globalType.toNativeType());
|
|
}
|
|
case ElementKind.ENUMVALUE: { // here: if referenced from within the same enum
|
|
if (!element.is(CommonFlags.COMPILED)) {
|
|
this.error(
|
|
DiagnosticCode.A_member_initializer_in_a_enum_declaration_cannot_reference_members_declared_after_it_including_members_defined_in_other_enums,
|
|
expression.range
|
|
);
|
|
this.currentType = Type.i32;
|
|
return this.module.createUnreachable();
|
|
}
|
|
this.currentType = Type.i32;
|
|
if ((<EnumValue>element).is(CommonFlags.INLINED)) {
|
|
return this.module.createI32((<EnumValue>element).constantValue);
|
|
}
|
|
return this.module.createGetGlobal((<EnumValue>element).internalName, NativeType.I32);
|
|
}
|
|
}
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
expression.range
|
|
);
|
|
return this.module.createUnreachable();
|
|
}
|
|
|
|
compileLiteralExpression(
|
|
expression: LiteralExpression,
|
|
contextualType: Type,
|
|
implicitNegate: bool = false
|
|
): ExpressionRef {
|
|
var module = this.module;
|
|
|
|
switch (expression.literalKind) {
|
|
case LiteralKind.ARRAY: {
|
|
assert(!implicitNegate);
|
|
let classType = contextualType.classType;
|
|
if (
|
|
classType &&
|
|
classType == this.program.elementsLookup.get("Array") &&
|
|
classType.typeArguments && classType.typeArguments.length == 1
|
|
) {
|
|
return this.compileStaticArray(
|
|
classType.typeArguments[0],
|
|
(<ArrayLiteralExpression>expression).elementExpressions,
|
|
expression
|
|
);
|
|
}
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
expression.range
|
|
);
|
|
return module.createUnreachable();
|
|
}
|
|
case LiteralKind.FLOAT: {
|
|
let floatValue = (<FloatLiteralExpression>expression).value;
|
|
if (implicitNegate) {
|
|
floatValue = -floatValue;
|
|
}
|
|
if (contextualType == Type.f32) {
|
|
return module.createF32(<f32>floatValue);
|
|
}
|
|
this.currentType = Type.f64;
|
|
return module.createF64(floatValue);
|
|
}
|
|
case LiteralKind.INTEGER: {
|
|
let intValue = (<IntegerLiteralExpression>expression).value;
|
|
if (implicitNegate) {
|
|
intValue = i64_sub(
|
|
i64_new(0),
|
|
intValue
|
|
);
|
|
}
|
|
switch (contextualType.kind) {
|
|
|
|
// compile to contextualType if matching
|
|
|
|
case TypeKind.I8: {
|
|
if (i64_is_i8(intValue)) return module.createI32(i64_low(intValue));
|
|
break;
|
|
}
|
|
case TypeKind.I16: {
|
|
if (i64_is_i16(intValue)) return module.createI32(i64_low(intValue));
|
|
break;
|
|
}
|
|
case TypeKind.I32: {
|
|
if (i64_is_i32(intValue)) return module.createI32(i64_low(intValue));
|
|
break;
|
|
}
|
|
case TypeKind.U8: {
|
|
if (i64_is_u8(intValue)) return module.createI32(i64_low(intValue));
|
|
break;
|
|
}
|
|
case TypeKind.U16: {
|
|
if (i64_is_u16(intValue)) return module.createI32(i64_low(intValue));
|
|
break;
|
|
}
|
|
case TypeKind.U32: {
|
|
if (i64_is_u32(intValue)) return module.createI32(i64_low(intValue));
|
|
break;
|
|
}
|
|
case TypeKind.BOOL: {
|
|
if (i64_is_bool(intValue)) return module.createI32(i64_low(intValue));
|
|
break;
|
|
}
|
|
case TypeKind.ISIZE: {
|
|
if (!this.options.isWasm64) {
|
|
if (i64_is_u32(intValue)) return module.createI32(i64_low(intValue));
|
|
break;
|
|
}
|
|
return module.createI64(i64_low(intValue), i64_high(intValue));
|
|
}
|
|
case TypeKind.USIZE: {
|
|
if (!this.options.isWasm64) {
|
|
if (i64_is_u32(intValue)) return module.createI32(i64_low(intValue));
|
|
break;
|
|
}
|
|
return module.createI64(i64_low(intValue), i64_high(intValue));
|
|
}
|
|
case TypeKind.I64:
|
|
case TypeKind.U64: {
|
|
return module.createI64(i64_low(intValue), i64_high(intValue));
|
|
}
|
|
case TypeKind.F32: {
|
|
if (i64_is_f32(intValue)) return module.createF32(i64_to_f32(intValue));
|
|
break;
|
|
}
|
|
case TypeKind.F64: {
|
|
if (i64_is_f64(intValue)) return module.createF64(i64_to_f64(intValue));
|
|
break;
|
|
}
|
|
case TypeKind.VOID: {
|
|
break; // compiles to best fitting type below, being dropped
|
|
}
|
|
default: {
|
|
assert(false);
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
expression.range
|
|
);
|
|
return module.createUnreachable();
|
|
}
|
|
}
|
|
|
|
// otherwise compile to best fitting native type
|
|
|
|
if (i64_is_i32(intValue)) {
|
|
this.currentType = Type.i32;
|
|
return module.createI32(i64_low(intValue));
|
|
} else {
|
|
this.currentType = Type.i64;
|
|
return module.createI64(i64_low(intValue), i64_high(intValue));
|
|
}
|
|
}
|
|
case LiteralKind.STRING: {
|
|
assert(!implicitNegate);
|
|
return this.compileStaticString((<StringLiteralExpression>expression).value);
|
|
}
|
|
// case LiteralKind.OBJECT:
|
|
// case LiteralKind.REGEXP:
|
|
}
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
expression.range
|
|
);
|
|
this.currentType = contextualType;
|
|
return module.createUnreachable();
|
|
}
|
|
|
|
compileStaticString(stringValue: string): ExpressionRef {
|
|
var module = this.module;
|
|
var options = this.options;
|
|
var stringSegments = this.stringSegments;
|
|
|
|
var stringSegment: MemorySegment | null = stringSegments.get(stringValue);
|
|
if (!stringSegment) {
|
|
let stringLength = stringValue.length;
|
|
let stringBuffer = new Uint8Array(4 + stringLength * 2);
|
|
stringBuffer[0] = stringLength & 0xff;
|
|
stringBuffer[1] = (stringLength >>> 8) & 0xff;
|
|
stringBuffer[2] = (stringLength >>> 16) & 0xff;
|
|
stringBuffer[3] = (stringLength >>> 24) & 0xff;
|
|
for (let i = 0; i < stringLength; ++i) {
|
|
stringBuffer[4 + i * 2] = stringValue.charCodeAt(i) & 0xff;
|
|
stringBuffer[5 + i * 2] = (stringValue.charCodeAt(i) >>> 8) & 0xff;
|
|
}
|
|
stringSegment = this.addMemorySegment(stringBuffer, options.usizeType.byteSize);
|
|
stringSegments.set(stringValue, stringSegment);
|
|
}
|
|
var stringOffset = stringSegment.offset;
|
|
var stringType = this.program.typesLookup.get("string");
|
|
this.currentType = stringType ? stringType : options.usizeType;
|
|
if (options.isWasm64) {
|
|
return module.createI64(i64_low(stringOffset), i64_high(stringOffset));
|
|
}
|
|
assert(i64_is_i32(stringOffset));
|
|
return module.createI32(i64_low(stringOffset));
|
|
}
|
|
|
|
compileStaticArray(elementType: Type, expressions: (Expression | null)[], reportNode: Node): ExpressionRef {
|
|
// compile as static if all element expressions are precomputable, otherwise
|
|
// initialize in place.
|
|
var isStatic = true;
|
|
var size = expressions.length;
|
|
|
|
var module = this.module;
|
|
var nativeType = elementType.toNativeType();
|
|
var values: usize;
|
|
switch (nativeType) {
|
|
case NativeType.I32: {
|
|
values = changetype<usize>(new Int32Array(size));
|
|
break;
|
|
}
|
|
case NativeType.I64: {
|
|
values = changetype<usize>(new Array<I64>(size));
|
|
break;
|
|
}
|
|
case NativeType.F32: {
|
|
values = changetype<usize>(new Float32Array(size));
|
|
break;
|
|
}
|
|
case NativeType.F64: {
|
|
values = changetype<usize>(new Float64Array(size));
|
|
break;
|
|
}
|
|
default: {
|
|
assert(false);
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
reportNode.range
|
|
);
|
|
return module.createUnreachable();
|
|
}
|
|
}
|
|
|
|
var exprs = new Array<ExpressionRef>(size);
|
|
var expr: BinaryenExpressionRef;
|
|
for (let i = 0; i < size; ++i) {
|
|
exprs[i] = expressions[i]
|
|
? this.compileExpression(<Expression>expressions[i], elementType)
|
|
: elementType.toNativeZero(module);
|
|
if (isStatic) {
|
|
expr = this.precomputeExpressionRef(exprs[i]);
|
|
if (_BinaryenExpressionGetId(expr) == ExpressionId.Const) {
|
|
assert(_BinaryenExpressionGetType(expr) == nativeType);
|
|
switch (nativeType) {
|
|
case NativeType.I32: {
|
|
changetype<i32[]>(values)[i] = _BinaryenConstGetValueI32(expr);
|
|
break;
|
|
}
|
|
case NativeType.I64: {
|
|
changetype<I64[]>(values)[i] = i64_new(
|
|
_BinaryenConstGetValueI64Low(expr),
|
|
_BinaryenConstGetValueI64High(expr)
|
|
);
|
|
break;
|
|
}
|
|
case NativeType.F32: {
|
|
changetype<f32[]>(values)[i] = _BinaryenConstGetValueF32(expr);
|
|
break;
|
|
}
|
|
case NativeType.F64: {
|
|
changetype<f64[]>(values)[i] = _BinaryenConstGetValueF64(expr);
|
|
break;
|
|
}
|
|
default: {
|
|
assert(false); // checked above
|
|
}
|
|
}
|
|
} else {
|
|
// TODO: emit a warning if declared 'const'
|
|
isStatic = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isStatic) {
|
|
// TODO: convert to Uint8Array and create the segment
|
|
} else {
|
|
// TODO: initialize in place
|
|
}
|
|
// TODO: alternatively, static elements could go into data segments while
|
|
// dynamic ones are initialized on top? any benefits? (doesn't seem so)
|
|
throw new Error("not implemented");
|
|
}
|
|
|
|
compileNewExpression(expression: NewExpression, contextualType: Type): ExpressionRef {
|
|
var module = this.module;
|
|
var options = this.options;
|
|
var currentFunction = this.currentFunction;
|
|
|
|
var resolved = this.program.resolveExpression( // reports
|
|
expression.expression,
|
|
currentFunction
|
|
);
|
|
if (resolved) {
|
|
if (resolved.element.kind == ElementKind.CLASS_PROTOTYPE) {
|
|
let prototype = <ClassPrototype>resolved.element;
|
|
let instance = prototype.resolveUsingTypeArguments( // reports
|
|
expression.typeArguments,
|
|
null,
|
|
expression
|
|
);
|
|
if (instance) {
|
|
let thisExpr = compileBuiltinAllocate(this, instance, expression);
|
|
let initializers = new Array<ExpressionRef>();
|
|
|
|
// use a temp local for 'this'
|
|
let tempLocal = currentFunction.getTempLocal(options.usizeType);
|
|
initializers.push(module.createSetLocal(tempLocal.index, thisExpr));
|
|
|
|
// apply field initializers
|
|
if (instance.members) {
|
|
for (let member of instance.members.values()) {
|
|
if (member.kind == ElementKind.FIELD) {
|
|
let field = <Field>member;
|
|
let fieldDeclaration = field.prototype.declaration;
|
|
if (field.is(CommonFlags.CONST)) {
|
|
assert(false); // there are no built-in fields currently
|
|
} else if (fieldDeclaration && fieldDeclaration.initializer) {
|
|
initializers.push(module.createStore(field.type.byteSize,
|
|
module.createGetLocal(tempLocal.index, options.nativeSizeType),
|
|
this.compileExpression(fieldDeclaration.initializer, field.type),
|
|
field.type.toNativeType(),
|
|
field.memoryOffset
|
|
));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// apply constructor
|
|
let constructorInstance = instance.constructorInstance;
|
|
if (constructorInstance) {
|
|
initializers.push(this.compileCallDirect(constructorInstance, expression.arguments, expression,
|
|
module.createGetLocal(tempLocal.index, options.nativeSizeType)
|
|
));
|
|
}
|
|
|
|
// return 'this'
|
|
initializers.push(module.createGetLocal(tempLocal.index, options.nativeSizeType));
|
|
currentFunction.freeTempLocal(tempLocal);
|
|
thisExpr = module.createBlock(null, initializers, options.nativeSizeType);
|
|
|
|
this.currentType = instance.type;
|
|
return thisExpr;
|
|
}
|
|
} else {
|
|
this.error(
|
|
DiagnosticCode.Cannot_use_new_with_an_expression_whose_type_lacks_a_construct_signature,
|
|
expression.expression.range
|
|
);
|
|
}
|
|
}
|
|
return module.createUnreachable();
|
|
}
|
|
|
|
compileParenthesizedExpression(
|
|
expression: ParenthesizedExpression,
|
|
contextualType: Type,
|
|
wrapSmallIntegers: bool = true
|
|
): ExpressionRef {
|
|
// does not change types, just order
|
|
return this.compileExpression(
|
|
expression.expression,
|
|
contextualType,
|
|
ConversionKind.NONE,
|
|
wrapSmallIntegers
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Compiles a property access in the specified context.
|
|
* @param retainConstantType Retains the type of inlined constants if `true`, otherwise
|
|
* precomputes them according to context.
|
|
*/
|
|
compilePropertyAccessExpression(
|
|
propertyAccess: PropertyAccessExpression,
|
|
contextualType: Type,
|
|
retainConstantType: bool
|
|
): ExpressionRef {
|
|
var program = this.program;
|
|
var module = this.module;
|
|
|
|
var resolved = program.resolvePropertyAccess(propertyAccess, this.currentFunction); // reports
|
|
if (!resolved) return module.createUnreachable();
|
|
|
|
var element = resolved.element;
|
|
var targetExpr: ExpressionRef;
|
|
switch (element.kind) {
|
|
case ElementKind.GLOBAL: { // static property
|
|
if (element.is(CommonFlags.BUILTIN)) {
|
|
return compileBuiltinGetConstant(this, <Global>element, propertyAccess);
|
|
}
|
|
if (!this.compileGlobal(<Global>element)) { // reports; not yet compiled if a static field
|
|
return module.createUnreachable();
|
|
}
|
|
let globalType = (<Global>element).type;
|
|
assert(globalType != Type.void);
|
|
if ((<Global>element).is(CommonFlags.INLINED)) {
|
|
return this.compileInlineConstant(<Global>element, contextualType, retainConstantType);
|
|
}
|
|
this.currentType = globalType;
|
|
return module.createGetGlobal((<Global>element).internalName, globalType.toNativeType());
|
|
}
|
|
case ElementKind.ENUMVALUE: { // enum value
|
|
if (!this.compileEnum((<EnumValue>element).enum)) {
|
|
return this.module.createUnreachable();
|
|
}
|
|
this.currentType = Type.i32;
|
|
if ((<EnumValue>element).is(CommonFlags.INLINED)) {
|
|
return module.createI32((<EnumValue>element).constantValue);
|
|
}
|
|
return module.createGetGlobal((<EnumValue>element).internalName, NativeType.I32);
|
|
}
|
|
case ElementKind.FIELD: { // instance field
|
|
assert(resolved.isInstanceTarget);
|
|
assert((<Field>element).memoryOffset >= 0);
|
|
targetExpr = this.compileExpression(
|
|
<Expression>resolved.targetExpression,
|
|
this.options.usizeType,
|
|
ConversionKind.NONE
|
|
);
|
|
this.currentType = (<Field>element).type;
|
|
return module.createLoad(
|
|
(<Field>element).type.size >> 3,
|
|
(<Field>element).type.is(TypeFlags.SIGNED | TypeFlags.INTEGER),
|
|
targetExpr,
|
|
(<Field>element).type.toNativeType(),
|
|
(<Field>element).memoryOffset
|
|
);
|
|
}
|
|
case ElementKind.PROPERTY: { // instance property (here: getter)
|
|
let prototype = (<Property>element).getterPrototype;
|
|
if (prototype) {
|
|
let instance = prototype.resolve(null); // reports
|
|
if (!instance) return module.createUnreachable();
|
|
let signature = instance.signature;
|
|
if (!this.checkCallSignature( // reports
|
|
signature,
|
|
0,
|
|
instance.is(CommonFlags.INSTANCE),
|
|
propertyAccess
|
|
)) {
|
|
return module.createUnreachable();
|
|
}
|
|
if (instance.instanceMethodOf) {
|
|
targetExpr = this.compileExpression(
|
|
<Expression>resolved.targetExpression,
|
|
instance.instanceMethodOf.type
|
|
);
|
|
this.currentType = signature.returnType;
|
|
return this.compileCallDirect(instance, [], propertyAccess, targetExpr);
|
|
} else {
|
|
this.currentType = signature.returnType;
|
|
return this.compileCallDirect(instance, [], propertyAccess);
|
|
}
|
|
} else {
|
|
this.error(
|
|
DiagnosticCode.Property_0_does_not_exist_on_type_1,
|
|
propertyAccess.range, (<Property>element).simpleName, (<Property>element).parent.toString()
|
|
);
|
|
return module.createUnreachable();
|
|
}
|
|
}
|
|
}
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
propertyAccess.range
|
|
);
|
|
return module.createUnreachable();
|
|
}
|
|
|
|
compileTernaryExpression(expression: TernaryExpression, contextualType: Type): ExpressionRef {
|
|
var ifThen = expression.ifThen;
|
|
var ifElse = expression.ifElse;
|
|
|
|
var condExpr = makeIsTrueish(
|
|
this.compileExpression(expression.condition, Type.u32, ConversionKind.NONE),
|
|
this.currentType,
|
|
this.module
|
|
);
|
|
|
|
// Eliminate unnecesssary branches in generic contexts if the condition is constant
|
|
if (
|
|
this.currentFunction.isAny(CommonFlags.GENERIC | CommonFlags.GENERIC_CONTEXT) &&
|
|
_BinaryenExpressionGetId(condExpr = this.precomputeExpressionRef(condExpr)) == ExpressionId.Const &&
|
|
_BinaryenExpressionGetType(condExpr) == NativeType.I32
|
|
) {
|
|
return _BinaryenConstGetValueI32(condExpr)
|
|
? this.compileExpression(ifThen, contextualType)
|
|
: this.compileExpression(ifElse, contextualType);
|
|
}
|
|
|
|
var ifThenExpr = this.compileExpression(ifThen, contextualType);
|
|
var ifElseExpr = this.compileExpression(ifElse, contextualType);
|
|
return this.module.createIf(condExpr, ifThenExpr, ifElseExpr);
|
|
}
|
|
|
|
compileUnaryPostfixExpression(expression: UnaryPostfixExpression, contextualType: Type): ExpressionRef {
|
|
var module = this.module;
|
|
var currentFunction = this.currentFunction;
|
|
|
|
// make a getter for the expression (also obtains the type)
|
|
var getValue = this.compileExpression(
|
|
expression.operand,
|
|
contextualType == Type.void
|
|
? Type.i32
|
|
: contextualType,
|
|
ConversionKind.NONE,
|
|
false // wrapped below
|
|
);
|
|
var currentType = this.currentType;
|
|
|
|
var op: BinaryOp;
|
|
var nativeType: NativeType;
|
|
var nativeOne: ExpressionRef;
|
|
var possiblyOverflows = false;
|
|
|
|
switch (expression.operator) {
|
|
case Token.PLUS_PLUS: {
|
|
if (currentType.isReference) {
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
expression.range
|
|
);
|
|
return module.createUnreachable();
|
|
}
|
|
switch (currentType.kind) {
|
|
case TypeKind.I8:
|
|
case TypeKind.I16:
|
|
case TypeKind.U8:
|
|
case TypeKind.U16:
|
|
case TypeKind.BOOL: possiblyOverflows = true;
|
|
default: {
|
|
op = BinaryOp.AddI32;
|
|
nativeType = NativeType.I32;
|
|
nativeOne = module.createI32(1);
|
|
break;
|
|
}
|
|
case TypeKind.USIZE: // TODO: check operator overload
|
|
case TypeKind.ISIZE: {
|
|
let options = this.options;
|
|
op = options.isWasm64
|
|
? BinaryOp.AddI64
|
|
: BinaryOp.AddI32;
|
|
nativeType = options.nativeSizeType;
|
|
nativeOne = currentType.toNativeOne(module);
|
|
break;
|
|
}
|
|
case TypeKind.I64:
|
|
case TypeKind.U64: {
|
|
op = BinaryOp.AddI64;
|
|
nativeType = NativeType.I64;
|
|
nativeOne = module.createI64(1);
|
|
break;
|
|
}
|
|
case TypeKind.F32: {
|
|
op = BinaryOp.AddF32;
|
|
nativeType = NativeType.F32;
|
|
nativeOne = module.createF32(1);
|
|
break;
|
|
}
|
|
case TypeKind.F64: {
|
|
op = BinaryOp.AddF64;
|
|
nativeType = NativeType.F64;
|
|
nativeOne = module.createF64(1);
|
|
break;
|
|
}
|
|
case TypeKind.VOID: {
|
|
assert(false);
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
expression.range
|
|
);
|
|
return module.createUnreachable();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case Token.MINUS_MINUS: {
|
|
if (currentType.isReference) {
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
expression.range
|
|
);
|
|
return module.createUnreachable();
|
|
}
|
|
switch (currentType.kind) {
|
|
case TypeKind.I8:
|
|
case TypeKind.I16:
|
|
case TypeKind.U8:
|
|
case TypeKind.U16:
|
|
case TypeKind.BOOL: possiblyOverflows = true;
|
|
default: {
|
|
op = BinaryOp.SubI32;
|
|
nativeType = NativeType.I32;
|
|
nativeOne = module.createI32(1);
|
|
break;
|
|
}
|
|
case TypeKind.USIZE: // TODO: check operator overload
|
|
case TypeKind.ISIZE: {
|
|
let options = this.options;
|
|
op = options.isWasm64
|
|
? BinaryOp.SubI64
|
|
: BinaryOp.SubI32;
|
|
nativeType = options.nativeSizeType;
|
|
nativeOne = currentType.toNativeOne(module);
|
|
break;
|
|
}
|
|
case TypeKind.I64:
|
|
case TypeKind.U64: {
|
|
op = BinaryOp.SubI64;
|
|
nativeType = NativeType.I64;
|
|
nativeOne = module.createI64(1);
|
|
break;
|
|
}
|
|
case TypeKind.F32: {
|
|
op = BinaryOp.SubF32;
|
|
nativeType = NativeType.F32;
|
|
nativeOne = module.createF32(1);
|
|
break;
|
|
}
|
|
case TypeKind.F64: {
|
|
op = BinaryOp.SubF64;
|
|
nativeType = NativeType.F64;
|
|
nativeOne = module.createF64(1);
|
|
break;
|
|
}
|
|
case TypeKind.VOID: {
|
|
assert(false);
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
expression.range
|
|
);
|
|
return module.createUnreachable();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
assert(false);
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
expression.range
|
|
);
|
|
return module.createUnreachable();
|
|
}
|
|
}
|
|
|
|
var setValue: ExpressionRef;
|
|
var tempLocal: Local | null = null;
|
|
|
|
// simplify if dropped anyway
|
|
if (contextualType == Type.void) {
|
|
setValue = module.createBinary(op,
|
|
getValue,
|
|
nativeOne
|
|
);
|
|
|
|
// otherwise use a temp local for the intermediate value
|
|
} else {
|
|
tempLocal = currentFunction.getTempLocal(currentType);
|
|
setValue = module.createBinary(op,
|
|
this.module.createGetLocal(tempLocal.index, nativeType),
|
|
nativeOne
|
|
);
|
|
}
|
|
|
|
if (possiblyOverflows) {
|
|
assert(currentType.is(TypeFlags.SMALL | TypeFlags.INTEGER));
|
|
setValue = makeSmallIntegerWrap(setValue, currentType, module);
|
|
}
|
|
|
|
setValue = this.compileAssignmentWithValue(expression.operand, setValue, false);
|
|
// ^ sets currentType = void
|
|
if (contextualType == Type.void) {
|
|
assert(!tempLocal);
|
|
return setValue;
|
|
}
|
|
|
|
this.currentType = assert(tempLocal).type;
|
|
currentFunction.freeTempLocal(<Local>tempLocal);
|
|
var localIndex = (<Local>tempLocal).index;
|
|
return module.createBlock(null, [
|
|
module.createSetLocal(localIndex, getValue),
|
|
setValue,
|
|
module.createGetLocal(localIndex, nativeType)
|
|
], nativeType);
|
|
}
|
|
|
|
compileUnaryPrefixExpression(
|
|
expression: UnaryPrefixExpression,
|
|
contextualType: Type,
|
|
wrapSmallIntegers: bool = true
|
|
): ExpressionRef {
|
|
var module = this.module;
|
|
var currentType = this.currentType;
|
|
|
|
var possiblyOverflows = false;
|
|
var compound = false;
|
|
var expr: ExpressionRef;
|
|
|
|
switch (expression.operator) {
|
|
case Token.PLUS: {
|
|
if (currentType.isReference) {
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
expression.range
|
|
);
|
|
return module.createUnreachable();
|
|
}
|
|
expr = this.compileExpression(
|
|
expression.operand,
|
|
contextualType == Type.void
|
|
? Type.i32
|
|
: contextualType,
|
|
ConversionKind.NONE,
|
|
false // wrapped below
|
|
);
|
|
currentType = this.currentType;
|
|
possiblyOverflows = currentType.is(TypeFlags.SMALL | TypeFlags.INTEGER); // if operand already did
|
|
break;
|
|
}
|
|
case Token.MINUS: {
|
|
if (currentType.isReference) {
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
expression.range
|
|
);
|
|
return module.createUnreachable();
|
|
}
|
|
if (expression.operand.kind == NodeKind.LITERAL && (
|
|
(<LiteralExpression>expression.operand).literalKind == LiteralKind.INTEGER ||
|
|
(<LiteralExpression>expression.operand).literalKind == LiteralKind.FLOAT
|
|
)) {
|
|
// implicitly negate integer and float literals. also enables proper checking of literal ranges.
|
|
expr = this.compileLiteralExpression(<LiteralExpression>expression.operand, contextualType, true);
|
|
if (this.options.sourceMap) {
|
|
// compileExpression normally does this
|
|
addDebugLocation(expr, expression.range, module, this.currentFunction);
|
|
}
|
|
currentType = this.currentType;
|
|
} else {
|
|
expr = this.compileExpression(
|
|
expression.operand,
|
|
contextualType == Type.void
|
|
? Type.i32
|
|
: contextualType,
|
|
ConversionKind.NONE,
|
|
false // wrapped below
|
|
);
|
|
currentType = this.currentType;
|
|
switch (currentType.kind) {
|
|
case TypeKind.I8:
|
|
case TypeKind.I16:
|
|
case TypeKind.U8:
|
|
case TypeKind.U16:
|
|
case TypeKind.BOOL: possiblyOverflows = true; // or if operand already did
|
|
default: {
|
|
expr = module.createBinary(BinaryOp.SubI32, module.createI32(0), expr);
|
|
break;
|
|
}
|
|
case TypeKind.USIZE: {
|
|
if (currentType.isReference) {
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
expression.range
|
|
);
|
|
return module.createUnreachable();
|
|
}
|
|
// fall-through
|
|
}
|
|
case TypeKind.ISIZE: {
|
|
expr = module.createBinary(
|
|
this.options.isWasm64
|
|
? BinaryOp.SubI64
|
|
: BinaryOp.SubI32,
|
|
currentType.toNativeZero(module),
|
|
expr
|
|
);
|
|
break;
|
|
}
|
|
case TypeKind.I64:
|
|
case TypeKind.U64: {
|
|
expr = module.createBinary(BinaryOp.SubI64, module.createI64(0), expr);
|
|
break;
|
|
}
|
|
case TypeKind.F32: {
|
|
expr = module.createUnary(UnaryOp.NegF32, expr);
|
|
break;
|
|
}
|
|
case TypeKind.F64: {
|
|
expr = module.createUnary(UnaryOp.NegF64, expr);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case Token.PLUS_PLUS: {
|
|
if (currentType.isReference) {
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
expression.range
|
|
);
|
|
return module.createUnreachable();
|
|
}
|
|
compound = true;
|
|
expr = this.compileExpression(
|
|
expression.operand,
|
|
contextualType == Type.void
|
|
? Type.i32
|
|
: contextualType,
|
|
ConversionKind.NONE,
|
|
false // wrapped below
|
|
);
|
|
currentType = this.currentType;
|
|
switch (currentType.kind) {
|
|
case TypeKind.I8:
|
|
case TypeKind.I16:
|
|
case TypeKind.U8:
|
|
case TypeKind.U16:
|
|
case TypeKind.BOOL: possiblyOverflows = true; // or if operand already did
|
|
default: {
|
|
expr = module.createBinary(BinaryOp.AddI32, expr, this.module.createI32(1));
|
|
break;
|
|
}
|
|
case TypeKind.USIZE: {
|
|
if (currentType.isReference) {
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
expression.range
|
|
);
|
|
return module.createUnreachable();
|
|
}
|
|
// fall-through
|
|
}
|
|
case TypeKind.ISIZE: {
|
|
expr = module.createBinary(
|
|
this.options.isWasm64
|
|
? BinaryOp.AddI64
|
|
: BinaryOp.AddI32,
|
|
expr,
|
|
currentType.toNativeOne(module)
|
|
);
|
|
break;
|
|
}
|
|
case TypeKind.I64:
|
|
case TypeKind.U64: {
|
|
expr = module.createBinary(BinaryOp.AddI64, expr, module.createI64(1));
|
|
break;
|
|
}
|
|
case TypeKind.F32: {
|
|
expr = module.createBinary(BinaryOp.AddF32, expr, module.createF32(1));
|
|
break;
|
|
}
|
|
case TypeKind.F64: {
|
|
expr = module.createBinary(BinaryOp.AddF64, expr, module.createF64(1));
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case Token.MINUS_MINUS: {
|
|
if (currentType.isReference) {
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
expression.range
|
|
);
|
|
return module.createUnreachable();
|
|
}
|
|
compound = true;
|
|
expr = this.compileExpression(
|
|
expression.operand,
|
|
contextualType == Type.void
|
|
? Type.i32
|
|
: contextualType,
|
|
ConversionKind.NONE,
|
|
false // wrapped below
|
|
);
|
|
currentType = this.currentType;
|
|
switch (currentType.kind) {
|
|
case TypeKind.I8:
|
|
case TypeKind.I16:
|
|
case TypeKind.U8:
|
|
case TypeKind.U16:
|
|
case TypeKind.BOOL: possiblyOverflows = true; // or if operand already did
|
|
default: {
|
|
expr = module.createBinary(BinaryOp.SubI32, expr, module.createI32(1));
|
|
break;
|
|
}
|
|
case TypeKind.USIZE: {
|
|
if (currentType.isReference) {
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
expression.range
|
|
);
|
|
return module.createUnreachable();
|
|
}
|
|
// fall-through
|
|
}
|
|
case TypeKind.ISIZE: {
|
|
expr = module.createBinary(
|
|
this.options.isWasm64
|
|
? BinaryOp.SubI64
|
|
: BinaryOp.SubI32,
|
|
expr,
|
|
currentType.toNativeOne(module)
|
|
);
|
|
break;
|
|
}
|
|
case TypeKind.I64:
|
|
case TypeKind.U64: {
|
|
expr = module.createBinary(BinaryOp.SubI64, expr, module.createI64(1));
|
|
break;
|
|
}
|
|
case TypeKind.F32: {
|
|
expr = module.createBinary(BinaryOp.SubF32, expr, module.createF32(1));
|
|
break;
|
|
}
|
|
case TypeKind.F64: {
|
|
expr = module.createBinary(BinaryOp.SubF64, expr, module.createF64(1));
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case Token.EXCLAMATION: {
|
|
expr = this.compileExpression(
|
|
expression.operand,
|
|
contextualType == Type.void
|
|
? Type.i32
|
|
: contextualType,
|
|
ConversionKind.NONE,
|
|
true // must wrap small integers
|
|
);
|
|
expr = makeIsFalseish(expr, this.currentType, module);
|
|
this.currentType = Type.bool;
|
|
break;
|
|
}
|
|
case Token.TILDE: {
|
|
if (currentType.isReference) {
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
expression.range
|
|
);
|
|
return module.createUnreachable();
|
|
}
|
|
expr = this.compileExpression(
|
|
expression.operand,
|
|
contextualType == Type.void
|
|
? Type.i32
|
|
: contextualType.is(TypeFlags.FLOAT)
|
|
? Type.i64
|
|
: contextualType,
|
|
contextualType == Type.void
|
|
? ConversionKind.NONE
|
|
: ConversionKind.IMPLICIT,
|
|
false // retains low bits of small integers
|
|
);
|
|
currentType = this.currentType;
|
|
switch (currentType.kind) {
|
|
case TypeKind.I8:
|
|
case TypeKind.I16:
|
|
case TypeKind.U8:
|
|
case TypeKind.U16:
|
|
case TypeKind.BOOL: possiblyOverflows = true; // or if operand already did
|
|
default: {
|
|
expr = module.createBinary(BinaryOp.XorI32, expr, module.createI32(-1));
|
|
break;
|
|
}
|
|
case TypeKind.USIZE: {
|
|
if (currentType.isReference) {
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
expression.range
|
|
);
|
|
return module.createUnreachable();
|
|
}
|
|
// fall-through
|
|
}
|
|
case TypeKind.ISIZE: {
|
|
expr = module.createBinary(
|
|
this.options.isWasm64
|
|
? BinaryOp.XorI64
|
|
: BinaryOp.XorI32,
|
|
expr,
|
|
currentType.toNativeNegOne(module)
|
|
);
|
|
break;
|
|
}
|
|
case TypeKind.I64:
|
|
case TypeKind.U64: {
|
|
expr = module.createBinary(BinaryOp.XorI64, expr, module.createI64(-1, -1));
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case Token.TYPEOF: {
|
|
// it might make sense to implement typeof in a way that a generic function can detect
|
|
// whether its type argument is a class type or string. that could then be used, for
|
|
// example, to generate hash codes for sets and maps, depending on the kind of type
|
|
// parameter we have. ideally the comparison would not involve actual string comparison and
|
|
// limit available operations to hard-coded string literals.
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
expression.range
|
|
);
|
|
return module.createUnreachable();
|
|
}
|
|
default: {
|
|
assert(false);
|
|
this.error(
|
|
DiagnosticCode.Operation_not_supported,
|
|
expression.range
|
|
);
|
|
return module.createUnreachable();
|
|
}
|
|
}
|
|
if (possiblyOverflows && wrapSmallIntegers) {
|
|
assert(currentType.is(TypeFlags.SMALL | TypeFlags.INTEGER));
|
|
expr = makeSmallIntegerWrap(expr, currentType, module);
|
|
}
|
|
return compound
|
|
? this.compileAssignmentWithValue(expression.operand, expr, contextualType != Type.void)
|
|
: expr;
|
|
}
|
|
}
|
|
|
|
// helpers
|
|
|
|
/** Adds the debug location of the specified expression at the specified range to the source map. */
|
|
function addDebugLocation(expr: ExpressionRef, range: Range, module: Module, currentFunction: Function): void {
|
|
var source = range.source;
|
|
if (source.debugInfoIndex < 0) {
|
|
source.debugInfoIndex = module.addDebugInfoFile(source.normalizedPath);
|
|
}
|
|
range.debugInfoRef = expr;
|
|
if (!currentFunction.debugLocations) currentFunction.debugLocations = [];
|
|
currentFunction.debugLocations.push(range);
|
|
}
|
|
|
|
/** Wraps a 32-bit integer expression so it evaluates to a valid value of the specified type. */
|
|
export function makeSmallIntegerWrap(expr: ExpressionRef, type: Type, module: Module): ExpressionRef {
|
|
switch (type.kind) {
|
|
case TypeKind.I8: {
|
|
return module.createBinary(BinaryOp.ShrI32,
|
|
module.createBinary(BinaryOp.ShlI32,
|
|
expr,
|
|
module.createI32(24)
|
|
),
|
|
module.createI32(24)
|
|
);
|
|
}
|
|
case TypeKind.I16: {
|
|
return module.createBinary(BinaryOp.ShrI32,
|
|
module.createBinary(BinaryOp.ShlI32,
|
|
expr,
|
|
module.createI32(16)
|
|
),
|
|
module.createI32(16)
|
|
);
|
|
}
|
|
case TypeKind.U8: {
|
|
return module.createBinary(BinaryOp.AndI32,
|
|
expr,
|
|
module.createI32(0xff)
|
|
);
|
|
}
|
|
case TypeKind.U16: {
|
|
return module.createBinary(BinaryOp.AndI32,
|
|
expr,
|
|
module.createI32(0xffff)
|
|
);
|
|
}
|
|
case TypeKind.BOOL: {
|
|
return module.createBinary(BinaryOp.AndI32,
|
|
expr,
|
|
module.createI32(0x1)
|
|
);
|
|
}
|
|
default: {
|
|
assert(false);
|
|
return expr;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Creates a comparison whether an expression is not 'true' in a broader sense. */
|
|
export function makeIsFalseish(expr: ExpressionRef, type: Type, module: Module): ExpressionRef {
|
|
switch (type.kind) {
|
|
default: { // any native i32
|
|
return module.createUnary(UnaryOp.EqzI32, expr);
|
|
}
|
|
case TypeKind.I64:
|
|
case TypeKind.U64: {
|
|
return module.createUnary(UnaryOp.EqzI64, expr);
|
|
}
|
|
case TypeKind.USIZE: // TODO: strings?
|
|
case TypeKind.ISIZE: {
|
|
return module.createUnary(type.size == 64 ? UnaryOp.EqzI64 : UnaryOp.EqzI32, expr);
|
|
}
|
|
case TypeKind.F32: {
|
|
return module.createBinary(BinaryOp.EqF32, expr, module.createF32(0));
|
|
}
|
|
case TypeKind.F64: {
|
|
return module.createBinary(BinaryOp.EqF64, expr, module.createF64(0));
|
|
}
|
|
case TypeKind.VOID: {
|
|
assert(false);
|
|
return module.createI32(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Creates a comparison whether an expression is 'true' in a broader sense. */
|
|
export function makeIsTrueish(expr: ExpressionRef, type: Type, module: Module): ExpressionRef {
|
|
switch (type.kind) {
|
|
default: { // any native i32
|
|
return expr;
|
|
}
|
|
case TypeKind.I64:
|
|
case TypeKind.U64: {
|
|
return module.createBinary(BinaryOp.NeI64, expr, module.createI64(0));
|
|
}
|
|
case TypeKind.USIZE: // TODO: strings?
|
|
case TypeKind.ISIZE: {
|
|
return type.size == 64
|
|
? module.createBinary(BinaryOp.NeI64, expr, module.createI64(0))
|
|
: expr;
|
|
}
|
|
case TypeKind.F32: {
|
|
return module.createBinary(BinaryOp.NeF32, expr, module.createF32(0));
|
|
}
|
|
case TypeKind.F64: {
|
|
return module.createBinary(BinaryOp.NeF64, expr, module.createF64(0));
|
|
}
|
|
case TypeKind.VOID: {
|
|
assert(false);
|
|
return module.createI32(0);
|
|
}
|
|
}
|
|
}
|