From c6af2d14544e367fac2cb025f7a742d7ef262055 Mon Sep 17 00:00:00 2001 From: dcodeIO Date: Mon, 4 Dec 2017 14:49:24 +0100 Subject: [PATCH] Implement ternary using if, see AssemblyScript/assemblyscript#123 --- assembly.d.ts | 2 +- bin/asc.json | 9 +++ bin/asc.ts | 25 ++++++- src/ast.ts | 12 ++-- src/compiler.ts | 38 ++++++----- src/module.ts | 2 +- src/parser.ts | 4 +- tests/compiler/builtins.ts | 11 +++ tests/compiler/builtins.wast | 126 ++++++++++++++++++++++++++++------- tests/compiler/ternary.ts | 5 ++ tests/compiler/ternary.wast | 64 ++++++++++++++++++ 11 files changed, 245 insertions(+), 53 deletions(-) create mode 100644 tests/compiler/ternary.ts create mode 100644 tests/compiler/ternary.wast diff --git a/assembly.d.ts b/assembly.d.ts index 3003ff33..bb974a42 100644 --- a/assembly.d.ts +++ b/assembly.d.ts @@ -66,7 +66,7 @@ declare function current_memory(): i32; /** Grows linear memory by a given unsigned delta of pages. One page is 64kb. Returns the previous memory size in units of pages or `-1` on failure. */ declare function grow_memory(value: i32): i32; /** Emits an unreachable operation that results in a runtime error when executed. */ -declare function unreachable(): void; +declare function unreachable(): any; // sic /** Loads a value of the specified type from memory. */ declare function load(offset: usize): T; diff --git a/bin/asc.json b/bin/asc.json index 0c6aa47f..04f86cb3 100644 --- a/bin/asc.json +++ b/bin/asc.json @@ -8,5 +8,14 @@ "desc": "Print this message.", "type": "boolean", "aliases": [ "h" ] + }, + "optimize": { + "desc": "Optimize the module.", + "type": "boolean", + "aliases": [ "O" ] + }, + "validate": { + "desc": "Validate the module.", + "type": "boolean" } } diff --git a/bin/asc.ts b/bin/asc.ts index 67a30c45..0770c375 100644 --- a/bin/asc.ts +++ b/bin/asc.ts @@ -8,6 +8,7 @@ import * as as from "../src"; var conf: { [key: string]: { desc: string, type: string, aliases: string[], default: any } } = require("./asc.json"); var opts: minimist.Opts = {}; + Object.keys(conf).forEach(key => { var opt = conf[key]; if (opt.aliases) @@ -80,7 +81,7 @@ let diagnostic: as.DiagnosticMessage | null; let hasErrors: boolean = false; while ((diagnostic = as.nextDiagnostic(parser)) != null) { - console.error(as.formatDiagnostic(diagnostic, process.stdout.isTTY, true)); + console.error(as.formatDiagnostic(diagnostic, process.stderr.isTTY, true)); if (as.isError(diagnostic)) hasErrors = true; } @@ -89,5 +90,27 @@ if (hasErrors) process.exit(1); const module = as.compile(parser); + +hasErrors = false; +while ((diagnostic = as.nextDiagnostic(parser)) != null) { + console.error(as.formatDiagnostic(diagnostic, process.stderr.isTTY, true)); + if (as.isError(diagnostic)) + hasErrors = true; +} + +if (hasErrors) { + module.dispose(); + process.exit(1); +} + +if (args["validate"]) + if (!module.validate()) { + module.dispose(); + process.exit(1); + } + +if (args["optimize"]) + module.optimize(); + _BinaryenModulePrint(module.ref); module.dispose(); diff --git a/src/ast.ts b/src/ast.ts index 607c02f6..1ec4723b 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -19,7 +19,7 @@ │ ├ ParenthesizedExpression │ ├ NewExpression │ ├ PropertyAccessExpression - │ ├ SelectExpression + │ ├ TernaryExpression │ └ UnaryExpression │ ├ UnaryPostfixExpression │ └ UnaryPrefixExpression @@ -101,7 +101,7 @@ export enum NodeKind { NULL, PARENTHESIZED, PROPERTYACCESS, - SELECT, + TERNARY, SUPER, THIS, TRUE, @@ -302,8 +302,8 @@ export abstract class Expression extends Node { return expr; } - static createSelect(condition: Expression, ifThen: Expression, ifElse: Expression, range: Range): SelectExpression { - const expr: SelectExpression = new SelectExpression(); + static createTernary(condition: Expression, ifThen: Expression, ifElse: Expression, range: Range): TernaryExpression { + const expr: TernaryExpression = new TernaryExpression(); expr.range = range; (expr.condition = condition).parent = expr; (expr.ifThen = ifThen).parent = expr; @@ -549,9 +549,9 @@ export class RegexpLiteralExpression extends LiteralExpression { } } -export class SelectExpression extends Expression { +export class TernaryExpression extends Expression { - kind = NodeKind.SELECT; + kind = NodeKind.TERNARY; condition: Expression; ifThen: Expression; ifElse: Expression; diff --git a/src/compiler.ts b/src/compiler.ts index bbb8f7b9..671dbefb 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -59,7 +59,7 @@ import { NewExpression, ParenthesizedExpression, PropertyAccessExpression, - SelectExpression, + TernaryExpression, StringLiteralExpression, UnaryPostfixExpression, UnaryPrefixExpression, @@ -927,8 +927,8 @@ export class Compiler extends DiagnosticEmitter { expr = this.compilePropertyAccessExpression(expression, contextualType); break; - case NodeKind.SELECT: - expr = this.compileSelectExpression(expression, contextualType); + case NodeKind.TERNARY: + expr = this.compileTernaryExpression(expression, contextualType); break; case NodeKind.UNARYPOSTFIX: @@ -1482,6 +1482,7 @@ export class Compiler extends DiagnosticEmitter { /** Compiles a call to a function. If an instance method, `this` is the first element in `argumentExpressions`. */ compileCall(functionInstance: Function, argumentExpressions: Expression[], reportNode: Node): ExpressionRef { + const previousType: Type = this.currentType; // validate and compile arguments const parameters: Parameter[] = functionInstance.parameters; @@ -1627,6 +1628,7 @@ export class Compiler extends DiagnosticEmitter { return this.module.createHost(HostOp.GrowMemory, null, operands); case "unreachable": + this.currentType = previousType; return this.module.createUnreachable(); case "isNaN": // value != value @@ -1645,35 +1647,35 @@ export class Compiler extends DiagnosticEmitter { } break; - case "isFinite": // value != value ? false : abs(value) != Infinity + case "isFinite": // v=[abs(value) != Infinity, false]; return value == value ? v[0] : v[1] if (functionInstance.typeArguments[0] == Type.f64) { tempLocal = this.currentFunction.addLocal(Type.f64); return this.module.createSelect( - this.module.createBinary(BinaryOp.NeF64, - this.module.createTeeLocal(tempLocal.index, operands[0]), - this.module.createGetLocal(tempLocal.index, NativeType.F64) - ), - this.module.createI32(0), this.module.createBinary(BinaryOp.NeF64, this.module.createUnary(UnaryOp.AbsF64, - this.module.createGetLocal(tempLocal.index, NativeType.F64) + this.module.createTeeLocal(tempLocal.index, operands[0]) ), this.module.createF64(Infinity) + ), + this.module.createI32(0), + this.module.createBinary(BinaryOp.EqF64, + this.module.createGetLocal(tempLocal.index, NativeType.F64), + this.module.createGetLocal(tempLocal.index, NativeType.F64) ) ); } else if (functionInstance.typeArguments[0] == Type.f32) { tempLocal = this.currentFunction.addLocal(Type.f32); return this.module.createSelect( - this.module.createBinary(BinaryOp.NeF32, - this.module.createTeeLocal(tempLocal.index, operands[0]), - this.module.createGetLocal(tempLocal.index, NativeType.F32) - ), - this.module.createI32(0), this.module.createBinary(BinaryOp.NeF32, this.module.createUnary(UnaryOp.AbsF32, - this.module.createGetLocal(tempLocal.index, NativeType.F32) + this.module.createTeeLocal(tempLocal.index, operands[0]) ), this.module.createF32(Infinity) + ), + this.module.createI32(0), + this.module.createBinary(BinaryOp.EqF32, + this.module.createGetLocal(tempLocal.index, NativeType.F32), + this.module.createGetLocal(tempLocal.index, NativeType.F32) ) ); } @@ -1845,11 +1847,11 @@ export class Compiler extends DiagnosticEmitter { throw new Error("not implemented"); } - compileSelectExpression(expression: SelectExpression, contextualType: Type): ExpressionRef { + compileTernaryExpression(expression: TernaryExpression, contextualType: Type): ExpressionRef { const condition: ExpressionRef = this.compileExpression(expression.condition, Type.i32); const ifThen: ExpressionRef = this.compileExpression(expression.ifThen, contextualType); const ifElse: ExpressionRef = this.compileExpression(expression.ifElse, contextualType); - return this.module.createSelect(condition, ifThen, ifElse); + return this.module.createIf(condition, ifThen, ifElse); } compileUnaryPostfixExpression(expression: UnaryPostfixExpression, contextualType: Type): ExpressionRef { diff --git a/src/module.ts b/src/module.ts index 19f360f9..1a5d5440 100644 --- a/src/module.ts +++ b/src/module.ts @@ -448,7 +448,7 @@ export class Module { return _BinaryenReturn(this.ref, expression); } - createSelect(condition: ExpressionRef, ifTrue: ExpressionRef, ifFalse: ExpressionRef): ExpressionRef { + createSelect(ifTrue: ExpressionRef, ifFalse: ExpressionRef, condition: ExpressionRef): ExpressionRef { if (this.noEmit) return 0; return _BinaryenSelect(this.ref, condition, ifTrue, ifFalse); } diff --git a/src/parser.ts b/src/parser.ts index 0a8850ae..96c8194b 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -1456,7 +1456,7 @@ export class Parser extends DiagnosticEmitter { this.error(DiagnosticCode.The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access, expr.range); expr = Expression.createUnaryPostfix(token, expr, tn.range(startPos, tn.pos)); - // SelectExpression + // TernaryExpression } else if (token == Token.QUESTION) { const ifThen: Expression | null = this.parseExpression(tn); if (!ifThen) @@ -1465,7 +1465,7 @@ export class Parser extends DiagnosticEmitter { const ifElse: Expression | null = this.parseExpression(tn); if (!ifElse) return null; - expr = Expression.createSelect(expr, ifThen, ifElse, tn.range(startPos, tn.pos)); + expr = Expression.createTernary(expr, ifThen, ifElse, tn.range(startPos, tn.pos)); } else { this.error(DiagnosticCode._0_expected, tn.range(), ":"); return null; diff --git a/tests/compiler/builtins.ts b/tests/compiler/builtins.ts index ccf5a35b..2fa18144 100644 --- a/tests/compiler/builtins.ts +++ b/tests/compiler/builtins.ts @@ -121,3 +121,14 @@ sizeof(); i = load(4); store(4, i); + +if (NaN == NaN) + unreachable(); +if (!isNaN(NaN)) + unreachable(); +if (isFinite(NaN)) + unreachable(); +if (isFinite(Infinity)) + unreachable(); +if (!isFinite(0)) + unreachable(); diff --git a/tests/compiler/builtins.wast b/tests/compiler/builtins.wast index c7a188c1..236a3024 100644 --- a/tests/compiler/builtins.wast +++ b/tests/compiler/builtins.wast @@ -19,6 +19,10 @@ (local $5 f64) (local $6 f64) (local $7 f64) + (local $8 f32) + (local $9 f32) + (local $10 f32) + (local $11 f64) (drop (i32.clz (i32.const 1) @@ -191,17 +195,17 @@ ) (drop (select - (i32.const 0) (f32.ne (f32.abs - (get_local $1) + (tee_local $1 + (f32.const 1.25) + ) ) (f32.const inf) ) - (f32.ne - (tee_local $1 - (f32.const 1.25) - ) + (i32.const 0) + (f32.eq + (get_local $1) (get_local $1) ) ) @@ -270,17 +274,17 @@ ) (set_global $builtins/b (select - (i32.const 0) (f32.ne (f32.abs - (get_local $3) + (tee_local $3 + (f32.const 1.25) + ) ) (f32.const inf) ) - (f32.ne - (tee_local $3 - (f32.const 1.25) - ) + (i32.const 0) + (f32.eq + (get_local $3) (get_local $3) ) ) @@ -355,17 +359,17 @@ ) (drop (select - (i32.const 0) (f64.ne (f64.abs - (get_local $5) + (tee_local $5 + (f64.const 1.25) + ) ) (f64.const inf) ) - (f64.ne - (tee_local $5 - (f64.const 1.25) - ) + (i32.const 0) + (f64.eq + (get_local $5) (get_local $5) ) ) @@ -434,17 +438,17 @@ ) (set_global $builtins/b (select - (i32.const 0) (f64.ne (f64.abs - (get_local $7) + (tee_local $7 + (f64.const 1.25) + ) ) (f64.const inf) ) - (f64.ne - (tee_local $7 - (f64.const 1.25) - ) + (i32.const 0) + (f64.eq + (get_local $7) (get_local $7) ) ) @@ -523,6 +527,80 @@ (i32.const 4) (get_global $builtins/i) ) + (if + (f64.eq + (f64.const nan:0x8000000000000) + (f64.const nan:0x8000000000000) + ) + (unreachable) + ) + (if + (i32.eqz + (f32.ne + (tee_local $8 + (f32.const nan:0x400000) + ) + (get_local $8) + ) + ) + (unreachable) + ) + (if + (select + (f32.ne + (f32.abs + (tee_local $9 + (f32.const nan:0x400000) + ) + ) + (f32.const inf) + ) + (i32.const 0) + (f32.eq + (get_local $9) + (get_local $9) + ) + ) + (unreachable) + ) + (if + (select + (f32.ne + (f32.abs + (tee_local $10 + (f32.const inf) + ) + ) + (f32.const inf) + ) + (i32.const 0) + (f32.eq + (get_local $10) + (get_local $10) + ) + ) + (unreachable) + ) + (if + (i32.eqz + (select + (f64.ne + (f64.abs + (tee_local $11 + (f64.const 0) + ) + ) + (f64.const inf) + ) + (i32.const 0) + (f64.eq + (get_local $11) + (get_local $11) + ) + ) + ) + (unreachable) + ) ) ) (; diff --git a/tests/compiler/ternary.ts b/tests/compiler/ternary.ts new file mode 100644 index 00000000..59fffd5d --- /dev/null +++ b/tests/compiler/ternary.ts @@ -0,0 +1,5 @@ +let a: i32; + +a = 0 ? unreachable() : 1; +a = 1 ? 1 : unreachable(); +a = (0 ? unreachable() : 1) ? 1 : unreachable(); diff --git a/tests/compiler/ternary.wast b/tests/compiler/ternary.wast new file mode 100644 index 00000000..99f93858 --- /dev/null +++ b/tests/compiler/ternary.wast @@ -0,0 +1,64 @@ +(module + (type $v (func)) + (global $ternary/a (mut i32) (i32.const 0)) + (memory $0 1) + (data (i32.const 4) "\08\00\00\00") + (export "memory" (memory $0)) + (start $start) + (func $start (; 0 ;) (type $v) + (set_global $ternary/a + (if (result i32) + (i32.const 0) + (unreachable) + (i32.const 1) + ) + ) + (set_global $ternary/a + (if (result i32) + (i32.const 1) + (i32.const 1) + (unreachable) + ) + ) + (set_global $ternary/a + (if (result i32) + (if (result i32) + (i32.const 0) + (unreachable) + (i32.const 1) + ) + (i32.const 1) + (unreachable) + ) + ) + ) +) +(; +[program.elements] + clz + ctz + popcnt + rotl + rotr + abs + ceil + copysign + floor + max + min + nearest + sqrt + trunc + current_memory + grow_memory + unreachable + isNaN + isFinite + assert + sizeof + load + store + ternary/a +[program.exports] + +;)