Portable conversions

This commit is contained in:
dcodeIO
2017-12-15 17:23:04 +01:00
parent 7cf879fb4b
commit 4b3cc981a6
31 changed files with 1147 additions and 483 deletions

View File

@ -1,4 +1,4 @@
import { Compiler, Target, typeToNativeType, typeToNativeOne, typeToNativeZero } from "./compiler";
import { Compiler, Target, ConversionKind, typeToNativeType, typeToNativeOne, typeToNativeZero } from "./compiler";
import { DiagnosticCode } from "./diagnostics";
import { Node, Expression } from "./ast";
import { Type } from "./types";
@ -7,35 +7,48 @@ import { Program, ElementFlags, Element, Global, FunctionPrototype, Local } from
/** Initializes the specified program with built-in functions. */
export function initialize(program: Program): void {
// math
addFunction(program, "isNaN", true);
addFunction(program, "isFinite", true);
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, "ceil", true);
addFunction(program, "floor", true);
addFunction(program, "copysign", true);
addFunction(program, "nearest", true);
addFunction(program, "reinterpret", true);
addFunction(program, "sqrt", true);
addFunction(program, "trunc", true);
addFunction(program, "current_memory");
addFunction(program, "grow_memory");
addFunction(program, "unreachable");
// memory access
addFunction(program, "load", true);
addFunction(program, "store", true);
addFunction(program, "reinterpret", true);
addFunction(program, "select", true);
addFunction(program, "sizeof", true);
addFunction(program, "changetype", true);
addFunction(program, "isNaN", true);
addFunction(program, "isFinite", true);
addFunction(program, "assert");
// control flow
addFunction(program, "select", true);
addFunction(program, "unreachable");
// host operations
addFunction(program, "current_memory");
addFunction(program, "grow_memory");
// imported
addFunction(program, "parseInt");
addFunction(program, "parseFloat");
// other
addFunction(program, "changetype", true);
addFunction(program, "assert");
// conversions and limits
addFunction(program, "i8").members = new Map([
[ "MIN_VALUE", new Global(program, "i8.MIN_VALUE", null, Type.i8).withConstantIntegerValue(-128, -1) ],
[ "MAX_VALUE", new Global(program, "i8.MAX_VALUE", null, Type.i8).withConstantIntegerValue(127, 0) ]
@ -116,6 +129,76 @@ export function compileCall(compiler: Compiler, prototype: FunctionPrototype, ty
switch (prototype.internalName) {
// math
case "isNaN": // isNaN<T>(value: T) -> bool
compiler.currentType = Type.bool;
if (!validateCall(compiler, typeArguments, 1, operands, 1, reportNode))
return module.createUnreachable();
if (typeArguments[0].isAnyInteger)
return module.createI32(0);
if (typeArguments[0].isAnyFloat) {
arg0 = compiler.compileExpression(operands[0], typeArguments[0]); // reports
compiler.currentType = Type.bool;
if (typeArguments[0] == Type.f32) {
tempLocal0 = compiler.currentFunction.getAndFreeTempLocal(Type.f32);
return module.createBinary(BinaryOp.NeF32,
module.createTeeLocal(tempLocal0.index, arg0),
module.createGetLocal(tempLocal0.index, NativeType.F32)
);
} else {
tempLocal0 = compiler.currentFunction.getAndFreeTempLocal(Type.f64);
return module.createBinary(BinaryOp.NeF64,
module.createTeeLocal(tempLocal0.index, arg0),
module.createGetLocal(tempLocal0.index, NativeType.F64)
);
}
}
break;
case "isFinite": // isFinite<T>(value: T) -> bool
compiler.currentType = Type.bool;
if (!validateCall(compiler, typeArguments, 1, operands, 1, reportNode))
return module.createUnreachable();
if (typeArguments[0].isAnyInteger)
return module.createI32(1);
if (typeArguments[0].isAnyFloat) {
arg0 = compiler.compileExpression(operands[0], typeArguments[0]); // reports
compiler.currentType = Type.bool;
if (typeArguments[0] == Type.f32) {
tempLocal0 = compiler.currentFunction.getAndFreeTempLocal(Type.f32);
return module.createSelect(
module.createBinary(BinaryOp.NeF32,
module.createUnary(UnaryOp.AbsF32,
module.createTeeLocal(tempLocal0.index, arg0)
),
module.createF32(Infinity)
),
module.createI32(0),
module.createBinary(BinaryOp.EqF32,
module.createGetLocal(tempLocal0.index, NativeType.F32),
module.createGetLocal(tempLocal0.index, NativeType.F32)
)
);
} else {
tempLocal0 = compiler.currentFunction.getAndFreeTempLocal(Type.f64);
return module.createSelect(
module.createBinary(BinaryOp.NeF64,
module.createUnary(UnaryOp.AbsF64,
module.createTeeLocal(tempLocal0.index, arg0)
),
module.createF64(Infinity)
),
module.createI32(0),
module.createBinary(BinaryOp.EqF64,
module.createGetLocal(tempLocal0.index, NativeType.F64),
module.createGetLocal(tempLocal0.index, NativeType.F64)
)
);
}
}
break;
case "clz": // clz<T>(value: T) -> T
if (!validateCall(compiler, typeArguments, 1, operands, 1, reportNode))
return module.createUnreachable();
@ -357,47 +440,6 @@ export function compileCall(compiler: Compiler, prototype: FunctionPrototype, ty
}
break;
case "sqrt": // sqrt<T>(value: T) -> T
if (!validateCall(compiler, typeArguments, 1, operands, 1, reportNode))
return module.createUnreachable();
if ((compiler.currentType = typeArguments[0]).isAnyFloat) {
arg0 = compiler.compileExpression(operands[0], typeArguments[0]);
return (compiler.currentType = typeArguments[0]) == Type.f32 // sic
? module.createUnary(UnaryOp.SqrtF32, arg0)
: module.createUnary(UnaryOp.SqrtF64, arg0);
}
break;
case "trunc": // trunc<T>(value: T) -> T
if (!validateCall(compiler, typeArguments, 1, operands, 1, reportNode))
return module.createUnreachable();
if ((compiler.currentType = typeArguments[0]).isAnyFloat) {
arg0 = compiler.compileExpression(operands[0], typeArguments[0]);
return (compiler.currentType = typeArguments[0]) == Type.f32 // sic
? module.createUnary(UnaryOp.TruncF32, arg0)
: module.createUnary(UnaryOp.TruncF64, arg0);
}
break;
case "load": // load<T>(offset: usize) -> T
if (!validateCall(compiler, typeArguments, 1, operands, 1, reportNode))
return module.createUnreachable();
arg0 = compiler.compileExpression(operands[0], usizeType); // reports
if ((compiler.currentType = typeArguments[0]) != Type.void)
return 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 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 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 module.createUnreachable();
@ -424,6 +466,59 @@ export function compileCall(compiler: Compiler, prototype: FunctionPrototype, ty
}
break;
case "sqrt": // sqrt<T>(value: T) -> T
if (!validateCall(compiler, typeArguments, 1, operands, 1, reportNode))
return module.createUnreachable();
if ((compiler.currentType = typeArguments[0]).isAnyFloat) {
arg0 = compiler.compileExpression(operands[0], typeArguments[0]);
return (compiler.currentType = typeArguments[0]) == Type.f32 // sic
? module.createUnary(UnaryOp.SqrtF32, arg0)
: module.createUnary(UnaryOp.SqrtF64, arg0);
}
break;
case "trunc": // trunc<T>(value: T) -> T
if (!validateCall(compiler, typeArguments, 1, operands, 1, reportNode))
return module.createUnreachable();
if ((compiler.currentType = typeArguments[0]).isAnyFloat) {
arg0 = compiler.compileExpression(operands[0], typeArguments[0]);
return (compiler.currentType = typeArguments[0]) == Type.f32 // sic
? module.createUnary(UnaryOp.TruncF32, arg0)
: module.createUnary(UnaryOp.TruncF64, arg0);
}
break;
// memory access
case "load": // load<T>(offset: usize) -> T
if (!validateCall(compiler, typeArguments, 1, operands, 1, reportNode))
return module.createUnreachable();
arg0 = compiler.compileExpression(operands[0], usizeType); // reports
if ((compiler.currentType = typeArguments[0]) != Type.void)
return 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 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 module.createStore(typeArguments[0].byteSize, arg0, arg1, typeToNativeType(typeArguments[0]));
break;
case "sizeof": // sizeof<T>() -> usize
compiler.currentType = usizeType;
if (!validateCall(compiler, typeArguments, 1, operands, 0, reportNode))
return module.createUnreachable();
return usizeType.isLongInteger
? module.createI64(typeArguments[0].byteSize, 0)
: module.createI32(typeArguments[0].byteSize);
// control flow
case "select": // select<T>(ifTrue: T, ifFalse: T, condition: bool) -> T
if (!validateCall(compiler, typeArguments, 1, operands, 3, reportNode))
return module.createUnreachable();
@ -436,6 +531,13 @@ export function compileCall(compiler: Compiler, prototype: FunctionPrototype, ty
}
break;
case "unreachable": // unreachable() -> *
// does not modify currentType
validateCall(compiler, typeArguments, 0, operands, 0, reportNode);
return module.createUnreachable();
// host operations
case "current_memory": // current_memory() -> i32
compiler.currentType = Type.i32;
if (!validateCall(compiler, typeArguments, 0, operands, 0, reportNode))
@ -449,121 +551,7 @@ export function compileCall(compiler: Compiler, prototype: FunctionPrototype, ty
arg0 = compiler.compileExpression(operands[0], Type.i32);
return module.createHost(HostOp.GrowMemory, null, [ arg0 ]);
case "unreachable": // unreachable() -> *
// does not modify currentType
validateCall(compiler, typeArguments, 0, operands, 0, reportNode);
return module.createUnreachable();
case "sizeof": // sizeof<T>() -> usize
compiler.currentType = usizeType;
if (!validateCall(compiler, typeArguments, 1, operands, 0, reportNode))
return module.createUnreachable();
return usizeType.isLongInteger
? module.createI64(typeArguments[0].byteSize, 0)
: module.createI32(typeArguments[0].byteSize);
case "changetype": // changetype<T1,T2>(value: T1) -> T2
if (!validateCall(compiler, typeArguments, 2, operands, 1, reportNode))
return module.createUnreachable();
if ((typeArguments[0] == usizeType && typeArguments[1].classType) || (typeArguments[0].classType && typeArguments[1] == usizeType)) {
arg0 = compiler.compileExpression(operands[0], typeArguments[0]);
compiler.currentType = typeArguments[1];
return arg0;
}
compiler.error(DiagnosticCode.Type_0_cannot_be_changed_to_type_1, reportNode.range, typeArguments[0].toString(), typeArguments[1].toString());
return module.createUnreachable();
case "isNaN": // isNaN<T>(value: T) -> bool
compiler.currentType = Type.bool;
if (!validateCall(compiler, typeArguments, 1, operands, 1, reportNode))
return module.createUnreachable();
if (typeArguments[0].isAnyInteger)
return module.createI32(0);
if (typeArguments[0].isAnyFloat) {
arg0 = compiler.compileExpression(operands[0], typeArguments[0]); // reports
compiler.currentType = Type.bool;
if (typeArguments[0] == Type.f32) {
tempLocal0 = compiler.currentFunction.getAndFreeTempLocal(Type.f32);
return module.createBinary(BinaryOp.NeF32,
module.createTeeLocal(tempLocal0.index, arg0),
module.createGetLocal(tempLocal0.index, NativeType.F32)
);
} else {
tempLocal0 = compiler.currentFunction.getAndFreeTempLocal(Type.f64);
return module.createBinary(BinaryOp.NeF64,
module.createTeeLocal(tempLocal0.index, arg0),
module.createGetLocal(tempLocal0.index, NativeType.F64)
);
}
}
break;
case "isFinite": // isFinite<T>(value: T) -> bool
compiler.currentType = Type.bool;
if (!validateCall(compiler, typeArguments, 1, operands, 1, reportNode))
return module.createUnreachable();
if (typeArguments[0].isAnyInteger)
return module.createI32(1);
if (typeArguments[0].isAnyFloat) {
arg0 = compiler.compileExpression(operands[0], typeArguments[0]); // reports
compiler.currentType = Type.bool;
if (typeArguments[0] == Type.f32) {
tempLocal0 = compiler.currentFunction.getAndFreeTempLocal(Type.f32);
return module.createSelect(
module.createBinary(BinaryOp.NeF32,
module.createUnary(UnaryOp.AbsF32,
module.createTeeLocal(tempLocal0.index, arg0)
),
module.createF32(Infinity)
),
module.createI32(0),
module.createBinary(BinaryOp.EqF32,
module.createGetLocal(tempLocal0.index, NativeType.F32),
module.createGetLocal(tempLocal0.index, NativeType.F32)
)
);
} else {
tempLocal0 = compiler.currentFunction.getAndFreeTempLocal(Type.f64);
return module.createSelect(
module.createBinary(BinaryOp.NeF64,
module.createUnary(UnaryOp.AbsF64,
module.createTeeLocal(tempLocal0.index, arg0)
),
module.createF64(Infinity)
),
module.createI32(0),
module.createBinary(BinaryOp.EqF64,
module.createGetLocal(tempLocal0.index, NativeType.F64),
module.createGetLocal(tempLocal0.index, NativeType.F64)
)
);
}
}
break;
case "assert": // assert(isTrue: bool) -> void
compiler.currentType = Type.void;
if (typeArguments.length != 0) {
compiler.error(DiagnosticCode.Expected_0_type_arguments_but_got_1, reportNode.range, "0", typeArguments.length.toString(10));
return module.createUnreachable();
}
if (operands.length < 1) {
compiler.error(DiagnosticCode.Expected_at_least_0_arguments_but_got_1, reportNode.range, "1", operands.length.toString(10));
return module.createUnreachable();
}
if (operands.length > 2) {
compiler.error(DiagnosticCode.Expected_0_arguments_but_got_1, reportNode.range, "2", operands.length.toString(10));
return module.createUnreachable();
}
arg0 = compiler.compileExpression(operands[0], Type.i32); // reports
arg1 = operands.length > 1 ? compiler.compileExpression(operands[1], usizeType) : typeToNativeZero(module, usizeType); // TODO: string type
compiler.currentType = Type.void;
return compiler.options.noAssert
? module.createNop()
: module.createIf(
module.createUnary(UnaryOp.EqzI32, arg0),
module.createUnreachable() // TODO: report message to embedder
);
// imported
case "parseInt": // takes a pointer to the string
compiler.currentType = Type.f64;
@ -601,6 +589,110 @@ export function compileCall(compiler: Compiler, prototype: FunctionPrototype, ty
}
arg0 = compiler.compileExpression(operands[0], usizeType); // reports
return module.createCallImport("parseFloat", [ arg0 ], NativeType.F64);
// other
case "changetype": // changetype<T1,T2>(value: T1) -> T2
if (!validateCall(compiler, typeArguments, 2, operands, 1, reportNode))
return module.createUnreachable();
if ((typeArguments[0] == usizeType && typeArguments[1].classType) || (typeArguments[0].classType && typeArguments[1] == usizeType)) {
arg0 = compiler.compileExpression(operands[0], typeArguments[0]);
compiler.currentType = typeArguments[1];
return arg0;
}
compiler.error(DiagnosticCode.Type_0_cannot_be_changed_to_type_1, reportNode.range, typeArguments[0].toString(), typeArguments[1].toString());
return module.createUnreachable();
case "assert": // assert(isTrue: bool) -> void
compiler.currentType = Type.void;
if (typeArguments.length != 0) {
compiler.error(DiagnosticCode.Expected_0_type_arguments_but_got_1, reportNode.range, "0", typeArguments.length.toString(10));
return module.createUnreachable();
}
if (operands.length < 1) {
compiler.error(DiagnosticCode.Expected_at_least_0_arguments_but_got_1, reportNode.range, "1", operands.length.toString(10));
return module.createUnreachable();
}
if (operands.length > 2) {
compiler.error(DiagnosticCode.Expected_0_arguments_but_got_1, reportNode.range, "2", operands.length.toString(10));
return module.createUnreachable();
}
arg0 = compiler.compileExpression(operands[0], Type.i32); // reports
arg1 = operands.length > 1 ? compiler.compileExpression(operands[1], usizeType) : typeToNativeZero(module, usizeType); // TODO: string type
compiler.currentType = Type.void;
return compiler.options.noAssert
? module.createNop()
: module.createIf(
module.createUnary(UnaryOp.EqzI32, arg0),
module.createUnreachable() // TODO: report message to embedder
);
// conversions
case "i8":
if (!validateCall(compiler, typeArguments, 0, operands, 1, reportNode))
return module.createUnreachable();
return compiler.compileExpression(operands[0], Type.i8, ConversionKind.EXPLICIT);
case "i16":
if (!validateCall(compiler, typeArguments, 0, operands, 1, reportNode))
return module.createUnreachable();
return compiler.compileExpression(operands[0], Type.i16, ConversionKind.EXPLICIT);
case "i32":
if (!validateCall(compiler, typeArguments, 0, operands, 1, reportNode))
return module.createUnreachable();
return compiler.compileExpression(operands[0], Type.i32, ConversionKind.EXPLICIT);
case "i64":
if (!validateCall(compiler, typeArguments, 0, operands, 1, reportNode))
return module.createUnreachable();
return compiler.compileExpression(operands[0], Type.i64, ConversionKind.EXPLICIT);
case "isize":
if (!validateCall(compiler, typeArguments, 0, operands, 1, reportNode))
return module.createUnreachable();
return compiler.compileExpression(operands[0], select<Type>(Type.isize64, Type.isize32, compiler.options.target == Target.WASM64), ConversionKind.EXPLICIT);
case "u8":
if (!validateCall(compiler, typeArguments, 0, operands, 1, reportNode))
return module.createUnreachable();
return compiler.compileExpression(operands[0], Type.u8, ConversionKind.EXPLICIT);
case "u16":
if (!validateCall(compiler, typeArguments, 0, operands, 1, reportNode))
return module.createUnreachable();
return compiler.compileExpression(operands[0], Type.u16, ConversionKind.EXPLICIT);
case "u32":
if (!validateCall(compiler, typeArguments, 0, operands, 1, reportNode))
return module.createUnreachable();
return compiler.compileExpression(operands[0], Type.u32, ConversionKind.EXPLICIT);
case "u64":
if (!validateCall(compiler, typeArguments, 0, operands, 1, reportNode))
return module.createUnreachable();
return compiler.compileExpression(operands[0], Type.u64, ConversionKind.EXPLICIT);
case "usize":
if (!validateCall(compiler, typeArguments, 0, operands, 1, reportNode))
return module.createUnreachable();
return compiler.compileExpression(operands[0], usizeType, ConversionKind.EXPLICIT);
case "bool":
if (!validateCall(compiler, typeArguments, 0, operands, 1, reportNode))
return module.createUnreachable();
return compiler.compileExpression(operands[0], Type.bool, ConversionKind.EXPLICIT);
case "f32":
if (!validateCall(compiler, typeArguments, 0, operands, 1, reportNode))
return module.createUnreachable();
return compiler.compileExpression(operands[0], Type.f32, ConversionKind.EXPLICIT);
case "f64":
if (!validateCall(compiler, typeArguments, 0, operands, 1, reportNode))
return module.createUnreachable();
return compiler.compileExpression(operands[0], Type.f64, ConversionKind.EXPLICIT);
}
return 0;
}

View File

@ -127,7 +127,7 @@ export class Options {
}
/** Indicates the desired kind of a conversion. */
const enum ConversionKind {
export const enum ConversionKind {
/** No conversion. */
NONE,
/** Implicit conversion. */

View File

@ -1028,12 +1028,14 @@ export class Global extends Element {
withConstantIntegerValue(lo: i32, hi: i32): this {
this.constantIntegerValue = new I64(lo, hi);
this.hasConstantValue = true;
this.isMutable = false;
return this;
}
withConstantFloatValue(value: f64): this {
this.constantFloatValue = value;
this.hasConstantValue = true;
this.isMutable = false;
return this;
}
}