diff --git a/bin/asc.js b/bin/asc.js index e48a8e6e..69b096cc 100644 --- a/bin/asc.js +++ b/bin/asc.js @@ -1,6 +1,7 @@ var fs = require("fs"); var path = require("path"); var minimist = require("minimist"); +var glob = require("glob"); var assemblyscript; var isDev = true; @@ -88,6 +89,17 @@ function checkDiagnostics(parser) { process.exit(1); } +// Include standard library +if (!args.noLib) { + var stdlibDir = path.join(__dirname + "..", "std", "assembly"); + glob.sync("*.ts", { cwd: stdlibDir }).forEach(file => { + var nextPath = "std/" + file; + var nextText = fs.readFileSync(path.join(stdlibDir, file), { encoding: "utf8" }); + parser = assemblyscript.parseFile(nextText, nextPath, parser, false); + }); +} + +// Include entry files args._.forEach(filename => { var entryPath = filename.replace(/\\/g, "/").replace(/(\.ts|\/)$/, ""); var entryDir = path.dirname(entryPath); @@ -105,11 +117,12 @@ args._.forEach(filename => { } } - parser = assemblyscript.parseFile(entryText, entryPath, parser, true); - var nextPath; var nextText; + // Load entry text + parser = assemblyscript.parseFile(entryText, entryPath, parser, true); + while ((nextPath = parser.nextFile()) != null) { try { nextText = fs.readFileSync(nextPath + ".ts", { encoding: "utf8" }); diff --git a/bin/asc.json b/bin/asc.json index b6d9f113..99aedaa2 100644 --- a/bin/asc.json +++ b/bin/asc.json @@ -47,6 +47,10 @@ "desc": "Disables assertions.", "type": "boolean" }, + "noLib": { + "desc": "Does not include the standard library.", + "type": "boolean" + }, "trapMode": { "desc": [ "Sets the trap mode to use.", diff --git a/package-lock.json b/package-lock.json index d468f5ef..ec3879df 100644 --- a/package-lock.json +++ b/package-lock.json @@ -186,8 +186,7 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "base64-js": { "version": "1.2.1", @@ -222,7 +221,6 @@ "version": "1.1.8", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", - "dev": true, "requires": { "balanced-match": "1.0.0", "concat-map": "0.0.1" @@ -427,8 +425,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "console-browserify": { "version": "1.1.0", @@ -852,8 +849,7 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "get-caller-file": { "version": "1.0.2", @@ -871,7 +867,6 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, "requires": { "fs.realpath": "1.0.0", "inflight": "1.0.6", @@ -970,7 +965,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "requires": { "once": "1.4.0", "wrappy": "1.0.2" @@ -979,8 +973,7 @@ "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "interpret": { "version": "1.1.0", @@ -1326,7 +1319,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, "requires": { "brace-expansion": "1.1.8" } @@ -1462,7 +1454,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1.0.2" } @@ -1560,8 +1551,7 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-key": { "version": "2.0.1", @@ -2347,8 +2337,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "xtend": { "version": "4.0.1", diff --git a/package.json b/package.json index 1bf50161..0379e175 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "dependencies": { "@types/node": "^8.0.58", "binaryen": "40.0.0-nightly.20171209", + "glob": "^7.1.2", "minimist": "^1.2.0", "source-map-support": "^0.5.0" }, @@ -24,7 +25,6 @@ "@types/minimist": "^1.2.0", "chalk": "^2.3.0", "diff": "^3.4.0", - "glob": "^7.1.2", "long": "^3.2.0", "ts-loader": "^3.2.0", "ts-node": "^3.3.0", diff --git a/src/ast.ts b/src/ast.ts index 352e5dcf..7aba0ca6 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -109,6 +109,7 @@ export enum NodeKind { SUPER, THIS, TRUE, + CONSTRUCTOR, UNARYPOSTFIX, UNARYPREFIX, @@ -240,6 +241,12 @@ export abstract class Expression extends Node { return expr; } + static createConstructor(range: Range): ConstructorExpression { + const expr: ConstructorExpression = new ConstructorExpression(); + expr.range = range; + return expr; + } + static createElementAccess(expression: Expression, element: Expression, range: Range): ElementAccessExpression { const expr: ElementAccessExpression = new ElementAccessExpression(); expr.range = range; @@ -357,6 +364,16 @@ export abstract class Expression extends Node { } } +export class IdentifierExpression extends Expression { + + kind = NodeKind.IDENTIFIER; + name: string; + + serialize(sb: string[]): void { + sb.push(this.name); + } +} + export const enum LiteralKind { FLOAT, INTEGER, @@ -459,6 +476,11 @@ export class CallExpression extends Expression { } } +export class ConstructorExpression extends IdentifierExpression { + kind = NodeKind.CONSTRUCTOR; + name = "this"; +} + export class ElementAccessExpression extends Expression { kind = NodeKind.ELEMENTACCESS; @@ -483,16 +505,6 @@ export class FloatLiteralExpression extends LiteralExpression { } } -export class IdentifierExpression extends Expression { - - kind = NodeKind.IDENTIFIER; - name: string; - - serialize(sb: string[]): void { - sb.push(this.name); - } -} - export class IntegerLiteralExpression extends LiteralExpression { literalKind = LiteralKind.INTEGER; @@ -641,8 +653,9 @@ export enum ModifierKind { PUBLIC, PRIVATE, PROTECTED, + READONLY, GET, - SET + SET, } export abstract class Statement extends Node { @@ -1052,7 +1065,7 @@ export class ClassDeclaration extends DeclarationStatement { if (this._cachedInternalName !== null) return this._cachedInternalName; const globalDecorator: Decorator | null = this.decorators ? getDecoratorByName("global", this.decorators) : null; - if (globalDecorator && globalDecorator.expression.kind == NodeKind.IDENTIFIER && (globalDecorator.expression).name == "global") + if (globalDecorator) return this._cachedInternalName = this.identifier.name; else return this._cachedInternalName = mangleInternalName(this); @@ -1589,9 +1602,10 @@ export class Modifier extends Node { case ModifierKind.PRIVATE: sb.push("private"); break; case ModifierKind.PROTECTED: sb.push("protected"); break; case ModifierKind.PUBLIC: sb.push("public"); break; + case ModifierKind.READONLY: sb.push("readonly"); break; case ModifierKind.SET: sb.push("set"); break; case ModifierKind.STATIC: sb.push("static"); break; - default: sb.push("INVALID"); break; + default: throw new Error("unexpected modifier kind"); } } } @@ -1772,16 +1786,21 @@ export function hasModifier(kind: ModifierKind, modifiers: Modifier[] | null): b return false; } -export function getDecoratorByName(name: string, decorators: Decorator[]): Decorator | null { - for (let i: i32 = 0, k: i32 = decorators.length; i < k; ++i) { - const decorator: Decorator = decorators[i]; - const expression: Expression = decorator.expression; - if (expression.kind == NodeKind.IDENTIFIER && (expression).name == name) - return decorator; - } +function getDecoratorByName(name: string, decorators: Decorator[] | null): Decorator | null { + if (decorators) + for (let i: i32 = 0, k: i32 = decorators.length; i < k; ++i) { + const decorator: Decorator = decorators[i]; + const expression: Expression = decorator.expression; + if (expression.kind == NodeKind.IDENTIFIER && (expression).name == name) + return decorator; + } return null; } +export function hasDecorator(name: string, decorators: Decorator[] | null): bool { + return getDecoratorByName(name, decorators) != null; +} + export function serialize(node: Node, indent: i32 = 0): string { const sb: string[] = new Array(); // shared builder could grow too much node.serialize(sb); diff --git a/src/builtins.ts b/src/builtins.ts index 16cfcda7..460af549 100644 --- a/src/builtins.ts +++ b/src/builtins.ts @@ -1,6 +1,6 @@ import { Compiler, Target, ConversionKind, typeToNativeType, typeToNativeOne, typeToNativeZero } from "./compiler"; import { DiagnosticCode } from "./diagnostics"; -import { Node, Expression } from "./ast"; +import { Node, Expression, IdentifierExpression } from "./ast"; import { Type } from "./types"; import { Module, ExpressionRef, UnaryOp, BinaryOp, HostOp, NativeType, FunctionTypeRef } from "./module"; import { Program, ElementFlags, Element, Global, FunctionPrototype, Local } from "./program"; @@ -9,6 +9,9 @@ import { Program, ElementFlags, Element, Global, FunctionPrototype, Local } from export function initialize(program: Program): void { // math + addConstant(program, "NaN", Type.f64); + addConstant(program, "Infinity", Type.f64); + addFunction(program, "isNaN", true); addFunction(program, "isFinite", true); addFunction(program, "clz", true); @@ -96,12 +99,24 @@ export function initialize(program: Program): void { if (program.target == Target.WASM64) { program.elements.set("isize", program.elements.get("i64")); program.elements.set("usize", program.elements.get("u64")); + addConstant(program, "HEAP_START", Type.usize64); } else { program.elements.set("isize", program.elements.get("i32")); program.elements.set("usize", program.elements.get("u32")); + addConstant(program, "HEAP_START", Type.usize32); } } +/** Adds a built-in global to the specified program. */ +function addConstant(program: Program, name: string, type: Type): Global { + const global: Global = new Global(program, name, null, null); + global.isBuiltIn = true; + global.isConstant = true; + global.type = type; + program.elements.set(name, global); + return global; +} + /** Adds a built-in function to the specified program. */ function addFunction(program: Program, name: string, isGeneric: bool = false): FunctionPrototype { let prototype: FunctionPrototype = new FunctionPrototype(program, name, name, null, null); @@ -111,6 +126,29 @@ function addFunction(program: Program, name: string, isGeneric: bool = false): F return prototype; } +export function compileGetGlobal(compiler: Compiler, global: Global): ExpressionRef { + switch (global.internalName) { + + case "NaN": + if (compiler.currentType == Type.f32) + return compiler.module.createF32(NaN); + compiler.currentType = Type.f64; + return compiler.module.createF64(NaN); + + case "Infinity": + if (compiler.currentType == Type.f32) + return compiler.module.createF32(Infinity); + compiler.currentType = Type.f64; + return compiler.module.createF64(Infinity); + + case "HEAP_START": // never inlined + return compiler.module.createGetGlobal("HEAP_START", typeToNativeType(global.type)); + + default: + throw new Error("not implemented: " + global.internalName); + } +} + /** Compiles a call to a built-in function. */ export function compileCall(compiler: Compiler, prototype: FunctionPrototype, typeArguments: Type[], operands: Expression[], reportNode: Node): ExpressionRef { const module: Module = compiler.module; diff --git a/src/compiler.ts b/src/compiler.ts index bc931f0d..a2073fa1 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -1,4 +1,4 @@ -import { compileCall as compileBuiltinCall, initialize } from "./builtins"; +import { compileCall as compileBuiltinCall, compileGetGlobal as compileBuiltinGetGlobal, initialize } from "./builtins"; import { PATH_DELIMITER } from "./constants"; import { DiagnosticCode, DiagnosticEmitter } from "./diagnostics"; import { @@ -324,6 +324,10 @@ export class Compiler extends DiagnosticEmitter { compileGlobal(global: Global): bool { if (global.isCompiled) return true; + if (global.isBuiltIn) + if (compileBuiltinGetGlobal(this, global)) + return true; + const declaration: VariableLikeDeclarationStatement | null = global.declaration; let type: Type | null = global.type; if (!type) { @@ -376,6 +380,7 @@ export class Compiler extends DiagnosticEmitter { initializer = typeToNativeZero(this.module, type); } else throw new Error("unexpected missing declaration or constant value"); + const internalName: string = global.internalName; if (initializeInStart) { this.module.addGlobal(internalName, nativeType, true, typeToNativeZero(this.module, type)); @@ -1606,14 +1611,15 @@ export class Compiler extends DiagnosticEmitter { } if (element.kind == ElementKind.GLOBAL) { - this.compileGlobal(element); - if (!(element).isMutable) + if (!this.compileGlobal(element)) + return this.module.createUnreachable(); + this.currentType = (element).type; + if (!(element).isMutable) { this.error(DiagnosticCode.Cannot_assign_to_0_because_it_is_a_constant_or_a_read_only_property, expression.range, element.internalName); + return this.module.createUnreachable(); + } if (tee) { - if (!(element).type) - return this.module.createUnreachable(); const globalNativeType: NativeType = typeToNativeType((element).type); - this.currentType = (element).type; return this.module.createBlock(null, [ // teeGlobal this.module.createSetGlobal((element).internalName, valueWithCorrectType), this.module.createGetGlobal((element).internalName, globalNativeType) @@ -1759,22 +1765,6 @@ export class Compiler extends DiagnosticEmitter { this.error(DiagnosticCode._this_cannot_be_referenced_in_current_location, expression.range); this.currentType = this.options.target == Target.WASM64 ? Type.u64 : Type.u32; return this.module.createUnreachable(); - - case NodeKind.IDENTIFIER: - // TODO: some sort of resolveIdentifier maybe - if ((expression).name == "NaN") { - if (this.currentType == Type.f32) - return this.module.createF32(NaN); - this.currentType = Type.f64; - return this.module.createF64(NaN); - } - if ((expression).name == "Infinity") { - if (this.currentType == Type.f32) - return this.module.createF32(Infinity); - this.currentType = Type.f64; - return this.module.createF64(Infinity); - } - break; } const element: Element | null = this.program.resolveElement(expression, this.currentFunction); // reports @@ -1789,6 +1779,9 @@ export class Compiler extends DiagnosticEmitter { // global if (element.kind == ElementKind.GLOBAL) { + if (element.isBuiltIn) + return compileBuiltinGetGlobal(this, element); + const global: Global = element; if (!this.compileGlobal(global)) // reports return this.module.createUnreachable(); diff --git a/src/diagnosticMessages.generated.ts b/src/diagnosticMessages.generated.ts index 6323826a..d0f5d1fa 100644 --- a/src/diagnosticMessages.generated.ts +++ b/src/diagnosticMessages.generated.ts @@ -66,6 +66,7 @@ export enum DiagnosticCode { Cannot_invoke_an_expression_whose_type_lacks_a_call_signature_Type_0_has_no_compatible_call_signatures = 2349, The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access = 2357, The_left_hand_side_of_an_assignment_expression_must_be_a_variable_or_a_property_access = 2364, + Constructor_implementation_is_missing = 2390, Function_implementation_is_missing_or_not_immediately_following_the_declaration = 2391, Duplicate_function_implementation = 2393, Export_declaration_conflicts_with_exported_declaration_of_0 = 2484, @@ -145,6 +146,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string { case 2349: return "Cannot invoke an expression whose type lacks a call signature. Type '{0}' has no compatible call signatures."; case 2357: return "The operand of an increment or decrement operator must be a variable or a property access."; case 2364: return "The left-hand side of an assignment expression must be a variable or a property access."; + case 2390: return "Constructor implementation is missing."; case 2391: return "Function implementation is missing or not immediately following the declaration."; case 2393: return "Duplicate function implementation."; case 2484: return "Export declaration conflicts with exported declaration of '{0}'."; diff --git a/src/diagnosticMessages.json b/src/diagnosticMessages.json index dcdbb6b7..dd29b980 100644 --- a/src/diagnosticMessages.json +++ b/src/diagnosticMessages.json @@ -66,6 +66,7 @@ "Cannot invoke an expression whose type lacks a call signature. Type '{0}' has no compatible call signatures.": 2349, "The operand of an increment or decrement operator must be a variable or a property access.": 2357, "The left-hand side of an assignment expression must be a variable or a property access.": 2364, + "Constructor implementation is missing.": 2390, "Function implementation is missing or not immediately following the declaration.": 2391, "Duplicate function implementation.": 2393, "Export declaration conflicts with exported declaration of '{0}'.": 2484, diff --git a/src/parser.ts b/src/parser.ts index 0172623b..486fc64e 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -694,6 +694,9 @@ export class Parser extends DiagnosticEmitter { else if (tn.skip(Token.ABSTRACT)) modifiers = addModifier(Statement.createModifier(ModifierKind.ABSTRACT, tn.range()), modifiers); + if (tn.skip(Token.READONLY)) + modifiers = addModifier(Statement.createModifier(ModifierKind.READONLY, tn.range()), modifiers); + let isGetter: bool = false; let isSetter: bool = false; if (tn.skip(Token.GET)) { @@ -704,16 +707,23 @@ export class Parser extends DiagnosticEmitter { isSetter = true; } - if (tn.skip(Token.IDENTIFIER)) { - const identifier: IdentifierExpression = Expression.createIdentifier(tn.readIdentifier(), tn.range()); + if (tn.skip(Token.IDENTIFIER) || tn.skip(Token.CONSTRUCTOR)) { + const identifier: IdentifierExpression = tn.token == Token.CONSTRUCTOR + ? Expression.createConstructor(tn.range()) + : Expression.createIdentifier(tn.readIdentifier(), tn.range()); let typeParameters: TypeParameter[] | null; if (tn.skip(Token.LESSTHAN)) { + if (identifier.kind == NodeKind.CONSTRUCTOR) + this.error(DiagnosticCode.Type_parameters_cannot_appear_on_a_constructor_declaration, tn.range()); // recoverable typeParameters = this.parseTypeParameters(tn); if (!typeParameters) return null; } else typeParameters = []; + if (identifier.kind == NodeKind.CONSTRUCTOR && tn.peek() != Token.OPENPAREN) + this.error(DiagnosticCode.Constructor_implementation_is_missing, tn.range()); + // method: '(' Parameters (':' Type)? '{' Statement* '}' ';'? if (tn.skip(Token.OPENPAREN)) { let parameters = this.parseParameters(tn); @@ -729,14 +739,18 @@ export class Parser extends DiagnosticEmitter { } let returnType: TypeNode | null = null; if (tn.skip(Token.COLON)) { - returnType = this.parseType(tn, isSetter); + if (identifier.kind == NodeKind.CONSTRUCTOR) + this.error(DiagnosticCode.Type_annotation_cannot_appear_on_a_constructor_declaration, tn.range()); + else if (isSetter) + this.error(DiagnosticCode.A_set_accessor_cannot_have_a_return_type_annotation, tn.range()); + returnType = this.parseType(tn, identifier.kind == NodeKind.CONSTRUCTOR || isSetter); if (!returnType) return null; } else { if (isSetter) { if (parameters.length) returnType = parameters[0].type; - } else + } else if (identifier.kind != NodeKind.CONSTRUCTOR) this.error(DiagnosticCode.Type_expected, tn.range()); // recoverable } let statements: Statement[] | null = null; @@ -1323,36 +1337,21 @@ export class Parser extends DiagnosticEmitter { let p: Precedence = determinePrecedencePrefix(token); if (p != Precedence.INVALID) { - const operand: Expression | null = this.parseExpression(tn, p); - if (!operand) - return null; + let operand: Expression | null // TODO: SpreadExpression, YieldExpression (currently become unsupported UnaryPrefixExpressions) // NewExpression if (token == Token.NEW) { - if (operand.kind == NodeKind.IDENTIFIER || operand.kind == NodeKind.PROPERTYACCESS) { - const args: Expression[] = new Array(); - if (tn.skip(Token.OPENPAREN)) { - if (tn.peek() != Token.CLOSEPAREN) { - do { - expr = this.parseExpression(tn, Precedence.COMMA + 1); - if (!expr) - return null; - args.push(expr); - } while (tn.skip(Token.COMMA)); - } - if (!tn.skip(Token.CLOSEPAREN)) { - this.error(DiagnosticCode._0_expected, tn.range(), ")"); - return null; - } - } - return Expression.createNew(operand, [], args, tn.range(startPos, tn.pos)); - } else { - this.error(DiagnosticCode.Identifier_expected, tn.range()); + operand = this.parseExpression(tn, Precedence.CALL); + if (!operand) return null; - } - } + if (operand.kind == NodeKind.CALL) + return Expression.createNew((operand).expression, (operand).typeArguments, (operand).arguments, tn.range(startPos, tn.pos)); + this.error(DiagnosticCode.Operation_not_supported, tn.range()); + return null; + } else + operand = this.parseExpression(tn, p); // UnaryPrefixExpression if (token == Token.PLUS_PLUS || token == Token.MINUS_MINUS) @@ -1406,7 +1405,7 @@ export class Parser extends DiagnosticEmitter { this.error(DiagnosticCode._0_expected, tn.range(), ">"); return null; } - expr = this.parseExpressionPrefix(tn); + expr = this.parseExpression(tn, Precedence.CALL); if (!expr) return null; return Expression.createAssertion(AssertionKind.PREFIX, expr, toType, tn.range(startPos, tn.pos)); @@ -1416,6 +1415,15 @@ export class Parser extends DiagnosticEmitter { case Token.IDENTIFIER: return Expression.createIdentifier(tn.readIdentifier(), tn.range(startPos, tn.pos)); + case Token.THIS: + return Expression.createThis(tn.range(startPos, tn.pos)); + + case Token.CONSTRUCTOR: + return Expression.createConstructor(tn.range(startPos, tn.pos)); + + case Token.SUPER: + return Expression.createSuper(tn.range(startPos, tn.pos)); + // StringLiteralExpression case Token.STRINGLITERAL: return Expression.createStringLiteral(tn.readString(), tn.range(startPos, tn.pos)); diff --git a/src/program.ts b/src/program.ts index 639bb22f..056f1c92 100644 --- a/src/program.ts +++ b/src/program.ts @@ -19,6 +19,7 @@ import { LiteralKind, PropertyAccessExpression, StringLiteralExpression, + CallExpression, ClassDeclaration, DeclarationStatement, @@ -101,7 +102,9 @@ export class Program extends DiagnosticEmitter { ["bool", Type.bool], ["f32", Type.f32], ["f64", Type.f64], - ["void", Type.void] + ["void", Type.void], + ["number", Type.f64], + ["boolean", Type.bool] ]); initializeBuiltins(this); @@ -779,27 +782,26 @@ export class Program extends DiagnosticEmitter { // static or instance property (incl. enum values) or method } else if (expression.kind == NodeKind.PROPERTYACCESS) { return this.resolvePropertyAccess(expression, contextualFunction); + + // instantiation + } else if (expression.kind == NodeKind.NEW) { + return this.resolveElement((expression).expression, contextualFunction); } throw new Error("not implemented: " + expression.kind); } } -function checkGlobalDecorator(decorators: Decorator[]): string | null { - for (let i: i32 = 0, k: i32 = decorators.length; i < k; ++i) { - const decorator: Decorator = decorators[i]; - const expression: Expression = decorator.expression; - const args: Expression[] = decorator.arguments; - if (expression.kind == NodeKind.IDENTIFIER && args.length <= 1 && (expression).name == "global") { - if (args.length) { - const firstArg: Expression = args[0]; - if (firstArg.kind == NodeKind.LITERAL && (firstArg).literalKind == LiteralKind.STRING) - return (firstArg).value; - } else - return ""; // instead inherits declaration identifier +function hasDecorator(name: string, decorators: Decorator[] | null): bool { + if (decorators) + for (let i: i32 = 0, k: i32 = decorators.length; i < k; ++i) { + const decorator: Decorator = decorators[i]; + const expression: Expression = decorator.expression; + const args: Expression[] = decorator.arguments; + if (expression.kind == NodeKind.IDENTIFIER && args.length <= 1 && (expression).name == name) + return true; } - } - return null; + return false; } /** Indicates the specific kind of an {@link Element}. */ @@ -857,7 +859,11 @@ export enum ElementFlags { /** Is getter. */ GETTER = 1 << 9, /** Is setter. */ - SETTER = 1 << 10 + SETTER = 1 << 10, + /** Is global. */ + GLOBAL = 1 << 11, + /** Is read-only. */ + READONLY = 1 << 12 } /** Base class of all program elements. */ @@ -919,9 +925,13 @@ export abstract class Element { /** Whether an instance member or not. */ get isInstance(): bool { return (this.flags & ElementFlags.INSTANCE) != 0; } set isInstance(is: bool) { if (is) this.flags |= ElementFlags.INSTANCE; else this.flags &= ~ElementFlags.INSTANCE; } + + /** Whether a member of the global namespace or not. */ + get isGlobal(): bool { return (this.flags & ElementFlags.GLOBAL) != 0; } + set isGlobal(is: bool) { if (is) this.flags |= ElementFlags.GLOBAL; else this.flags &= ~ElementFlags.GLOBAL; } } -/** A namespace. Also the base class of other namespace-like program elements. */ +/** A namespace. */ export class Namespace extends Element { kind = ElementKind.NAMESPACE; @@ -985,8 +995,7 @@ export class EnumValue extends Element { constructor(enm: Enum, program: Program, internalName: string, declaration: EnumValueDeclaration | null = null) { super(program, internalName); this.enum = enm; - if (!(this.declaration = declaration)) - this.hasConstantValue = true; // built-ins have constant values + this.declaration = declaration; } } @@ -1185,7 +1194,7 @@ export class FunctionPrototype extends Element { resolveInclTypeArguments(typeArgumentNodes: TypeNode[] | null, contextualTypeArguments: Map | null, alternativeReportNode: Node | null): Function | null { let resolvedTypeArguments: Type[] | null; if (this.isGeneric) { - assert(typeArgumentNodes != null && typeArgumentNodes.length != 0); + assert(typeArgumentNodes != null && typeArgumentNodes.length != 0, "" + this); if (!this.declaration) throw new Error("missing declaration"); resolvedTypeArguments = this.program.resolveTypeArguments(this.declaration.typeParameters, typeArgumentNodes, contextualTypeArguments, alternativeReportNode); @@ -1372,11 +1381,17 @@ export class FieldPrototype extends Element { for (let i: i32 = 0, k = this.declaration.modifiers.length; i < k; ++i) { switch (this.declaration.modifiers[i].modifierKind) { case ModifierKind.EXPORT: this.isExported = true; break; + case ModifierKind.READONLY: this.isReadonly = true; break; + case ModifierKind.STATIC: break; // already handled default: throw new Error("unexpected modifier"); } } } } + + /** Whether the field is read-only or not. */ + get isReadonly(): bool { return (this.flags & ElementFlags.READONLY) != 0; } + set isReadonly(is: bool) { if (is) this.flags |= ElementFlags.READONLY; else this.flags &= ~ElementFlags.READONLY; } } /** A resolved instance field. */ diff --git a/std/assembly/array.ts b/std/assembly/array.ts index 7df7bd62..47e84bda 100644 --- a/std/assembly/array.ts +++ b/std/assembly/array.ts @@ -2,6 +2,7 @@ // 1. C-like with no 'length' or 'push' // 2. Descriptors that can be constructed from lower level arrays +@global() class Array { readonly capacity: i32; @@ -33,4 +34,6 @@ class Array { this.ptr = 0; Heap.dispose(changetype(this)); } + + static test(): void {} } diff --git a/std/assembly/heap.ts b/std/assembly/heap.ts index 32aae957..922d6df9 100644 --- a/std/assembly/heap.ts +++ b/std/assembly/heap.ts @@ -5,7 +5,6 @@ const ALIGN_MASK: usize = ALIGN_SIZE - 1; let HEAP_OFFSET: usize = HEAP_START; // HEAP_START is a constant generated by the compiler @global() -@struct() class Heap { static allocate(size: usize): usize { @@ -180,6 +179,4 @@ class Heap { } return dest; } - - private constructor() {} } diff --git a/std/portable.js b/std/portable.js index 4a0c0c74..bf37aa9e 100644 --- a/std/portable.js +++ b/std/portable.js @@ -66,9 +66,12 @@ globalScope["sqrt"] = Math.sqrt; globalScope["trunc"] = Math.trunc; function UnreachableError() { - this.stack = new Error().stack; + if (Error.captureStackTrace) + Error.captureStackTrace(this, UnreachableError); + else + this.stack = this.name + ": " + this.message + "\n" + new Error().stack; } -UnreachableError.prototype = new Error; +UnreachableError.prototype = Object.create(Error.prototype); UnreachableError.prototype.name = "UnreachableError"; UnreachableError.prototype.message = "unreachable"; @@ -76,9 +79,12 @@ globalScope["unreachable"] = function unreachable() { throw new UnreachableError function AssertionError(message) { this.message = message || "assertion failed"; - this.stack = new Error().stack; + if (Error.captureStackTrace) + Error.captureStackTrace(this, AssertionError); + else + this.stack = this.name + ": " + this.message + "\n" + new Error().stack; } -AssertionError.prototype = new Error; +AssertionError.prototype = Object.create(Error.prototype); AssertionError.prototype.name = "AssertionError"; globalScope["assert"] = function assert(isTrue, message) { if (!isTrue) throw new AssertionError(message); }; diff --git a/std/portable/heap.js b/std/portable/heap.js index cd6829ac..75dfdd5b 100644 --- a/std/portable/heap.js +++ b/std/portable/heap.js @@ -5,7 +5,7 @@ var HEAP_OFFSET = 0; Object.defineProperties(globalScope["Heap"] = { allocate: function allocate(size) { - if (!size) return 0; + if (!(size >>>= 0)) return 0; if (HEAP_OFFSET + size > HEAP.length) { var oldHeap = HEAP; HEAP = new Uint8Array(Math.max(65536, HEAP.length + size, HEAP.length * 2)); @@ -18,7 +18,7 @@ Object.defineProperties(globalScope["Heap"] = { }, dispose: function dispose() { }, copy: function copy(dest, src, n) { - HEAP.set(HEAP.subarray(src, src + n), dest); + HEAP.set(HEAP.subarray(src >>> 0, (src + n) >>> 0), dest >>> 0); return dest; } }, { @@ -26,9 +26,6 @@ Object.defineProperties(globalScope["Heap"] = { free: { get: function get_free() { return HEAP.length - HEAP_OFFSET; } }, size: { get: function get_size() { return HEAP.length; } } }); -globalScope["store"] = function store(ptr, val) { - binaryen.HEAPU8[ptr] = val; -}; -globalScope["load"] = function load(ptr) { - return binaryen.HEAPU8[ptr]; -}; + +globalScope["store"] = function store(ptr, val) { binaryen.HEAPU8[ptr] = val; }; +globalScope["load"] = function load(ptr) { return binaryen.HEAPU8[ptr]; }; diff --git a/tests/compiler.js b/tests/compiler.js index 3cc3eb43..9c9f98f4 100644 --- a/tests/compiler.js +++ b/tests/compiler.js @@ -12,15 +12,25 @@ var Module = require("../src/module").Module; var Parser = require("../src/parser").Parser; var isCreate = process.argv[2] === "--create"; -var filter = process.argv.length > 2 && !isCreate ? "*" + process.argv[2] + "*.ts" : "*.ts"; +var filter = process.argv.length > 2 && !isCreate ? "**/" + process.argv[2] + ".ts" : "**/*.ts"; + +var stdFiles = glob.sync("*.ts", { cwd: __dirname + "/../std/assembly" }); glob.sync(filter, { cwd: __dirname + "/compiler" }).forEach(filename => { - if (filename.charAt(0) == "_" || filename.endsWith(".fixture.ts")) + if (filename.charAt(0) == "_") return; console.log(chalk.default.whiteBright("Testing compiler/" + filename)); + var fixture = path.basename(filename, ".ts"); var parser = new Parser(); + if (filename.startsWith("std/")) { + stdFiles.forEach(file => { + parser.parseFile(fs.readFileSync(__dirname + "/../std/assembly/" + file, { encoding: "utf8" }), file, false); + }); + fixture = "std/" + fixture; + } + var sourceText = fs.readFileSync(__dirname + "/compiler/" + filename, { encoding: "utf8" }); parser.parseFile(sourceText, filename, true); var nextFile; @@ -38,7 +48,6 @@ glob.sync(filter, { cwd: __dirname + "/compiler" }).forEach(filename => { var actual = module.toText() + "(;\n[program.elements]\n " + iterate(program.elements.keys()).join("\n ") + "\n[program.exports]\n " + iterate(program.exports.keys()).join("\n ") + "\n;)\n"; var actualOptimized = null; var actualInlined = null; - var fixture = path.basename(filename, ".ts") + ".wast"; if (module.validate()) { console.log(chalk.default.green("validate OK")); @@ -59,27 +68,27 @@ glob.sync(filter, { cwd: __dirname + "/compiler" }).forEach(filename => { } if (isCreate) { - fs.writeFileSync(__dirname + "/compiler/" + fixture, actual, { encoding: "utf8" }); + fs.writeFileSync(__dirname + "/compiler/" + fixture + ".wast", actual, { encoding: "utf8" }); console.log("Created"); if (actualOptimized != null) { - fs.writeFileSync(__dirname + "/compiler/" + path.basename(filename, ".ts") + ".optimized.wast", actualOptimized, { encoding: "utf8" }); + fs.writeFileSync(__dirname + "/compiler/" + fixture + ".optimized.wast", actualOptimized, { encoding: "utf8" }); console.log("Created optimized"); } if (actualInlined != null) { if (actualInlined != actualOptimized) { - fs.writeFileSync(__dirname + "/compiler/" + path.basename(filename, ".ts") + ".optimized-inlined.wast", actualInlined, { encoding: "utf8" }); + fs.writeFileSync(__dirname + "/compiler/" + fixture + ".optimized-inlined.wast", actualInlined, { encoding: "utf8" }); console.log("Created optimized & inlined"); } else { try { - fs.unlinkSync(__dirname + "/compiler/" + path.basename(filename, ".ts") + ".optimized-inlined.wast"); + fs.unlinkSync(__dirname + "/compiler/" + fixture + ".optimized-inlined.wast"); console.log("Deleted optimized & inlined"); } catch (e) {} } } } else { try { - var expected = fs.readFileSync(__dirname + "/compiler/" + fixture, { encoding: "utf8" }); - var diffs = diff("compiler/" + fixture, expected, actual); + var expected = fs.readFileSync(__dirname + "/compiler/" + fixture + ".wast", { encoding: "utf8" }); + var diffs = diff(filename + ".wast", expected, actual); if (diffs !== null) { process.exitCode = 1; console.log(diffs); diff --git a/tests/compiler/assert.wast b/tests/compiler/assert.wast index 392dd0ed..a959a787 100644 --- a/tests/compiler/assert.wast +++ b/tests/compiler/assert.wast @@ -15,6 +15,8 @@ ) (; [program.elements] + NaN + Infinity isNaN isFinite clz @@ -56,6 +58,7 @@ f64 isize usize + HEAP_START [program.exports] ;) diff --git a/tests/compiler/binary.wast b/tests/compiler/binary.wast index 77ac16a1..f4b2b421 100644 --- a/tests/compiler/binary.wast +++ b/tests/compiler/binary.wast @@ -818,6 +818,8 @@ ) (; [program.elements] + NaN + Infinity isNaN isFinite clz @@ -859,6 +861,7 @@ f64 isize usize + HEAP_START binary/b binary/i binary/I diff --git a/tests/compiler/builtins.wast b/tests/compiler/builtins.wast index 0aeca301..38afc425 100644 --- a/tests/compiler/builtins.wast +++ b/tests/compiler/builtins.wast @@ -1035,6 +1035,8 @@ ) (; [program.elements] + NaN + Infinity isNaN isFinite clz @@ -1076,6 +1078,7 @@ f64 isize usize + HEAP_START builtins/b builtins/i builtins/I diff --git a/tests/compiler/class.wast b/tests/compiler/class.wast index 95f85b9b..ec74a3cd 100644 --- a/tests/compiler/class.wast +++ b/tests/compiler/class.wast @@ -43,6 +43,8 @@ ) (; [program.elements] + NaN + Infinity isNaN isFinite clz @@ -84,6 +86,7 @@ f64 isize usize + HEAP_START class/Animal class/Animal.MAX class/Animal.add diff --git a/tests/compiler/declare.wast b/tests/compiler/declare.wast index f12213c4..2354477e 100644 --- a/tests/compiler/declare.wast +++ b/tests/compiler/declare.wast @@ -8,6 +8,8 @@ ) (; [program.elements] + NaN + Infinity isNaN isFinite clz @@ -49,6 +51,7 @@ f64 isize usize + HEAP_START declare/external [program.exports] declare/external diff --git a/tests/compiler/do.wast b/tests/compiler/do.wast index 6637d2d7..49a1b858 100644 --- a/tests/compiler/do.wast +++ b/tests/compiler/do.wast @@ -53,6 +53,8 @@ ) (; [program.elements] + NaN + Infinity isNaN isFinite clz @@ -94,6 +96,7 @@ f64 isize usize + HEAP_START do/loopDo do/loopDoInDo [program.exports] diff --git a/tests/compiler/enum.wast b/tests/compiler/enum.wast index 992122b3..d505dfd4 100644 --- a/tests/compiler/enum.wast +++ b/tests/compiler/enum.wast @@ -38,6 +38,8 @@ ) (; [program.elements] + NaN + Infinity isNaN isFinite clz @@ -79,6 +81,7 @@ f64 isize usize + HEAP_START enum/Implicit enum/Explicit enum/Mixed diff --git a/tests/compiler/export.wast b/tests/compiler/export.wast index aeb2eb65..3718d942 100644 --- a/tests/compiler/export.wast +++ b/tests/compiler/export.wast @@ -32,6 +32,8 @@ ) (; [program.elements] + NaN + Infinity isNaN isFinite clz @@ -73,6 +75,7 @@ f64 isize usize + HEAP_START export/add export/sub export/a diff --git a/tests/compiler/for.wast b/tests/compiler/for.wast index 57b289c0..92e932a5 100644 --- a/tests/compiler/for.wast +++ b/tests/compiler/for.wast @@ -149,6 +149,8 @@ ) (; [program.elements] + NaN + Infinity isNaN isFinite clz @@ -190,6 +192,7 @@ f64 isize usize + HEAP_START for/i [program.exports] diff --git a/tests/compiler/game-of-life.wast b/tests/compiler/game-of-life.wast index e139a737..ddcef671 100644 --- a/tests/compiler/game-of-life.wast +++ b/tests/compiler/game-of-life.wast @@ -310,6 +310,8 @@ ) (; [program.elements] + NaN + Infinity isNaN isFinite clz @@ -351,6 +353,7 @@ f64 isize usize + HEAP_START ../../examples/game-of-life/assembly/game-of-life/w ../../examples/game-of-life/assembly/game-of-life/h ../../examples/game-of-life/assembly/game-of-life/s diff --git a/tests/compiler/i64.wast b/tests/compiler/i64.wast index fa4be1cd..38fe6b57 100644 --- a/tests/compiler/i64.wast +++ b/tests/compiler/i64.wast @@ -1191,6 +1191,8 @@ ) (; [program.elements] + NaN + Infinity isNaN isFinite clz @@ -1232,6 +1234,7 @@ f64 isize usize + HEAP_START ../../examples/i64-polyfill/assembly/i64/lo ../../examples/i64-polyfill/assembly/i64/hi ../../examples/i64-polyfill/assembly/i64/getLo diff --git a/tests/compiler/if.wast b/tests/compiler/if.wast index a01d3c30..79868217 100644 --- a/tests/compiler/if.wast +++ b/tests/compiler/if.wast @@ -48,6 +48,8 @@ ) (; [program.elements] + NaN + Infinity isNaN isFinite clz @@ -89,6 +91,7 @@ f64 isize usize + HEAP_START if/ifThenElse if/ifThen if/ifThenElseBlock diff --git a/tests/compiler/import.wast b/tests/compiler/import.wast index 8d414fce..ccf4daaf 100644 --- a/tests/compiler/import.wast +++ b/tests/compiler/import.wast @@ -43,6 +43,8 @@ ) (; [program.elements] + NaN + Infinity isNaN isFinite clz @@ -84,6 +86,7 @@ f64 isize usize + HEAP_START export/add export/sub export/a diff --git a/tests/compiler/limits.wast b/tests/compiler/limits.wast index aa3a5572..3f3f09c9 100644 --- a/tests/compiler/limits.wast +++ b/tests/compiler/limits.wast @@ -109,6 +109,8 @@ ) (; [program.elements] + NaN + Infinity isNaN isFinite clz @@ -150,6 +152,7 @@ f64 isize usize + HEAP_START [program.exports] ;) diff --git a/tests/compiler/literals.wast b/tests/compiler/literals.wast index 67cee1af..53cebd9c 100644 --- a/tests/compiler/literals.wast +++ b/tests/compiler/literals.wast @@ -141,6 +141,8 @@ ) (; [program.elements] + NaN + Infinity isNaN isFinite clz @@ -182,6 +184,7 @@ f64 isize usize + HEAP_START [program.exports] ;) diff --git a/tests/compiler/logical.wast b/tests/compiler/logical.wast index f518c841..713e91f8 100644 --- a/tests/compiler/logical.wast +++ b/tests/compiler/logical.wast @@ -257,6 +257,8 @@ ) (; [program.elements] + NaN + Infinity isNaN isFinite clz @@ -298,6 +300,7 @@ f64 isize usize + HEAP_START logical/i logical/I logical/f diff --git a/tests/compiler/memcpy.wast b/tests/compiler/memcpy.wast index 28e0416e..44a26d63 100644 --- a/tests/compiler/memcpy.wast +++ b/tests/compiler/memcpy.wast @@ -2055,6 +2055,8 @@ ) (; [program.elements] + NaN + Infinity isNaN isFinite clz @@ -2096,6 +2098,7 @@ f64 isize usize + HEAP_START memcpy/memcpy memcpy/base memcpy/dest diff --git a/tests/compiler/namespace.wast b/tests/compiler/namespace.wast index 22f90d87..9d396baf 100644 --- a/tests/compiler/namespace.wast +++ b/tests/compiler/namespace.wast @@ -19,6 +19,8 @@ ) (; [program.elements] + NaN + Infinity isNaN isFinite clz @@ -60,6 +62,7 @@ f64 isize usize + HEAP_START namespace/Outer namespace/Outer.Inner namespace/Outer.Inner.aVar diff --git a/tests/compiler/portable-conversions.wast b/tests/compiler/portable-conversions.wast index fdcee716..0953b5d7 100644 --- a/tests/compiler/portable-conversions.wast +++ b/tests/compiler/portable-conversions.wast @@ -331,6 +331,8 @@ ) (; [program.elements] + NaN + Infinity isNaN isFinite clz @@ -372,6 +374,7 @@ f64 isize usize + HEAP_START portable-conversions/i portable-conversions/I portable-conversions/f diff --git a/tests/compiler/reexport.wast b/tests/compiler/reexport.wast index 60ef4f40..2157b4e4 100644 --- a/tests/compiler/reexport.wast +++ b/tests/compiler/reexport.wast @@ -48,6 +48,8 @@ ) (; [program.elements] + NaN + Infinity isNaN isFinite clz @@ -89,6 +91,7 @@ f64 isize usize + HEAP_START export/add export/sub export/a diff --git a/tests/compiler/std/array.optimized-inlined.wast b/tests/compiler/std/array.optimized-inlined.wast new file mode 100644 index 00000000..251eb50c --- /dev/null +++ b/tests/compiler/std/array.optimized-inlined.wast @@ -0,0 +1,11 @@ +(module + (type $v (func)) + (memory $0 1) + (export "memory" (memory $0)) + (start $start) + (func $start (; 0 ;) (type $v) + (block $__inlined_func$Array.test + (nop) + ) + ) +) diff --git a/tests/compiler/std/array.optimized.wast b/tests/compiler/std/array.optimized.wast new file mode 100644 index 00000000..4a67c92e --- /dev/null +++ b/tests/compiler/std/array.optimized.wast @@ -0,0 +1,12 @@ +(module + (type $v (func)) + (memory $0 1) + (export "memory" (memory $0)) + (start $start) + (func $Array.test (; 0 ;) (type $v) + (nop) + ) + (func $start (; 1 ;) (type $v) + (call $Array.test) + ) +) diff --git a/tests/compiler/std/array.ts b/tests/compiler/std/array.ts new file mode 100644 index 00000000..212326cc --- /dev/null +++ b/tests/compiler/std/array.ts @@ -0,0 +1,2 @@ +// Array.fromPtr(1); +Array.test(); diff --git a/tests/compiler/std/array.wast b/tests/compiler/std/array.wast new file mode 100644 index 00000000..e4a63150 --- /dev/null +++ b/tests/compiler/std/array.wast @@ -0,0 +1,75 @@ +(module + (type $v (func)) + (global $HEAP_START i32 (i32.const 4)) + (memory $0 1) + (export "memory" (memory $0)) + (start $start) + (func $Array.test (; 0 ;) (type $v) + ) + (func $start (; 1 ;) (type $v) + (call $Array.test) + ) +) +(; +[program.elements] + NaN + Infinity + isNaN + isFinite + clz + ctz + popcnt + rotl + rotr + abs + max + min + ceil + floor + copysign + nearest + reinterpret + sqrt + trunc + load + store + sizeof + select + unreachable + current_memory + grow_memory + parseInt + parseFloat + changetype + assert + i8 + i16 + i32 + i64 + u8 + u16 + u32 + u64 + bool + f32 + f64 + isize + usize + HEAP_START + Array + Array.fromPtr + Array.test + heap/ALIGN_LOG2 + heap/ALIGN_SIZE + heap/ALIGN_MASK + heap/HEAP_OFFSET + Heap + Heap.allocate + Heap.dispose + Heap.get_used + Heap.get_free + Heap.get_size + Heap.copy +[program.exports] + +;) diff --git a/tests/compiler/std/heap.optimized-inlined.wast b/tests/compiler/std/heap.optimized-inlined.wast new file mode 100644 index 00000000..ac80cca8 --- /dev/null +++ b/tests/compiler/std/heap.optimized-inlined.wast @@ -0,0 +1,119 @@ +(module + (type $ii (func (param i32) (result i32))) + (type $iv (func (param i32))) + (type $v (func)) + (global $heap/HEAP_OFFSET (mut i32) (i32.const 0)) + (global $std/heap/ptr (mut i32) (i32.const 0)) + (global $HEAP_START i32 (i32.const 4)) + (memory $0 1) + (export "memory" (memory $0)) + (start $start) + (func $Heap.allocate (; 0 ;) (type $ii) (param $0 i32) (result i32) + (local $1 i32) + (local $2 i32) + (if + (i32.eqz + (get_local $0) + ) + (return + (i32.const 0) + ) + ) + (set_local $1 + (current_memory) + ) + (if + (i32.gt_u + (i32.add + (get_global $heap/HEAP_OFFSET) + (get_local $0) + ) + (i32.shl + (get_local $1) + (i32.const 16) + ) + ) + (if + (i32.lt_s + (grow_memory + (select + (tee_local $2 + (i32.trunc_s/f64 + (f64.ceil + (f64.div + (f64.convert_u/i32 + (get_local $0) + ) + (f64.const 65536) + ) + ) + ) + ) + (tee_local $1 + (i32.sub + (i32.mul + (get_local $1) + (i32.const 2) + ) + (get_local $1) + ) + ) + (i32.gt_s + (get_local $2) + (get_local $1) + ) + ) + ) + (i32.const 0) + ) + (unreachable) + ) + ) + (set_local $1 + (get_global $heap/HEAP_OFFSET) + ) + (if + (block (result i32) + (set_global $heap/HEAP_OFFSET + (i32.add + (get_global $heap/HEAP_OFFSET) + (get_local $0) + ) + ) + (i32.and + (get_global $heap/HEAP_OFFSET) + (i32.const 7) + ) + ) + (set_global $heap/HEAP_OFFSET + (i32.add + (i32.or + (get_global $heap/HEAP_OFFSET) + (i32.const 7) + ) + (i32.const 1) + ) + ) + ) + (get_local $1) + ) + (func $start (; 1 ;) (type $v) + (local $0 i32) + (set_global $heap/HEAP_OFFSET + (get_global $HEAP_START) + ) + (set_global $std/heap/ptr + (call $Heap.allocate + (i32.const 10) + ) + ) + (block + (block $__inlined_func$Heap.dispose + (set_local $0 + (get_global $std/heap/ptr) + ) + (nop) + ) + ) + ) +) diff --git a/tests/compiler/std/heap.optimized.wast b/tests/compiler/std/heap.optimized.wast new file mode 100644 index 00000000..a0f927b9 --- /dev/null +++ b/tests/compiler/std/heap.optimized.wast @@ -0,0 +1,116 @@ +(module + (type $ii (func (param i32) (result i32))) + (type $iv (func (param i32))) + (type $v (func)) + (global $heap/HEAP_OFFSET (mut i32) (i32.const 0)) + (global $std/heap/ptr (mut i32) (i32.const 0)) + (global $HEAP_START i32 (i32.const 4)) + (memory $0 1) + (export "memory" (memory $0)) + (start $start) + (func $Heap.allocate (; 0 ;) (type $ii) (param $0 i32) (result i32) + (local $1 i32) + (local $2 i32) + (if + (i32.eqz + (get_local $0) + ) + (return + (i32.const 0) + ) + ) + (set_local $1 + (current_memory) + ) + (if + (i32.gt_u + (i32.add + (get_global $heap/HEAP_OFFSET) + (get_local $0) + ) + (i32.shl + (get_local $1) + (i32.const 16) + ) + ) + (if + (i32.lt_s + (grow_memory + (select + (tee_local $2 + (i32.trunc_s/f64 + (f64.ceil + (f64.div + (f64.convert_u/i32 + (get_local $0) + ) + (f64.const 65536) + ) + ) + ) + ) + (tee_local $1 + (i32.sub + (i32.mul + (get_local $1) + (i32.const 2) + ) + (get_local $1) + ) + ) + (i32.gt_s + (get_local $2) + (get_local $1) + ) + ) + ) + (i32.const 0) + ) + (unreachable) + ) + ) + (set_local $1 + (get_global $heap/HEAP_OFFSET) + ) + (if + (block (result i32) + (set_global $heap/HEAP_OFFSET + (i32.add + (get_global $heap/HEAP_OFFSET) + (get_local $0) + ) + ) + (i32.and + (get_global $heap/HEAP_OFFSET) + (i32.const 7) + ) + ) + (set_global $heap/HEAP_OFFSET + (i32.add + (i32.or + (get_global $heap/HEAP_OFFSET) + (i32.const 7) + ) + (i32.const 1) + ) + ) + ) + (get_local $1) + ) + (func $Heap.dispose (; 1 ;) (type $iv) (param $0 i32) + (nop) + ) + (func $start (; 2 ;) (type $v) + (set_global $heap/HEAP_OFFSET + (get_global $HEAP_START) + ) + (set_global $std/heap/ptr + (call $Heap.allocate + (i32.const 10) + ) + ) + (call $Heap.dispose + (get_global $std/heap/ptr) + ) + ) +) diff --git a/tests/compiler/std/heap.ts b/tests/compiler/std/heap.ts new file mode 100644 index 00000000..4ecd5e69 --- /dev/null +++ b/tests/compiler/std/heap.ts @@ -0,0 +1,2 @@ +let ptr: usize = Heap.allocate(10); +Heap.dispose(ptr); diff --git a/tests/compiler/std/heap.wast b/tests/compiler/std/heap.wast new file mode 100644 index 00000000..0f35338f --- /dev/null +++ b/tests/compiler/std/heap.wast @@ -0,0 +1,191 @@ +(module + (type $i (func (result i32))) + (type $ii (func (param i32) (result i32))) + (type $iv (func (param i32))) + (type $v (func)) + (global $heap/HEAP_OFFSET (mut i32) (i32.const 0)) + (global $heap/ALIGN_LOG2 i32 (i32.const 3)) + (global $heap/ALIGN_SIZE i32 (i32.const 8)) + (global $heap/ALIGN_MASK i32 (i32.const 7)) + (global $std/heap/ptr (mut i32) (i32.const 0)) + (global $HEAP_START i32 (i32.const 4)) + (memory $0 1) + (export "memory" (memory $0)) + (start $start) + (func $Heap.allocate (; 0 ;) (type $ii) (param $0 i32) (result i32) + (local $1 i32) + (local $2 i32) + (local $3 i32) + (local $4 i32) + (if + (i32.eqz + (get_local $0) + ) + (return + (i32.const 0) + ) + ) + (block + (set_local $1 + (current_memory) + ) + ) + (if + (i32.gt_u + (i32.add + (get_global $heap/HEAP_OFFSET) + (get_local $0) + ) + (i32.shl + (get_local $1) + (i32.const 16) + ) + ) + (if + (i32.lt_s + (grow_memory + (select + (tee_local $2 + (i32.trunc_s/f64 + (f64.ceil + (f64.div + (f64.convert_u/i32 + (get_local $0) + ) + (f64.const 65536) + ) + ) + ) + ) + (tee_local $3 + (i32.sub + (i32.mul + (get_local $1) + (i32.const 2) + ) + (get_local $1) + ) + ) + (i32.gt_s + (get_local $2) + (get_local $3) + ) + ) + ) + (i32.const 0) + ) + (unreachable) + ) + ) + (block + (set_local $4 + (get_global $heap/HEAP_OFFSET) + ) + ) + (if + (i32.and + (block (result i32) + (set_global $heap/HEAP_OFFSET + (i32.add + (get_global $heap/HEAP_OFFSET) + (get_local $0) + ) + ) + (get_global $heap/HEAP_OFFSET) + ) + (i32.const 7) + ) + (set_global $heap/HEAP_OFFSET + (i32.add + (i32.or + (get_global $heap/HEAP_OFFSET) + (i32.const 7) + ) + (i32.const 1) + ) + ) + ) + (return + (get_local $4) + ) + ) + (func $Heap.dispose (; 1 ;) (type $iv) (param $0 i32) + ) + (func $start (; 2 ;) (type $v) + (set_global $heap/HEAP_OFFSET + (get_global $HEAP_START) + ) + (set_global $std/heap/ptr + (call $Heap.allocate + (i32.const 10) + ) + ) + (call $Heap.dispose + (get_global $std/heap/ptr) + ) + ) +) +(; +[program.elements] + NaN + Infinity + isNaN + isFinite + clz + ctz + popcnt + rotl + rotr + abs + max + min + ceil + floor + copysign + nearest + reinterpret + sqrt + trunc + load + store + sizeof + select + unreachable + current_memory + grow_memory + parseInt + parseFloat + changetype + assert + i8 + i16 + i32 + i64 + u8 + u16 + u32 + u64 + bool + f32 + f64 + isize + usize + HEAP_START + Array + Array.fromPtr + Array.test + heap/ALIGN_LOG2 + heap/ALIGN_SIZE + heap/ALIGN_MASK + heap/HEAP_OFFSET + Heap + Heap.allocate + Heap.dispose + Heap.get_used + Heap.get_free + Heap.get_size + Heap.copy + std/heap/ptr +[program.exports] + +;) diff --git a/tests/compiler/switch.wast b/tests/compiler/switch.wast index 935c3f75..e8bc50a4 100644 --- a/tests/compiler/switch.wast +++ b/tests/compiler/switch.wast @@ -147,6 +147,8 @@ ) (; [program.elements] + NaN + Infinity isNaN isFinite clz @@ -188,6 +190,7 @@ f64 isize usize + HEAP_START switch/doSwitch switch/doSwitchDefaultFirst switch/doSwitchDefaultOmitted diff --git a/tests/compiler/ternary.wast b/tests/compiler/ternary.wast index 47bef700..2fd3720d 100644 --- a/tests/compiler/ternary.wast +++ b/tests/compiler/ternary.wast @@ -35,6 +35,8 @@ ) (; [program.elements] + NaN + Infinity isNaN isFinite clz @@ -76,6 +78,7 @@ f64 isize usize + HEAP_START ternary/a [program.exports] diff --git a/tests/compiler/tlsf.wast b/tests/compiler/tlsf.wast index 9264afd8..3287a353 100644 --- a/tests/compiler/tlsf.wast +++ b/tests/compiler/tlsf.wast @@ -330,6 +330,8 @@ ) (; [program.elements] + NaN + Infinity isNaN isFinite clz @@ -371,6 +373,7 @@ f64 isize usize + HEAP_START tlsf/fls tlsf/ffs tlsf/ALIGN_SIZE_LOG2 diff --git a/tests/compiler/tsconfig.json b/tests/compiler/tsconfig.json index 54138117..0368ea5f 100644 --- a/tests/compiler/tsconfig.json +++ b/tests/compiler/tsconfig.json @@ -1,6 +1,6 @@ { "extends": "../../std/assembly.json", "include": [ - "./*.ts" + "./**/*.ts" ] } diff --git a/tests/compiler/unary.wast b/tests/compiler/unary.wast index b3a11b0b..58d72974 100644 --- a/tests/compiler/unary.wast +++ b/tests/compiler/unary.wast @@ -623,6 +623,8 @@ ) (; [program.elements] + NaN + Infinity isNaN isFinite clz @@ -664,6 +666,7 @@ f64 isize usize + HEAP_START unary/i unary/I unary/f diff --git a/tests/compiler/while.wast b/tests/compiler/while.wast index 4b8d5588..4ae0b3f1 100644 --- a/tests/compiler/while.wast +++ b/tests/compiler/while.wast @@ -62,6 +62,8 @@ ) (; [program.elements] + NaN + Infinity isNaN isFinite clz @@ -103,6 +105,7 @@ f64 isize usize + HEAP_START while/loopWhile while/loopWhileInWhile [program.exports]