mirror of
https://github.com/fluencelabs/assemblyscript
synced 2025-04-26 07:22:21 +00:00
Refactored builtins
This commit is contained in:
parent
b7030d4dea
commit
40b814ac73
12
assembly.d.ts
vendored
12
assembly.d.ts
vendored
@ -39,18 +39,18 @@ declare function popcnt<T = i32 | i64>(value: T): T;
|
|||||||
declare function rotl<T = i32 | i64>(value: T, shift: T): T;
|
declare function rotl<T = i32 | i64>(value: T, shift: T): T;
|
||||||
/** Performs the sign-agnostic rotate right operation on a 32-bit or 64-bit integer. */
|
/** Performs the sign-agnostic rotate right operation on a 32-bit or 64-bit integer. */
|
||||||
declare function rotr<T = i32 | i64>(value: T, shift: T): T;
|
declare function rotr<T = i32 | i64>(value: T, shift: T): T;
|
||||||
/** Computes the absolute value of a 32-bit or 64-bit float. */
|
/** Computes the absolute value of an integer or float. */
|
||||||
declare function abs<T = f32 | f64>(value: T): T;
|
declare function abs<T = i32 | i64 | f32 | f64>(value: T): T;
|
||||||
|
/** Determines the maximum of two integers or floats. If either operand is `NaN`, returns `NaN`. */
|
||||||
|
declare function max<T = i32 | i64 | f32 | f64>(left: T, right: T): T;
|
||||||
|
/** Determines the minimum of two integers or floats. If either operand is `NaN`, returns `NaN`. */
|
||||||
|
declare function min<T = i32 | i64 | f32 | f64>(left: T, right: T): T;
|
||||||
/** Performs the ceiling operation on a 32-bit or 64-bit float. */
|
/** Performs the ceiling operation on a 32-bit or 64-bit float. */
|
||||||
declare function ceil<T = f32 | f64>(value: T): T;
|
declare function ceil<T = f32 | f64>(value: T): T;
|
||||||
/** Composes a 32-bit or 64-bit float from the magnitude of `x` and the sign of `y`. */
|
/** Composes a 32-bit or 64-bit float from the magnitude of `x` and the sign of `y`. */
|
||||||
declare function copysign<T = f32 | f64>(x: T, y: T): T;
|
declare function copysign<T = f32 | f64>(x: T, y: T): T;
|
||||||
/** Performs the floor operation on a 32-bit or 64-bit float. */
|
/** Performs the floor operation on a 32-bit or 64-bit float. */
|
||||||
declare function floor<T = f32 | f64>(value: T): T;
|
declare function floor<T = f32 | f64>(value: T): T;
|
||||||
/** Determines the maximum of two 32-bit or 64-bit floats. If either operand is `NaN`, returns `NaN`. */
|
|
||||||
declare function max<T = f32 | f64>(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<T = f32 | f64>(left: T, right: T): T;
|
|
||||||
/** Rounds to the nearest integer tied to even of a 32-bit or 64-bit float. */
|
/** Rounds to the nearest integer tied to even of a 32-bit or 64-bit float. */
|
||||||
declare function nearest<T = f32 | f64>(value: T): T;
|
declare function nearest<T = f32 | f64>(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. */
|
/** Reinterprets the bits of a value of type `T1` as type `T2`. Valid reinterpretations are i32 to/from f32 and i64 to/from f64. */
|
||||||
|
431
src/builtins.ts
Normal file
431
src/builtins.ts
Normal file
@ -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>(Type.usize64, Type.usize32, compiler.options.target == Target.WASM64);
|
||||||
|
|
||||||
|
let arg0: ExpressionRef,
|
||||||
|
arg1: ExpressionRef,
|
||||||
|
arg2: ExpressionRef;
|
||||||
|
|
||||||
|
let tempLocal: Local;
|
||||||
|
|
||||||
|
switch (internalName) {
|
||||||
|
|
||||||
|
case "clz": // clz<T>(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<T>(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<T>(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<T>(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<T>(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<T>(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<T>(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<T>(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<T>(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<T>(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<T>(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<T>() -> 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<T>(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<T>(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<T1,T2>(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<T>(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;
|
||||||
|
}
|
311
src/compiler.ts
311
src/compiler.ts
@ -1,9 +1,10 @@
|
|||||||
|
import { compileCall as compileBuiltinCall } from "./builtins";
|
||||||
import { PATH_DELIMITER } from "./constants";
|
import { PATH_DELIMITER } from "./constants";
|
||||||
import { DiagnosticCode, DiagnosticMessage, DiagnosticEmitter } from "./diagnostics";
|
import { DiagnosticCode, DiagnosticEmitter } from "./diagnostics";
|
||||||
import { Module, MemorySegment, ExpressionRef, UnaryOp, BinaryOp, HostOp, NativeType, FunctionTypeRef, getExpressionId, ExpressionId } from "./module";
|
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 { Program, ClassPrototype, Class, Element, ElementKind, Enum, FunctionPrototype, Function, Global, Local, Namespace, Parameter } from "./program";
|
||||||
import { CharCode, I64, U64, normalizePath, sb } from "./util";
|
import { I64, U64, sb } from "./util";
|
||||||
import { Token, Range } from "./tokenizer";
|
import { Token } from "./tokenizer";
|
||||||
import {
|
import {
|
||||||
|
|
||||||
Node,
|
Node,
|
||||||
@ -972,6 +973,11 @@ export class Compiler extends DiagnosticEmitter {
|
|||||||
if (conversionKind == ConversionKind.NONE)
|
if (conversionKind == ConversionKind.NONE)
|
||||||
return expr;
|
return expr;
|
||||||
|
|
||||||
|
if (!fromType) {
|
||||||
|
_BinaryenExpressionPrint(expr);
|
||||||
|
throw new Error("WHAT");
|
||||||
|
}
|
||||||
|
|
||||||
// void to any
|
// void to any
|
||||||
if (fromType.kind == TypeKind.VOID) {
|
if (fromType.kind == TypeKind.VOID) {
|
||||||
this.error(DiagnosticCode.Operation_not_supported, reportNode.range);
|
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
|
const element: Element | null = this.program.resolveElement(expression.expression, this.currentFunction); // reports
|
||||||
if (!element)
|
if (!element)
|
||||||
return this.module.createUnreachable();
|
return this.module.createUnreachable();
|
||||||
|
|
||||||
if (element.kind == ElementKind.FUNCTION_PROTOTYPE) {
|
if (element.kind == ElementKind.FUNCTION_PROTOTYPE) {
|
||||||
const functionPrototype: FunctionPrototype = <FunctionPrototype>element;
|
const functionPrototype: FunctionPrototype = <FunctionPrototype>element;
|
||||||
let functionInstance: Function | null = null;
|
let functionInstance: Function | null = null;
|
||||||
@ -1466,114 +1473,13 @@ export class Compiler extends DiagnosticEmitter {
|
|||||||
|
|
||||||
functionInstance = <Function | null>functionPrototype.instances.get(sb.join(","));
|
functionInstance = <Function | null>functionPrototype.instances.get(sb.join(","));
|
||||||
if (!functionInstance) {
|
if (!functionInstance) {
|
||||||
let arg0: ExpressionRef, arg1: ExpressionRef, arg2: ExpressionRef;
|
this.currentType = contextualType;
|
||||||
|
let expr: ExpressionRef = compileBuiltinCall(this, functionPrototype.internalName, resolvedTypeArguments, expression.arguments, expression);
|
||||||
if (functionPrototype.internalName == "sizeof") { // no parameters
|
if (!expr) {
|
||||||
this.currentType = this.options.target == Target.WASM64 ? Type.usize64 : Type.usize32;
|
this.error(DiagnosticCode.Operation_not_supported, expression.range);
|
||||||
if (k != 1) {
|
return this.module.createUnreachable();
|
||||||
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.error(DiagnosticCode.Operation_not_supported, expression.range);
|
return expr;
|
||||||
return this.module.createUnreachable();
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// TODO: infer type arguments from parameter types if omitted
|
// TODO: infer type arguments from parameter types if omitted
|
||||||
@ -1625,181 +1531,6 @@ export class Compiler extends DiagnosticEmitter {
|
|||||||
|
|
||||||
this.currentType = functionInstance.returnType;
|
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)
|
if (!functionInstance.isCompiled)
|
||||||
this.compileFunction(functionInstance);
|
this.compileFunction(functionInstance);
|
||||||
|
|
||||||
@ -2087,7 +1818,7 @@ export class Compiler extends DiagnosticEmitter {
|
|||||||
|
|
||||||
// helpers
|
// helpers
|
||||||
|
|
||||||
function typeToNativeType(type: Type): NativeType {
|
export function typeToNativeType(type: Type): NativeType {
|
||||||
return type.kind == TypeKind.F32
|
return type.kind == TypeKind.F32
|
||||||
? NativeType.F32
|
? NativeType.F32
|
||||||
: type.kind == TypeKind.F64
|
: type.kind == TypeKind.F64
|
||||||
@ -2099,7 +1830,7 @@ function typeToNativeType(type: Type): NativeType {
|
|||||||
: NativeType.None;
|
: NativeType.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
function typesToNativeTypes(types: Type[]): NativeType[] {
|
export function typesToNativeTypes(types: Type[]): NativeType[] {
|
||||||
const k: i32 = types.length;
|
const k: i32 = types.length;
|
||||||
const ret: NativeType[] = new Array(k);
|
const ret: NativeType[] = new Array(k);
|
||||||
for (let i: i32 = 0; i < k; ++i)
|
for (let i: i32 = 0; i < k; ++i)
|
||||||
@ -2107,7 +1838,7 @@ function typesToNativeTypes(types: Type[]): NativeType[] {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
function typeToNativeZero(module: Module, type: Type): ExpressionRef {
|
export function typeToNativeZero(module: Module, type: Type): ExpressionRef {
|
||||||
return type.kind == TypeKind.F32
|
return type.kind == TypeKind.F32
|
||||||
? module.createF32(0)
|
? module.createF32(0)
|
||||||
: type.kind == TypeKind.F64
|
: type.kind == TypeKind.F64
|
||||||
@ -2117,7 +1848,7 @@ function typeToNativeZero(module: Module, type: Type): ExpressionRef {
|
|||||||
: module.createI32(0);
|
: module.createI32(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function typeToNativeOne(module: Module, type: Type): ExpressionRef {
|
export function typeToNativeOne(module: Module, type: Type): ExpressionRef {
|
||||||
return type.kind == TypeKind.F32
|
return type.kind == TypeKind.F32
|
||||||
? module.createF32(1)
|
? module.createF32(1)
|
||||||
: type.kind == TypeKind.F64
|
: type.kind == TypeKind.F64
|
||||||
|
3
src/glue/js.d.ts
vendored
3
src/glue/js.d.ts
vendored
@ -14,3 +14,6 @@ declare type bool = boolean;
|
|||||||
// Raw memory access (here: Binaryen memory)
|
// Raw memory access (here: Binaryen memory)
|
||||||
declare function store<T = u8>(ptr: usize, val: T): void;
|
declare function store<T = u8>(ptr: usize, val: T): void;
|
||||||
declare function load<T = u8>(ptr: usize): T;
|
declare function load<T = u8>(ptr: usize): T;
|
||||||
|
|
||||||
|
// Other things that might or might not be useful
|
||||||
|
declare function select<T>(ifTrue: T, ifFalse: T, condition: bool): T;
|
||||||
|
@ -8,6 +8,10 @@ globalScope["load"] = function load_u8(ptr: number) {
|
|||||||
return binaryen.HEAPU8[ptr];
|
return binaryen.HEAPU8[ptr];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
globalScope["select"] = function select<T>(ifTrue: T, ifFalse: T, condition: bool): T {
|
||||||
|
return condition ? ifTrue : ifFalse;
|
||||||
|
};
|
||||||
|
|
||||||
const binaryen = require("binaryen");
|
const binaryen = require("binaryen");
|
||||||
for (const key in binaryen)
|
for (const key in binaryen)
|
||||||
if (/^_(?:Binaryen|Relooper|malloc$|free$)/.test(key))
|
if (/^_(?:Binaryen|Relooper|malloc$|free$)/.test(key))
|
||||||
|
140
src/program.ts
140
src/program.ts
@ -1,6 +1,7 @@
|
|||||||
|
import { initialize as initializeBuiltins } from "./builtins";
|
||||||
import { Target } from "./compiler";
|
import { Target } from "./compiler";
|
||||||
import { GETTER_PREFIX, SETTER_PREFIX, PATH_DELIMITER } from "./constants";
|
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 { Type, typesToString } from "./types";
|
||||||
import { I64 } from "./util";
|
import { I64 } from "./util";
|
||||||
import {
|
import {
|
||||||
@ -57,7 +58,7 @@ class QueuedImport {
|
|||||||
declaration: ImportDeclaration;
|
declaration: ImportDeclaration;
|
||||||
}
|
}
|
||||||
|
|
||||||
const reusableTypesStub: Map<string,Type> = new Map();
|
const noTypesYet: Map<string,Type> = new Map();
|
||||||
|
|
||||||
export class Program extends DiagnosticEmitter {
|
export class Program extends DiagnosticEmitter {
|
||||||
|
|
||||||
@ -70,7 +71,7 @@ export class Program extends DiagnosticEmitter {
|
|||||||
/** Elements by internal name. */
|
/** Elements by internal name. */
|
||||||
elements: Map<string,Element> = new Map();
|
elements: Map<string,Element> = new Map();
|
||||||
/** Types by internal name. */
|
/** Types by internal name. */
|
||||||
types: Map<string,Type> = reusableTypesStub;
|
types: Map<string,Type> = noTypesYet;
|
||||||
/** Exports of individual files by internal name. Not global exports. */
|
/** Exports of individual files by internal name. Not global exports. */
|
||||||
exports: Map<string,Element> = new Map();
|
exports: Map<string,Element> = new Map();
|
||||||
|
|
||||||
@ -82,6 +83,22 @@ export class Program extends DiagnosticEmitter {
|
|||||||
/** Initializes the program and its elements prior to compilation. */
|
/** Initializes the program and its elements prior to compilation. */
|
||||||
initialize(target: Target = Target.WASM32): void {
|
initialize(target: Target = Target.WASM32): void {
|
||||||
this.target = target;
|
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);
|
initializeBuiltins(this);
|
||||||
|
|
||||||
@ -1110,120 +1127,3 @@ export class Interface extends Class {
|
|||||||
super(template, internalName, typeArguments, base);
|
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
|
|
||||||
}
|
|
||||||
|
@ -50,8 +50,10 @@ glob.sync(filter, { cwd: __dirname + "/compiler" }).forEach(filename => {
|
|||||||
}
|
}
|
||||||
module.optimize();
|
module.optimize();
|
||||||
actualOptimized = module.toText();
|
actualOptimized = module.toText();
|
||||||
} else
|
} else {
|
||||||
|
process.exitCode = 1;
|
||||||
console.log(chalk.default.red("validate ERROR"));
|
console.log(chalk.default.red("validate ERROR"));
|
||||||
|
}
|
||||||
|
|
||||||
if (isCreate) {
|
if (isCreate) {
|
||||||
fs.writeFileSync(__dirname + "/compiler/" + fixture, actual, { encoding: "utf8" });
|
fs.writeFileSync(__dirname + "/compiler/" + fixture, actual, { encoding: "utf8" });
|
||||||
|
Loading…
x
Reference in New Issue
Block a user