diff --git a/assembly.d.ts b/assembly.d.ts index 9d3dfa57..9a4c9bf7 100644 --- a/assembly.d.ts +++ b/assembly.d.ts @@ -39,18 +39,18 @@ declare function popcnt(value: T): T; declare function rotl(value: T, shift: T): T; /** Performs the sign-agnostic rotate right operation on a 32-bit or 64-bit integer. */ declare function rotr(value: T, shift: T): T; -/** Computes the absolute value of a 32-bit or 64-bit float. */ -declare function abs(value: T): T; +/** Computes the absolute value of an integer or float. */ +declare function abs(value: T): T; +/** Determines the maximum of two integers or floats. If either operand is `NaN`, returns `NaN`. */ +declare function max(left: T, right: T): T; +/** Determines the minimum of two integers or floats. If either operand is `NaN`, returns `NaN`. */ +declare function min(left: T, right: T): T; /** Performs the ceiling operation on a 32-bit or 64-bit float. */ declare function ceil(value: T): T; /** Composes a 32-bit or 64-bit float from the magnitude of `x` and the sign of `y`. */ declare function copysign(x: T, y: T): T; /** Performs the floor operation on a 32-bit or 64-bit float. */ declare function floor(value: T): T; -/** Determines the maximum of two 32-bit or 64-bit floats. If either operand is `NaN`, returns `NaN`. */ -declare function max(left: T, right: T): T; -/** Determines the minimum of two 32-bit or 64-bit floats. If either operand is `NaN`, returns `NaN`. */ -declare function min(left: T, right: T): T; /** Rounds to the nearest integer tied to even of a 32-bit or 64-bit float. */ declare function nearest(value: T): T; /** Reinterprets the bits of a value of type `T1` as type `T2`. Valid reinterpretations are i32 to/from f32 and i64 to/from f64. */ diff --git a/src/builtins.ts b/src/builtins.ts new file mode 100644 index 00000000..340d9cdb --- /dev/null +++ b/src/builtins.ts @@ -0,0 +1,431 @@ +import { Compiler, Target, typeToNativeType, typeToNativeOne } from "./compiler"; +import { DiagnosticCode } from "./diagnostics"; +import { Node, Expression } from "./ast"; +import { Type } from "./types"; +import { ExpressionRef, UnaryOp, BinaryOp, HostOp, NativeType } from "./module"; +import { Program, FunctionPrototype, Local } from "./program"; + +/** Initializes the specified program with builtin functions. */ +export function initialize(program: Program): void { + addFunction(program, "clz", true); + addFunction(program, "ctz", true); + addFunction(program, "popcnt", true); + addFunction(program, "rotl", true); + addFunction(program, "rotr", true); + addFunction(program, "abs", true); + addFunction(program, "ceil", true); + addFunction(program, "copysign", true); + addFunction(program, "floor", true); + addFunction(program, "max", true); + addFunction(program, "min", true); + addFunction(program, "nearest", true); + addFunction(program, "sqrt", true); + addFunction(program, "trunc", true); + addFunction(program, "current_memory"); + addFunction(program, "grow_memory"); + addFunction(program, "unreachable"); + addFunction(program, "load", true); + addFunction(program, "store", true); + addFunction(program, "reinterpret", true); + addFunction(program, "select", true); + addFunction(program, "sizeof", true); + addFunction(program, "isNaN", true); + addFunction(program, "isFinite", true); + addFunction(program, "assert"); +} + +/** Adds a builtin function to the specified program. */ +function addFunction(program: Program, name: string, isGeneric: bool = false): void { + let prototype: FunctionPrototype = new FunctionPrototype(program, name, null, null); + prototype.isGeneric = isGeneric; + prototype.isBuiltin = true; + program.elements.set(name, prototype); +} + +/** Compiles a call to a builtin function. */ +export function compileCall(compiler: Compiler, internalName: string, typeArguments: Type[], operands: Expression[], reportNode: Node): ExpressionRef { + const usizeType: Type = select(Type.usize64, Type.usize32, compiler.options.target == Target.WASM64); + + let arg0: ExpressionRef, + arg1: ExpressionRef, + arg2: ExpressionRef; + + let tempLocal: Local; + + switch (internalName) { + + case "clz": // clz(value: T) -> T + if (!validateCall(compiler, typeArguments, 1, operands, 1, reportNode)) + return compiler.module.createUnreachable(); + if ((compiler.currentType = typeArguments[0]).isAnyInteger) { + arg0 = compiler.compileExpression(operands[0], typeArguments[0]); + return (compiler.currentType = typeArguments[0]).isLongInteger // sic + ? compiler.module.createUnary(UnaryOp.ClzI64, arg0) + : typeArguments[0].isSmallInteger + ? compiler.module.createBinary(BinaryOp.AndI32, + compiler.module.createUnary(UnaryOp.ClzI32, arg0), + compiler.module.createI32(typeArguments[0].smallIntegerMask) + ) + : compiler.module.createUnary(UnaryOp.ClzI32, arg0); + } + break; + + case "ctz": // ctz(value: T) -> T + if (!validateCall(compiler, typeArguments, 1, operands, 1, reportNode)) + return compiler.module.createUnreachable(); + if ((compiler.currentType = typeArguments[0]).isAnyInteger) { + arg0 = compiler.compileExpression(operands[0], typeArguments[0]); + return (compiler.currentType = typeArguments[0]).isLongInteger // sic + ? compiler.module.createUnary(UnaryOp.CtzI64, arg0) + : typeArguments[0].isSmallInteger + ? compiler.module.createBinary(BinaryOp.AndI32, + compiler.module.createUnary(UnaryOp.CtzI32, arg0), + compiler.module.createI32(typeArguments[0].smallIntegerMask) + ) + : compiler.module.createUnary(UnaryOp.CtzI32, arg0); + } + break; + + case "popcnt": // popcnt(value: T) -> T + if (!validateCall(compiler, typeArguments, 1, operands, 1, reportNode)) + return compiler.module.createUnreachable(); + if ((compiler.currentType = typeArguments[0]).isAnyInteger) { + arg0 = compiler.compileExpression(operands[0], typeArguments[0]); + return (compiler.currentType = typeArguments[0]).isLongInteger // sic + ? compiler.module.createUnary(UnaryOp.PopcntI64, arg0) + : typeArguments[0].isSmallInteger + ? compiler.module.createBinary(BinaryOp.AndI32, + compiler.module.createUnary(UnaryOp.PopcntI32, arg0), + compiler.module.createI32(typeArguments[0].smallIntegerMask) + ) + : compiler.module.createUnary(UnaryOp.PopcntI32, arg0); + } + break; + + case "rotl": // rotl(value: T, shift: T) -> T + if (!validateCall(compiler, typeArguments, 1, operands, 2, reportNode)) + return compiler.module.createUnreachable(); + if ((compiler.currentType = typeArguments[0]).isAnyInteger) { + arg0 = compiler.compileExpression(operands[0], typeArguments[0]); + arg1 = compiler.compileExpression(operands[1], typeArguments[0]); + return (compiler.currentType = typeArguments[0]).isLongInteger // sic + ? compiler.module.createBinary(BinaryOp.RotlI64, arg0, arg1) + : typeArguments[0].isSmallInteger + ? compiler.module.createBinary(BinaryOp.AndI32, + compiler.module.createBinary(BinaryOp.RotlI32, arg0, arg1), + compiler.module.createI32(typeArguments[0].smallIntegerMask) + ) + : compiler.module.createBinary(BinaryOp.RotlI32, arg0, arg1); + } + break; + + case "rotr": // rotr(value: T, shift: T) -> T + if (!validateCall(compiler, typeArguments, 1, operands, 2, reportNode)) + return compiler.module.createUnreachable(); + if ((compiler.currentType = typeArguments[0]).isAnyInteger) { + arg0 = compiler.compileExpression(operands[0], typeArguments[0]); + arg1 = compiler.compileExpression(operands[1], typeArguments[0]); + return (compiler.currentType = typeArguments[0]).isLongInteger // sic + ? compiler.module.createBinary(BinaryOp.RotrI64, arg0, arg1) + : typeArguments[0].isSmallInteger + ? compiler.module.createBinary(BinaryOp.AndI32, + compiler.module.createBinary(BinaryOp.RotrI32, arg0, arg1), + compiler.module.createI32(typeArguments[0].smallIntegerMask) + ) + : compiler.module.createBinary(BinaryOp.RotrI32, arg0, arg1); + } + break; + + case "abs": // abs(value: T) -> T + if (!validateCall(compiler, typeArguments, 1, operands, 1, reportNode)) + return compiler.module.createUnreachable(); + if ((compiler.currentType = typeArguments[0]) != Type.void) { + arg0 = compiler.compileExpression(operands[0], typeArguments[0]); + if ((compiler.currentType = typeArguments[0]).isAnyFloat) // sic + return typeArguments[0] == Type.f32 + ? compiler.module.createUnary(UnaryOp.AbsF32, arg0) + : compiler.module.createUnary(UnaryOp.AbsF64, arg0); + if (typeArguments[0].isAnyInteger) { + // TODO: ternaries for integers + } + } + break; + + case "max": // max(left: T, right: T) -> T + if (!validateCall(compiler, typeArguments, 1, operands, 2, reportNode)) + return compiler.module.createUnreachable(); + if ((compiler.currentType = typeArguments[0]) != Type.void) { + arg0 = compiler.compileExpression(operands[0], typeArguments[0]); + arg1 = compiler.compileExpression(operands[1], typeArguments[0]); + if ((compiler.currentType = typeArguments[0]).isAnyFloat) // sic + return typeArguments[0] == Type.f32 + ? compiler.module.createBinary(BinaryOp.MaxF32, arg0, arg1) + : compiler.module.createBinary(BinaryOp.MaxF64, arg0, arg1); + if (typeArguments[0].isAnyInteger) { + // TODO: ternaries for integers + } + } + break; + + case "min": // min(left: T, right: T) -> T + if (!validateCall(compiler, typeArguments, 1, operands, 2, reportNode)) + return compiler.module.createUnreachable(); + if ((compiler.currentType = typeArguments[0]) != Type.void) { + arg0 = compiler.compileExpression(operands[0], typeArguments[0]); + arg1 = compiler.compileExpression(operands[1], typeArguments[0]); + if ((compiler.currentType = typeArguments[0]).isAnyFloat) // sic + return typeArguments[0] == Type.f32 + ? compiler.module.createBinary(BinaryOp.MinF32, arg0, arg1) + : compiler.module.createBinary(BinaryOp.MinF64, arg0, arg1); + if (typeArguments[0].isAnyInteger) { + // TODO: ternaries for integers + } + } + break; + + case "ceil": // ceil(value: T) -> T + if (!validateCall(compiler, typeArguments, 1, operands, 1, reportNode)) + return compiler.module.createUnreachable(); + if ((compiler.currentType = typeArguments[0]).isAnyFloat) { + arg0 = compiler.compileExpression(operands[0], typeArguments[0]); + return (compiler.currentType = typeArguments[0]) == Type.f32 // sic + ? compiler.module.createUnary(UnaryOp.CeilF32, arg0) + : compiler.module.createUnary(UnaryOp.CeilF64, arg0); + } + break; + + case "floor": // floor(value: T) -> T + if (!validateCall(compiler, typeArguments, 1, operands, 1, reportNode)) + return compiler.module.createUnreachable(); + if ((compiler.currentType = typeArguments[0]).isAnyFloat) { + arg0 = compiler.compileExpression(operands[0], typeArguments[0]); + return (compiler.currentType = typeArguments[0]) == Type.f32 // sic + ? compiler.module.createUnary(UnaryOp.FloorF32, arg0) + : compiler.module.createUnary(UnaryOp.FloorF64, arg0); + } + break; + + case "copysign": // copysign(left: T, right: T) -> T + if (!validateCall(compiler, typeArguments, 1, operands, 2, reportNode)) + return compiler.module.createUnreachable(); + if ((compiler.currentType = typeArguments[0]).isAnyFloat) { + arg0 = compiler.compileExpression(operands[0], typeArguments[0]); + arg1 = compiler.compileExpression(operands[1], typeArguments[0]); + return (compiler.currentType = typeArguments[0]) == Type.f32 // sic + ? compiler.module.createBinary(BinaryOp.CopysignF32, arg0, arg1) + : compiler.module.createBinary(BinaryOp.CopysignF64, arg0, arg1); + } + break; + + case "nearest": + if (!validateCall(compiler, typeArguments, 1, operands, 1, reportNode)) + return compiler.module.createUnreachable(); + if ((compiler.currentType = typeArguments[0]).isAnyFloat) { + arg0 = compiler.compileExpression(operands[0], typeArguments[0]); + return (compiler.currentType = typeArguments[0]) == Type.f32 // sic + ? compiler.module.createUnary(UnaryOp.NearestF32, arg0) + : compiler.module.createUnary(UnaryOp.NearestF64, arg0); + } + break; + + case "sqrt": + if (!validateCall(compiler, typeArguments, 1, operands, 1, reportNode)) + return compiler.module.createUnreachable(); + if ((compiler.currentType = typeArguments[0]).isAnyFloat) { + arg0 = compiler.compileExpression(operands[0], typeArguments[0]); + return (compiler.currentType = typeArguments[0]) == Type.f32 // sic + ? compiler.module.createUnary(UnaryOp.SqrtF32, arg0) + : compiler.module.createUnary(UnaryOp.SqrtF64, arg0); + } + break; + + case "trunc": + if (!validateCall(compiler, typeArguments, 1, operands, 1, reportNode)) + return compiler.module.createUnreachable(); + if ((compiler.currentType = typeArguments[0]).isAnyFloat) { + arg0 = compiler.compileExpression(operands[0], typeArguments[0]); + return (compiler.currentType = typeArguments[0]) == Type.f32 // sic + ? compiler.module.createUnary(UnaryOp.TruncF32, arg0) + : compiler.module.createUnary(UnaryOp.TruncF64, arg0); + } + break; + + case "sizeof": // sizeof() -> usize + compiler.currentType = usizeType; + if (!validateCall(compiler, typeArguments, 1, operands, 0, reportNode)) + return compiler.module.createUnreachable(); + return usizeType.isLongInteger + ? compiler.module.createI64(typeArguments[0].byteSize, 0) + : compiler.module.createI32(typeArguments[0].byteSize); + + case "load": // load(offset: usize) -> T + if (!validateCall(compiler, typeArguments, 1, operands, 1, reportNode)) + return compiler.module.createUnreachable(); + arg0 = compiler.compileExpression(operands[0], usizeType); // reports + if ((compiler.currentType = typeArguments[0]) != Type.void) + return compiler.module.createLoad(typeArguments[0].byteSize, typeArguments[0].isSignedInteger, arg0, typeToNativeType(typeArguments[0])); + break; + + case "store": // store(offset: usize, value: T) -> void + compiler.currentType = Type.void; + if (!validateCall(compiler, typeArguments, 1, operands, 2, reportNode)) + return compiler.module.createUnreachable(); + arg0 = compiler.compileExpression(operands[0], usizeType); // reports + arg1 = compiler.compileExpression(operands[1], typeArguments[0]); // reports + compiler.currentType = Type.void; + if (typeArguments[0] != Type.void) + return compiler.module.createStore(typeArguments[0].byteSize, arg0, arg1, typeToNativeType(typeArguments[0])); + break; + + case "reinterpret": // reinterpret(value: T1) -> T2 + if (!validateCall(compiler, typeArguments, 2, operands, 1, reportNode)) + return compiler.module.createUnreachable(); + compiler.currentType = typeArguments[1]; + if (typeArguments[0].isLongInteger && typeArguments[1] == Type.f64) { + arg0 = compiler.compileExpression(operands[0], Type.i64); // reports + compiler.currentType = Type.f64; + return compiler.module.createUnary(UnaryOp.ReinterpretI64, arg0); + } + if (typeArguments[0].isAnyInteger && typeArguments[0].byteSize == 4 && typeArguments[1] == Type.f32) { + arg0 = compiler.compileExpression(operands[0], Type.i32); // reports + compiler.currentType = Type.f32; + return compiler.module.createUnary(UnaryOp.ReinterpretI32, arg0); + } + if (typeArguments[0] == Type.f64 && typeArguments[1].isLongInteger) { + arg0 = compiler.compileExpression(operands[0], Type.f64); // reports + compiler.currentType = typeArguments[1]; + return compiler.module.createUnary(UnaryOp.ReinterpretF64, arg0); + } + if (typeArguments[0] == Type.f32 && typeArguments[1].isAnyInteger && typeArguments[1].byteSize == 4) { + arg0 = compiler.compileExpression(operands[0], Type.f32); // reports + compiler.currentType = typeArguments[1]; + return compiler.module.createUnary(UnaryOp.ReinterpretF32, arg0); + } + break; + + case "select": // select(ifTrue: T, ifFalse: T, condition: bool) -> T + if (!validateCall(compiler, typeArguments, 1, operands, 3, reportNode)) + return compiler.module.createUnreachable(); + if (typeArguments[0] != Type.void) { + arg0 = compiler.compileExpression(operands[0], typeArguments[0]); // reports + arg1 = compiler.compileExpression(operands[1], typeArguments[0]); // reports + arg2 = compiler.compileExpression(operands[2], Type.i32); // reports + compiler.currentType = typeArguments[0]; + return compiler.module.createSelect(arg0, arg1, arg2); + } + break; + + case "current_memory": // current_memory() -> i32 + compiler.currentType = Type.i32; + if (!validateCall(compiler, typeArguments, 0, operands, 0, reportNode)) + return compiler.module.createUnreachable(); + return compiler.module.createHost(HostOp.CurrentMemory); + + case "grow_memory": // grow_memory(pages: i32) -> i32 + compiler.currentType = Type.i32; + if (!validateCall(compiler, typeArguments, 0, operands, 1, reportNode)) + return compiler.module.createUnreachable(); + arg0 = compiler.compileExpression(operands[0], Type.i32); + return compiler.module.createHost(HostOp.GrowMemory, null, [ arg0 ]); + + case "unreachable": // unreachable() -> * + // does not modify currentType + validateCall(compiler, typeArguments, 0, operands, 0, reportNode); + return compiler.module.createUnreachable(); + + case "isNaN": + compiler.currentType = Type.bool; + if (!validateCall(compiler, typeArguments, 1, operands, 1, reportNode)) + return compiler.module.createUnreachable(); + if (typeArguments[0].isAnyInteger) + return compiler.module.createI32(0); + if (typeArguments[0].isAnyFloat) { + arg0 = compiler.compileExpression(operands[0], typeArguments[0]); // reports + compiler.currentType = Type.bool; + if (typeArguments[0] == Type.f32) { + tempLocal = compiler.currentFunction.addLocal(Type.f32); + return compiler.module.createBinary(BinaryOp.NeF32, + compiler.module.createTeeLocal(tempLocal.index, arg0), + compiler.module.createGetLocal(tempLocal.index, NativeType.F32) + ); + } else { + tempLocal = compiler.currentFunction.addLocal(Type.f64); + return compiler.module.createBinary(BinaryOp.NeF64, + compiler.module.createTeeLocal(tempLocal.index, arg0), + compiler.module.createGetLocal(tempLocal.index, NativeType.F64) + ); + } + } + break; + + case "isFinite": + compiler.currentType = Type.bool; + if (!validateCall(compiler, typeArguments, 1, operands, 1, reportNode)) + return compiler.module.createUnreachable(); + if (typeArguments[0].isAnyInteger) + return compiler.module.createI32(1); + if (typeArguments[0].isAnyFloat) { + arg0 = compiler.compileExpression(operands[0], typeArguments[0]); // reports + compiler.currentType = Type.bool; + if (typeArguments[0] == Type.f32) { + tempLocal = compiler.currentFunction.addLocal(Type.f32); + return compiler.module.createSelect( + compiler.module.createBinary(BinaryOp.NeF32, + compiler.module.createUnary(UnaryOp.AbsF32, + compiler.module.createTeeLocal(tempLocal.index, arg0) + ), + compiler.module.createF32(Infinity) + ), + compiler.module.createI32(0), + compiler.module.createBinary(BinaryOp.EqF32, + compiler.module.createGetLocal(tempLocal.index, NativeType.F32), + compiler.module.createGetLocal(tempLocal.index, NativeType.F32) + ) + ); + } else { + tempLocal = compiler.currentFunction.addLocal(Type.f64); + return compiler.module.createSelect( + compiler.module.createBinary(BinaryOp.NeF64, + compiler.module.createUnary(UnaryOp.AbsF64, + compiler.module.createTeeLocal(tempLocal.index, arg0) + ), + compiler.module.createF64(Infinity) + ), + compiler.module.createI32(0), + compiler.module.createBinary(BinaryOp.EqF64, + compiler.module.createGetLocal(tempLocal.index, NativeType.F64), + compiler.module.createGetLocal(tempLocal.index, NativeType.F64) + ) + ); + } + } + break; + + case "assert": + compiler.currentType = Type.void; + if (!validateCall(compiler, typeArguments, 0, operands, 1, reportNode)) + return compiler.module.createUnreachable(); + arg0 = compiler.compileExpression(operands[0], Type.i32); // reports + compiler.currentType = Type.void; + return compiler.options.noDebug + ? compiler.module.createNop() + : compiler.module.createIf( + compiler.module.createUnary(UnaryOp.EqzI32, arg0), + compiler.module.createUnreachable() + ); + } + return 0; +} + +/** Validates a call to a builtin function. */ +function validateCall(compiler: Compiler, typeArguments: Type[], expectedTypeArguments: i32, operands: Expression[], expectedOperands: i32, reportNode: Node): bool { + if (typeArguments.length != expectedTypeArguments) { + compiler.error(DiagnosticCode.Expected_0_type_arguments_but_got_1, reportNode.range, expectedTypeArguments.toString(10), typeArguments.length.toString(10)); + return false; + } + if (operands.length != expectedOperands) { + compiler.error(DiagnosticCode.Expected_0_arguments_but_got_1, reportNode.range, expectedOperands.toString(10), operands.length.toString(10)); + return false; + } + return true; +} diff --git a/src/compiler.ts b/src/compiler.ts index 716e18ed..81b557f1 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -1,9 +1,10 @@ +import { compileCall as compileBuiltinCall } from "./builtins"; import { PATH_DELIMITER } from "./constants"; -import { DiagnosticCode, DiagnosticMessage, DiagnosticEmitter } from "./diagnostics"; -import { Module, MemorySegment, ExpressionRef, UnaryOp, BinaryOp, HostOp, NativeType, FunctionTypeRef, getExpressionId, ExpressionId } from "./module"; +import { DiagnosticCode, DiagnosticEmitter } from "./diagnostics"; +import { Module, MemorySegment, ExpressionRef, UnaryOp, BinaryOp, NativeType, FunctionTypeRef, getExpressionId, ExpressionId } from "./module"; import { Program, ClassPrototype, Class, Element, ElementKind, Enum, FunctionPrototype, Function, Global, Local, Namespace, Parameter } from "./program"; -import { CharCode, I64, U64, normalizePath, sb } from "./util"; -import { Token, Range } from "./tokenizer"; +import { I64, U64, sb } from "./util"; +import { Token } from "./tokenizer"; import { Node, @@ -972,6 +973,11 @@ export class Compiler extends DiagnosticEmitter { if (conversionKind == ConversionKind.NONE) return expr; + if (!fromType) { + _BinaryenExpressionPrint(expr); + throw new Error("WHAT"); + } + // void to any if (fromType.kind == TypeKind.VOID) { this.error(DiagnosticCode.Operation_not_supported, reportNode.range); @@ -1449,6 +1455,7 @@ export class Compiler extends DiagnosticEmitter { const element: Element | null = this.program.resolveElement(expression.expression, this.currentFunction); // reports if (!element) return this.module.createUnreachable(); + if (element.kind == ElementKind.FUNCTION_PROTOTYPE) { const functionPrototype: FunctionPrototype = element; let functionInstance: Function | null = null; @@ -1466,114 +1473,13 @@ export class Compiler extends DiagnosticEmitter { functionInstance = functionPrototype.instances.get(sb.join(",")); if (!functionInstance) { - let arg0: ExpressionRef, arg1: ExpressionRef, arg2: ExpressionRef; - - if (functionPrototype.internalName == "sizeof") { // no parameters - this.currentType = this.options.target == Target.WASM64 ? Type.usize64 : Type.usize32; - if (k != 1) { - this.error(DiagnosticCode.Expected_0_type_arguments_but_got_1, expression.range, "1", k.toString()); - return this.module.createUnreachable(); - } - if (expression.arguments.length != 0) { - this.error(DiagnosticCode.Expected_0_arguments_but_got_1, expression.range, "0", expression.arguments.length.toString()); - return this.module.createUnreachable(); - } - return this.options.target == Target.WASM64 - ? this.module.createI64(resolvedTypeArguments[0].byteSize, 0) - : this.module.createI32(resolvedTypeArguments[0].byteSize); - - } else if (functionPrototype.internalName == "load") { - if (k != 1) { - this.error(DiagnosticCode.Expected_0_type_arguments_but_got_1, expression.range, "1", k.toString()); - return this.module.createUnreachable(); - } - this.currentType = resolvedTypeArguments[0]; - if (expression.arguments.length != 1) { - this.error(DiagnosticCode.Expected_0_arguments_but_got_1, expression.range, "1", expression.arguments.length.toString()); - return this.module.createUnreachable(); - } - arg0 = this.compileExpression(expression.arguments[0], Type.usize32, ConversionKind.IMPLICIT); // reports - this.currentType = resolvedTypeArguments[0]; - if (!arg0) - return this.module.createUnreachable(); - return this.module.createLoad(resolvedTypeArguments[0].byteSize, resolvedTypeArguments[0].isSignedInteger, arg0, typeToNativeType(resolvedTypeArguments[0])); - - } else if (functionPrototype.internalName == "store") { - this.currentType = Type.void; - if (k != 1) { - this.error(DiagnosticCode.Expected_0_type_arguments_but_got_1, expression.range, "1", k.toString()); - return this.module.createUnreachable(); - } - if (expression.arguments.length != 2) { - this.error(DiagnosticCode.Expected_0_arguments_but_got_1, expression.range, "2", expression.arguments.length.toString()); - return this.module.createUnreachable(); - } - arg0 = this.compileExpression(expression.arguments[0], Type.usize32, ConversionKind.IMPLICIT); // reports - this.currentType = Type.void; - if (!arg0) - return this.module.createUnreachable(); - arg1 = this.compileExpression(expression.arguments[1], resolvedTypeArguments[0], ConversionKind.IMPLICIT); - this.currentType = Type.void; - if (!arg1) - return this.module.createUnreachable(); - return this.module.createStore(resolvedTypeArguments[0].byteSize, arg0, arg1, typeToNativeType(resolvedTypeArguments[0])); - - } else if (functionPrototype.internalName == "reinterpret") { - if (k != 2) { - this.error(DiagnosticCode.Expected_0_type_arguments_but_got_1, expression.range, "2", k.toString()); - return this.module.createUnreachable(); - } - this.currentType = resolvedTypeArguments[1]; - if (expression.arguments.length != 1) { - this.error(DiagnosticCode.Expected_0_arguments_but_got_1, expression.range, "1", expression.arguments.length.toString()); - return this.module.createUnreachable(); - } - - if (this.currentType == Type.f64) { - arg0 = this.compileExpression(expression.arguments[0], Type.i64); // reports - this.currentType = Type.f64; - return this.module.createUnary(UnaryOp.ReinterpretI64, arg0); - } - if (this.currentType == Type.f32) { - arg0 = this.compileExpression(expression.arguments[0], Type.i32); // reports - this.currentType = Type.f32; - return this.module.createUnary(UnaryOp.ReinterpretI32, arg0); - } - if (this.currentType.isLongInteger) { - arg0 = this.compileExpression(expression.arguments[0], Type.f64); // reports - this.currentType = Type.i64; - return this.module.createUnary(UnaryOp.ReinterpretF64, arg0); - } - if (this.currentType.isAnyInteger) { - arg0 = this.compileExpression(expression.arguments[0], Type.f32); // reports - this.currentType = Type.i32; - return this.module.createUnary(UnaryOp.ReinterpretF32, arg0); - } - - } else if (functionPrototype.internalName == "select") { - if (k != 1) { - this.error(DiagnosticCode.Expected_0_type_arguments_but_got_1, expression.range, "1", k.toString()); - return this.module.createUnreachable(); - } - this.currentType = resolvedTypeArguments[0]; - if (expression.arguments.length != 3) { - this.error(DiagnosticCode.Expected_0_arguments_but_got_1, expression.range, "3", expression.arguments.length.toString()); - return this.module.createUnreachable(); - } - arg0 = this.compileExpression(expression.arguments[0], this.currentType); // reports - if (!arg0) - return this.module.createUnreachable(); - arg1 = this.compileExpression(expression.arguments[1], this.currentType); // reports - if (!arg1) - return this.module.createUnreachable(); - arg2 = this.compileExpression(expression.arguments[2], Type.i32); // reports - this.currentType = resolvedTypeArguments[0]; - if (!arg2) - return this.module.createUnreachable(); - return this.module.createSelect(arg0, arg1, arg2); + this.currentType = contextualType; + let expr: ExpressionRef = compileBuiltinCall(this, functionPrototype.internalName, resolvedTypeArguments, expression.arguments, expression); + if (!expr) { + this.error(DiagnosticCode.Operation_not_supported, expression.range); + return this.module.createUnreachable(); } - this.error(DiagnosticCode.Operation_not_supported, expression.range); - return this.module.createUnreachable(); + return expr; } } else { // TODO: infer type arguments from parameter types if omitted @@ -1625,181 +1531,6 @@ export class Compiler extends DiagnosticEmitter { this.currentType = functionInstance.returnType; - if (functionInstance.isBuiltin) { - let tempLocal: Local; - switch (functionInstance.template.internalName) { - - case "clz": // i32/i64.clz - if (this.currentType.isLongInteger) - return this.module.createUnary(UnaryOp.ClzI64, operands[0]); - else if (this.currentType.isAnyInteger) - return this.module.createUnary(UnaryOp.ClzI32, operands[0]); - break; - - case "ctz": // i32/i64.ctz - if (this.currentType.isLongInteger) - return this.module.createUnary(UnaryOp.CtzI64, operands[0]); - else if (this.currentType.isAnyInteger) - return this.module.createUnary(UnaryOp.CtzI32, operands[0]); - break; - - case "popcnt": // i32/i64.popcnt - if (this.currentType.isLongInteger) - return this.module.createUnary(UnaryOp.PopcntI64, operands[0]); - else if (this.currentType.isAnyInteger) - return this.module.createUnary(UnaryOp.PopcntI32, operands[0]); - break; - - case "rotl": // i32/i64.rotl - if (this.currentType.isLongInteger) - return this.module.createBinary(BinaryOp.RotlI64, operands[0], operands[1]); - else if (this.currentType.isAnyInteger) - return this.module.createBinary(BinaryOp.RotlI32, operands[0], operands[1]); - break; - - case "rotr": // i32/i64.rotr - if (this.currentType.isLongInteger) - return this.module.createBinary(BinaryOp.RotrI64, operands[0], operands[1]); - else if (this.currentType.isAnyInteger) - return this.module.createBinary(BinaryOp.RotrI32, operands[0], operands[1]); - break; - - case "abs": // f32/f64.abs - if (this.currentType == Type.f64) - return this.module.createUnary(UnaryOp.AbsF64, operands[0]); - else if (this.currentType == Type.f32) - return this.module.createUnary(UnaryOp.AbsF32, operands[0]); - break; - - case "ceil": // f32/f64.ceil - if (this.currentType == Type.f64) - return this.module.createUnary(UnaryOp.CeilF64, operands[0]); - else if (this.currentType == Type.f32) - return this.module.createUnary(UnaryOp.CeilF32, operands[0]); - break; - - case "copysign": // f32/f64.copysign - if (this.currentType == Type.f64) - return this.module.createBinary(BinaryOp.CopysignF64, operands[0], operands[1]); - else if (this.currentType == Type.f32) - return this.module.createBinary(BinaryOp.CopysignF32, operands[0], operands[1]); - break; - - case "floor": // f32/f64.floor - if (this.currentType == Type.f64) - return this.module.createUnary(UnaryOp.FloorF64, operands[0]); - else if (this.currentType == Type.f32) - return this.module.createUnary(UnaryOp.FloorF32, operands[0]); - break; - - case "max": // f32/f64.max - if (this.currentType == Type.f64) - return this.module.createBinary(BinaryOp.MaxF64, operands[0], operands[1]); - else if (this.currentType == Type.f32) - return this.module.createBinary(BinaryOp.MaxF32, operands[0], operands[1]); - break; - - case "min": // f32/f64.min - if (this.currentType == Type.f64) - return this.module.createBinary(BinaryOp.MinF64, operands[0], operands[1]); - else if (this.currentType == Type.f32) - return this.module.createBinary(BinaryOp.MinF32, operands[0], operands[1]); - break; - - case "nearest": // f32/f64.nearest - if (this.currentType == Type.f64) - return this.module.createUnary(UnaryOp.NearestF64, operands[0]); - else if (this.currentType == Type.f32) - return this.module.createUnary(UnaryOp.NearestF32, operands[0]); - break; - - case "sqrt": // f32/f64.sqrt - if (this.currentType == Type.f64) - return this.module.createUnary(UnaryOp.SqrtF64, operands[0]); - else if (this.currentType == Type.f32) - return this.module.createUnary(UnaryOp.SqrtF32, operands[0]); - break; - - case "trunc": // f32/f64.trunc - if (this.currentType == Type.f64) - return this.module.createUnary(UnaryOp.TruncF64, operands[0]); - else if (this.currentType == Type.f32) - return this.module.createUnary(UnaryOp.TruncF32, operands[0]); - break; - - case "current_memory": - return this.module.createHost(HostOp.CurrentMemory); - - case "grow_memory": - // this.warning(DiagnosticCode.Operation_is_unsafe, reportNode.range); // unsure - return this.module.createHost(HostOp.GrowMemory, null, operands); - - case "unreachable": - this.currentType = previousType; - return this.module.createUnreachable(); - - case "isNaN": // value != value - if (functionInstance.typeArguments[0] == Type.f64) { - tempLocal = this.currentFunction.addLocal(Type.f64); - return this.module.createBinary(BinaryOp.NeF64, - this.module.createTeeLocal(tempLocal.index, operands[0]), - this.module.createGetLocal(tempLocal.index, NativeType.F64) - ); - } else if (functionInstance.typeArguments[0] == Type.f32) { - tempLocal = this.currentFunction.addLocal(Type.f32); - return this.module.createBinary(BinaryOp.NeF32, - this.module.createTeeLocal(tempLocal.index, operands[0]), - this.module.createGetLocal(tempLocal.index, NativeType.F32) - ); - } - break; - - 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.createUnary(UnaryOp.AbsF64, - 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.createUnary(UnaryOp.AbsF32, - 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) - ) - ); - } - break; - - case "assert": - return this.options.noDebug - ? this.module.createNop() - : this.module.createIf( - this.module.createUnary(UnaryOp.EqzI32, operands[0]), - this.module.createUnreachable() - ); - } - this.error(DiagnosticCode.Operation_not_supported, reportNode.range); - return this.module.createUnreachable(); - } - if (!functionInstance.isCompiled) this.compileFunction(functionInstance); @@ -2087,7 +1818,7 @@ export class Compiler extends DiagnosticEmitter { // helpers -function typeToNativeType(type: Type): NativeType { +export function typeToNativeType(type: Type): NativeType { return type.kind == TypeKind.F32 ? NativeType.F32 : type.kind == TypeKind.F64 @@ -2099,7 +1830,7 @@ function typeToNativeType(type: Type): NativeType { : NativeType.None; } -function typesToNativeTypes(types: Type[]): NativeType[] { +export function typesToNativeTypes(types: Type[]): NativeType[] { const k: i32 = types.length; const ret: NativeType[] = new Array(k); for (let i: i32 = 0; i < k; ++i) @@ -2107,7 +1838,7 @@ function typesToNativeTypes(types: Type[]): NativeType[] { return ret; } -function typeToNativeZero(module: Module, type: Type): ExpressionRef { +export function typeToNativeZero(module: Module, type: Type): ExpressionRef { return type.kind == TypeKind.F32 ? module.createF32(0) : type.kind == TypeKind.F64 @@ -2117,7 +1848,7 @@ function typeToNativeZero(module: Module, type: Type): ExpressionRef { : module.createI32(0); } -function typeToNativeOne(module: Module, type: Type): ExpressionRef { +export function typeToNativeOne(module: Module, type: Type): ExpressionRef { return type.kind == TypeKind.F32 ? module.createF32(1) : type.kind == TypeKind.F64 diff --git a/src/glue/js.d.ts b/src/glue/js.d.ts index 27af38aa..1a70fb15 100644 --- a/src/glue/js.d.ts +++ b/src/glue/js.d.ts @@ -14,3 +14,6 @@ declare type bool = boolean; // Raw memory access (here: Binaryen memory) declare function store(ptr: usize, val: T): void; declare function load(ptr: usize): T; + +// Other things that might or might not be useful +declare function select(ifTrue: T, ifFalse: T, condition: bool): T; diff --git a/src/glue/js.ts b/src/glue/js.ts index c9ca9096..528b6f63 100644 --- a/src/glue/js.ts +++ b/src/glue/js.ts @@ -8,6 +8,10 @@ globalScope["load"] = function load_u8(ptr: number) { return binaryen.HEAPU8[ptr]; }; +globalScope["select"] = function select(ifTrue: T, ifFalse: T, condition: bool): T { + return condition ? ifTrue : ifFalse; +}; + const binaryen = require("binaryen"); for (const key in binaryen) if (/^_(?:Binaryen|Relooper|malloc$|free$)/.test(key)) diff --git a/src/program.ts b/src/program.ts index a899e451..c34dd1ed 100644 --- a/src/program.ts +++ b/src/program.ts @@ -1,6 +1,7 @@ +import { initialize as initializeBuiltins } from "./builtins"; import { Target } from "./compiler"; import { GETTER_PREFIX, SETTER_PREFIX, PATH_DELIMITER } from "./constants"; -import { DiagnosticCode, DiagnosticMessage, DiagnosticEmitter, DiagnosticCategory } from "./diagnostics"; +import { DiagnosticCode, DiagnosticMessage, DiagnosticEmitter } from "./diagnostics"; import { Type, typesToString } from "./types"; import { I64 } from "./util"; import { @@ -57,7 +58,7 @@ class QueuedImport { declaration: ImportDeclaration; } -const reusableTypesStub: Map = new Map(); +const noTypesYet: Map = new Map(); export class Program extends DiagnosticEmitter { @@ -70,7 +71,7 @@ export class Program extends DiagnosticEmitter { /** Elements by internal name. */ elements: Map = new Map(); /** Types by internal name. */ - types: Map = reusableTypesStub; + types: Map = noTypesYet; /** Exports of individual files by internal name. Not global exports. */ exports: Map = new Map(); @@ -82,6 +83,22 @@ export class Program extends DiagnosticEmitter { /** Initializes the program and its elements prior to compilation. */ initialize(target: Target = Target.WASM32): void { this.target = target; + this.types = new Map([ + ["i8", Type.i8], + ["i16", Type.i16], + ["i32", Type.i32], + ["i64", Type.i64], + ["isize", target == Target.WASM64 ? Type.isize64 : Type.isize32], + ["u8", Type.u8], + ["u16", Type.u16], + ["u32", Type.u32], + ["u64", Type.u64], + ["usize", target == Target.WASM64 ? Type.usize64 : Type.usize32], + ["bool", Type.bool], + ["f32", Type.f32], + ["f64", Type.f64], + ["void", Type.void] + ]); initializeBuiltins(this); @@ -1110,120 +1127,3 @@ export class Interface extends Class { super(template, internalName, typeArguments, base); } } - -const builtinIntTypes: Type[] = [ Type.i32, Type.i64 ]; -const builtinFloatTypes: Type[] = [ Type.f32, Type.f64 ]; - -function initializeBuiltins(program: Program): void { - - // types - - program.types = new Map([ - ["i8", Type.i8], - ["i16", Type.i16], - ["i32", Type.i32], - ["i64", Type.i64], - ["isize", program.target == Target.WASM64 ? Type.isize64 : Type.isize32], - ["u8", Type.u8], - ["u16", Type.u16], - ["u32", Type.u32], - ["u64", Type.u64], - ["usize", program.target == Target.WASM64 ? Type.usize64 : Type.usize32], - ["bool", Type.bool], - ["f32", Type.f32], - ["f64", Type.f64], - ["void", Type.void] - ]); - - // functions - - const usize: Type = program.target == Target.WASM64 ? Type.usize64 : Type.usize32; - - addGenericUnaryBuiltin(program, "clz", builtinIntTypes); - addGenericUnaryBuiltin(program, "ctz", builtinIntTypes); - addGenericUnaryBuiltin(program, "popcnt", builtinIntTypes); - addGenericBinaryBuiltin(program, "rotl", builtinIntTypes); - addGenericBinaryBuiltin(program, "rotr", builtinIntTypes); - - addGenericUnaryBuiltin(program, "abs", builtinFloatTypes); - addGenericUnaryBuiltin(program, "ceil", builtinFloatTypes); - addGenericBinaryBuiltin(program, "copysign", builtinFloatTypes); - addGenericUnaryBuiltin(program, "floor", builtinFloatTypes); - addGenericBinaryBuiltin(program, "max", builtinFloatTypes); - addGenericBinaryBuiltin(program, "min", builtinFloatTypes); - addGenericUnaryBuiltin(program, "nearest", builtinFloatTypes); - addGenericUnaryBuiltin(program, "sqrt", builtinFloatTypes); - addGenericUnaryBuiltin(program, "trunc", builtinFloatTypes); - - addSimpleBuiltin(program, "current_memory", [], usize); - addSimpleBuiltin(program, "grow_memory", [ usize ], usize); - addSimpleBuiltin(program, "unreachable", [], Type.void); - - addGenericAnyBuiltin(program, "load"); - addGenericAnyBuiltin(program, "store"); - addGenericAnyBuiltin(program, "reinterpret"); - addGenericAnyBuiltin(program, "select"); - - addGenericAnyBuiltin(program, "sizeof"); - addGenericUnaryTestBuiltin(program, "isNaN", builtinFloatTypes); - addGenericUnaryTestBuiltin(program, "isFinite", builtinFloatTypes); - addSimpleBuiltin(program, "assert", [ Type.bool ], Type.void); -} - -/** Adds a simple (non-generic) builtin. */ -function addSimpleBuiltin(program: Program, name: string, parameterTypes: Type[], returnType: Type) { - let prototype: FunctionPrototype = new FunctionPrototype(program, name, null, null); - prototype.isGeneric = false; - prototype.isBuiltin = true; - const k: i32 = parameterTypes.length; - const parameters: Parameter[] = new Array(k); - for (let i: i32 = 0; i < k; ++i) - parameters[i] = new Parameter("arg" + i, parameterTypes[i], null); - prototype.instances.set("", new Function(prototype, name, [], parameters, returnType, null)); - program.elements.set(name, prototype); -} - -/** Adds a generic unary builtin that takes and returns a value of its generic type. */ -function addGenericUnaryBuiltin(program: Program, name: string, types: Type[]): void { - let prototype: FunctionPrototype = new FunctionPrototype(program, name, null, null); - prototype.isGeneric = true; - prototype.isBuiltin = true; - for (let i: i32 = 0, k = types.length; i < k; ++i) { - const typeName: string = types[i].toString(); - prototype.instances.set(typeName, new Function(prototype, name + "<" + typeName + ">", [ types[i] ], [ new Parameter("value", types[i], null) ], types[i], null)); - } - program.elements.set(name, prototype); -} - -/** Adds a generic binary builtin that takes two and returns a value of its generic type. */ -function addGenericBinaryBuiltin(program: Program, name: string, types: Type[]): void { - let prototype: FunctionPrototype = new FunctionPrototype(program, name, null, null); - prototype.isGeneric = true; - prototype.isBuiltin = true; - for (let i: i32 = 0, k = types.length; i < k; ++i) { - const typeName: string = types[i].toString(); - prototype.instances.set(typeName, new Function(prototype, name + "<" + typeName + ">", [ types[i], types[i] ], [ new Parameter("left", types[i], null), new Parameter("right", types[i], null) ], types[i], null)); - } - program.elements.set(name, prototype); -} - -/** Adds a generic unary builtin that alwways returns `bool`. */ -function addGenericUnaryTestBuiltin(program: Program, name: string, types: Type[]): void { - let prototype: FunctionPrototype = new FunctionPrototype(program, name, null, null); - prototype.isGeneric = true; - prototype.isBuiltin = true; - for (let i: i32 = 0, k = types.length; i < k; ++i) { - const typeName: string = types[i].toString(); - prototype.instances.set(typeName, new Function(prototype, name + "<" + typeName + ">", [ types[i] ], [ new Parameter("value", types[i], null) ], Type.bool, null)); - } - program.elements.set(name, prototype); -} - -/** Adds a special generic builtin that takes any type argument. */ -function addGenericAnyBuiltin(program: Program, name: string): void { - let prototype: FunctionPrototype = new FunctionPrototype(program, name, null, null); - prototype.isGeneric = true; - prototype.isBuiltin = true; - program.elements.set(name, prototype); - // instances are hard coded in compiler.ts -} diff --git a/tests/compiler.ts b/tests/compiler.ts index 64e78d26..884fcea1 100644 --- a/tests/compiler.ts +++ b/tests/compiler.ts @@ -50,8 +50,10 @@ glob.sync(filter, { cwd: __dirname + "/compiler" }).forEach(filename => { } module.optimize(); actualOptimized = module.toText(); - } else + } else { + process.exitCode = 1; console.log(chalk.default.red("validate ERROR")); + } if (isCreate) { fs.writeFileSync(__dirname + "/compiler/" + fixture, actual, { encoding: "utf8" });