assemblyscript/src/types.ts

640 lines
17 KiB
TypeScript
Raw Normal View History

2017-12-24 03:19:47 +01:00
import {
Class,
Function
} from "./program";
import {
NativeType,
ExpressionRef,
Module
2017-12-24 03:19:47 +01:00
} from "./module";
2017-10-07 14:29:43 +02:00
2017-12-16 17:54:53 +01:00
/** Indicates the kind of a type. */
2017-10-02 12:52:15 +02:00
export const enum TypeKind {
// signed integers
I8,
I16,
I32,
I64,
ISIZE,
// unsigned integers
U8,
U16,
U32,
U64,
USIZE,
BOOL, // sic
// floats
F32,
F64,
2017-12-16 17:54:53 +01:00
// other
2017-10-02 12:52:15 +02:00
VOID
}
/** Indicates capabilities of a type. */
export const enum TypeFlags {
NONE = 0,
/** Is a signed type that can represent negative values. */
SIGNED = 1 << 0,
/** Is an unsigned type that cannot represent negative values. */
UNSIGNED = 1 << 1,
/** Is an integer type. */
INTEGER = 1 << 2,
/** Is a floating point type. */
FLOAT = 1 << 3,
/** Is a sized integer type with a target specific bit size. */
SIZE = 1 << 4,
/** Is a small type that is emulated in a larger type. */
SMALL = 1 << 5,
/** Is a long type larger than 32-bits. */
LONG = 1 << 6,
/** Is a value type. */
VALUE = 1 << 7,
/** Is a reference type. */
REFERENCE = 1 << 8,
/** Is a nullable type. */
NULLABLE = 1 << 9
}
2017-12-16 17:54:53 +01:00
/** Represents a resolved type. */
2017-10-02 12:52:15 +02:00
export class Type {
2017-12-16 17:54:53 +01:00
/** Type kind. */
2017-10-02 12:52:15 +02:00
kind: TypeKind;
/** Type flags. */
flags: TypeFlags;
2017-12-16 17:54:53 +01:00
/** Size in bits. */
size: u32;
/** Size in bytes. Ceiled to 8-bits. */
2017-12-04 02:00:48 +01:00
byteSize: i32;
2017-12-16 17:54:53 +01:00
/** Underlying class type, if a class type. */
2017-11-17 14:33:51 +01:00
classType: Class | null;
2017-12-16 17:54:53 +01:00
/** Underlying function type, if a function type. */
functionType: Function | null;
/** Respective nullable type, if non-nullable. */
2017-12-16 17:54:53 +01:00
nullableType: Type | null = null;
/** Respective non-nullable type, if nullable. */
nonNullableType: Type;
2017-10-02 12:52:15 +02:00
2017-12-16 17:54:53 +01:00
/** Constructs a new resolved type. */
constructor(kind: TypeKind, flags: TypeFlags, size: i32) {
2017-10-02 12:52:15 +02:00
this.kind = kind;
this.flags = flags;
2017-10-02 12:52:15 +02:00
this.size = size;
this.byteSize = <i32>ceil<f64>(<f64>size / 8);
2017-10-02 12:52:15 +02:00
this.classType = null;
this.nonNullableType = this;
2017-10-02 12:52:15 +02:00
}
/** Computes the sign-extending shift in the target type. */
computeSmallIntegerShift(targetType: Type): u32 {
return targetType.size - this.size;
}
/** Computes the truncating mask in the target type. */
computeSmallIntegerMask(targetType: Type): u32 {
return -1 >>> (targetType.size - this.size);
}
/** Tests if this type has the specified capabilities. */
is(flags: TypeFlags): bool {
return (this.flags & flags) == flags;
}
/** Tests if this type is a class type. */
get isClass(): bool { return this.classType != null; }
/** Tests if this type is a function type. */
get isFunction(): bool { return this.functionType != null; }
/** Tests if this type is a reference type. */
get isReference(): bool { return this.classType != null || this.functionType != null; }
2017-10-02 12:52:15 +02:00
2017-12-16 17:54:53 +01:00
/** Composes a class type from this type and a class. */
2017-11-17 14:33:51 +01:00
asClass(classType: Class): Type {
2017-12-16 17:54:53 +01:00
assert(this.kind == TypeKind.USIZE);
var ret = new Type(this.kind, this.flags & ~TypeFlags.VALUE | TypeFlags.REFERENCE, this.size);
2017-10-02 12:52:15 +02:00
ret.classType = classType;
return ret;
}
2017-12-16 17:54:53 +01:00
/** Composes a function type from this type and a function. */
asFunction(functionType: Function): Type {
assert(this.kind == TypeKind.U32 && !this.isReference);
var ret = new Type(this.kind, this.flags & ~TypeFlags.VALUE | TypeFlags.REFERENCE, this.size);
2017-12-16 17:54:53 +01:00
ret.functionType = functionType;
return ret;
}
/** Composes the respective nullable type of this type. */
2017-12-18 03:46:36 +01:00
asNullable(): Type | null {
assert(this.kind == TypeKind.USIZE);
if (!this.nullableType) {
assert(!this.is(TypeFlags.NULLABLE) && this.isReference);
this.nullableType = new Type(this.kind, this.flags | TypeFlags.NULLABLE, this.size);
this.nullableType.classType = this.classType;
this.nullableType.functionType = this.functionType;
}
2017-12-18 03:46:36 +01:00
return this.nullableType;
2017-10-02 12:52:15 +02:00
}
/** Tests if a value of this type is assignable to a target of the specified type. */
isAssignableTo(target: Type): bool {
var currentClass: Class | null;
var targetClass: Class | null;
var currentFunction: Function | null;
var targetFunction: Function | null;
if (this.isReference) {
if (target.isReference) {
if (currentClass = this.classType) {
if (targetClass = target.classType) {
return currentClass.isAssignableTo(targetClass);
}
} else if (currentFunction = this.functionType) {
if (targetFunction = target.functionType) {
return currentFunction.isAssignableTo(targetFunction);
}
}
}
} else if (!target.isReference) {
switch (this.kind) {
case TypeKind.I8:
switch (target.kind) {
case TypeKind.I8: // same
case TypeKind.I16: // larger
case TypeKind.I32: // larger
case TypeKind.I64: // larger
case TypeKind.ISIZE: // larger
case TypeKind.U8: // signed to unsigned
case TypeKind.U16: // larger
case TypeKind.U32: // larger
case TypeKind.U64: // larger
case TypeKind.USIZE: // larger
case TypeKind.F32: // safe
case TypeKind.F64: // safe
return true;
}
break;
case TypeKind.I16:
switch (target.kind) {
case TypeKind.I16: // same
case TypeKind.I32: // larger
case TypeKind.I64: // larger
case TypeKind.ISIZE: // larger
case TypeKind.U16: // signed to unsigned
case TypeKind.U32: // larger
case TypeKind.U64: // larger
case TypeKind.USIZE: // larger
case TypeKind.F32: // safe
case TypeKind.F64: // safe
return true;
}
break;
case TypeKind.I32:
switch (target.kind) {
case TypeKind.I32: // same
case TypeKind.I64: // larger
case TypeKind.ISIZE: // same or larger
case TypeKind.U32: // signed to unsigned
case TypeKind.U64: // larger
case TypeKind.USIZE: // signed to unsigned or larger
case TypeKind.F64: // safe
return true;
}
break;
case TypeKind.I64:
switch (target.kind) {
case TypeKind.I64: // same
case TypeKind.U64: // signed to unsigned
return true;
case TypeKind.ISIZE: // possibly same
case TypeKind.USIZE: // possibly signed to unsigned
return target.size == 64;
}
break;
case TypeKind.ISIZE:
switch (target.kind) {
case TypeKind.I32: // possibly same
case TypeKind.U32: // possibly signed to unsigned
return this.size == 32;
case TypeKind.I64: // same or larger
case TypeKind.ISIZE: // same
case TypeKind.U64: // signed to unsigned or larger
case TypeKind.USIZE: // signed to unsigned
return true;
case TypeKind.F64: // possibly safe
return target.size == 32;
}
break;
case TypeKind.U8:
switch (target.kind) {
case TypeKind.I16: // larger
case TypeKind.I32: // larger
case TypeKind.I64: // larger
case TypeKind.ISIZE: // larger
case TypeKind.U8: // same
case TypeKind.U16: // larger
case TypeKind.U32: // larger
case TypeKind.U64: // larger
case TypeKind.USIZE: // larger
case TypeKind.F32: // safe
case TypeKind.F64: // safe
return true;
}
break;
case TypeKind.U16:
switch (target.kind) {
case TypeKind.I32: // larger
case TypeKind.I64: // larger
case TypeKind.ISIZE: // larger
case TypeKind.U16: // same
case TypeKind.U32: // larger
case TypeKind.U64: // larger
case TypeKind.USIZE: // larger
case TypeKind.F32: // safe
case TypeKind.F64: // safe
return true;
}
break;
case TypeKind.U32:
switch (target.kind) {
case TypeKind.I64: // larger
case TypeKind.U32: // same
case TypeKind.U64: // larger
case TypeKind.USIZE: // same or larger
case TypeKind.F64: // safe
return true;
}
break;
case TypeKind.U64:
switch (target.kind) {
case TypeKind.U64: // same
return true;
case TypeKind.USIZE: // possibly same
return target.size == 64;
}
break;
case TypeKind.USIZE:
switch (target.kind) {
case TypeKind.U32: // possibly same
return this.size == 32;
case TypeKind.U64: // same or larger
case TypeKind.USIZE: // same
return true;
case TypeKind.F64: // possibly safe
return target.size == 32;
}
break;
case TypeKind.BOOL:
switch (target.kind) {
case TypeKind.I8: // larger
case TypeKind.I16: // larger
case TypeKind.I32: // larger
case TypeKind.I64: // larger
case TypeKind.ISIZE: // larger
case TypeKind.U8: // larger
case TypeKind.U16: // larger
case TypeKind.U32: // larger
case TypeKind.U64: // larger
case TypeKind.USIZE: // larger
case TypeKind.BOOL: // same
return true;
}
break;
case TypeKind.F32:
switch (target.kind) {
case TypeKind.F32: // same
case TypeKind.F64: // larger
return true;
}
break;
case TypeKind.F64:
return target.kind == TypeKind.F64;
}
}
return false;
}
2017-12-16 17:54:53 +01:00
/** Converts this type to its TypeScript representation. */
2017-10-07 14:29:43 +02:00
toString(kindOnly: bool = false): string {
2017-10-02 12:52:15 +02:00
switch (this.kind) {
case TypeKind.I8: return "i8";
case TypeKind.I16: return "i16";
case TypeKind.I32: return "i32";
2017-12-01 02:08:03 +01:00
case TypeKind.I64: return "i64";
2017-10-02 12:52:15 +02:00
case TypeKind.ISIZE: return "isize";
case TypeKind.U8: return "u8";
case TypeKind.U16: return "u16";
case TypeKind.U32: return "u32";
case TypeKind.U64: return "u64";
2017-12-16 17:54:53 +01:00
case TypeKind.USIZE:
if (kindOnly) return "usize";
2018-02-25 00:13:39 +01:00
return this.classType
? this.classType.toString()
: this.functionType
? this.functionType.toTypeString()
: "usize";
2017-10-02 12:52:15 +02:00
case TypeKind.BOOL: return "bool";
case TypeKind.F32: return "f32";
case TypeKind.F64: return "f64";
case TypeKind.VOID: return "void";
2017-12-18 03:46:36 +01:00
default: assert(false); return "";
2017-10-02 12:52:15 +02:00
}
}
2017-12-24 03:19:47 +01:00
// Binaryen specific
/** Converts this type to its respective native type. */
toNativeType(): NativeType {
2018-01-05 01:55:59 +01:00
switch (this.kind) {
default:
return NativeType.I32;
case TypeKind.I64:
case TypeKind.U64:
return NativeType.I64;
case TypeKind.ISIZE:
case TypeKind.USIZE:
return this.size == 64 ? NativeType.I64 : NativeType.I32;
2018-01-05 01:55:59 +01:00
case TypeKind.F32:
return NativeType.F32;
case TypeKind.F64:
return NativeType.F64;
case TypeKind.VOID:
return NativeType.None;
}
2017-12-24 03:19:47 +01:00
}
/** Converts this type to its native `0` value. */
toNativeZero(module: Module): ExpressionRef {
2018-01-05 01:55:59 +01:00
switch (this.kind) {
case TypeKind.VOID:
assert(false);
default:
return module.createI32(0);
case TypeKind.ISIZE:
case TypeKind.USIZE:
2018-02-25 00:13:39 +01:00
if (this.size != 64) return module.createI32(0);
2018-01-05 01:55:59 +01:00
// fall-through
case TypeKind.I64:
case TypeKind.U64:
return module.createI64(0);
2018-01-05 01:55:59 +01:00
case TypeKind.F32:
return module.createF32(0);
case TypeKind.F64:
return module.createF64(0);
}
2017-12-24 03:19:47 +01:00
}
/** Converts this type to its native `1` value. */
toNativeOne(module: Module): ExpressionRef {
2018-01-05 01:55:59 +01:00
switch (this.kind) {
case TypeKind.VOID:
assert(false);
default:
return module.createI32(1);
case TypeKind.ISIZE:
case TypeKind.USIZE:
2018-02-25 00:13:39 +01:00
if (this.size != 64) return module.createI32(1);
2018-01-05 01:55:59 +01:00
// fall-through
case TypeKind.I64:
case TypeKind.U64:
return module.createI64(1);
2018-01-05 01:55:59 +01:00
case TypeKind.F32:
return module.createF32(1);
case TypeKind.F64:
return module.createF64(1);
}
2017-12-24 03:19:47 +01:00
}
/** Converts this type to its native `-1` value. */
toNativeNegOne(module: Module): ExpressionRef {
switch (this.kind) {
case TypeKind.VOID:
assert(false);
default:
return module.createI32(-1);
case TypeKind.ISIZE:
case TypeKind.USIZE:
2018-02-25 00:13:39 +01:00
if (this.size != 64) return module.createI32(-1);
// fall-through
case TypeKind.I64:
case TypeKind.U64:
return module.createI64(-1, -1);
case TypeKind.F32:
return module.createF32(-1);
case TypeKind.F64:
return module.createF64(-1);
}
}
/** Converts this type to its signature string. */
toSignatureString(): string {
switch (this.kind) {
default:
return "i";
case TypeKind.I64:
case TypeKind.U64:
return "I";
case TypeKind.ISIZE:
case TypeKind.USIZE:
return this.size == 64 ? "I" : "i";
case TypeKind.F32:
return "f";
case TypeKind.F64:
return "F";
case TypeKind.VOID:
return "v";
}
2017-12-24 03:19:47 +01:00
}
// Types
2017-12-16 17:54:53 +01:00
/** An 8-bit signed integer. */
static readonly i8: Type = new Type(TypeKind.I8,
TypeFlags.SIGNED |
TypeFlags.SMALL |
TypeFlags.INTEGER |
TypeFlags.VALUE, 8
);
2017-12-16 17:54:53 +01:00
/** A 16-bit signed integer. */
static readonly i16: Type = new Type(TypeKind.I16,
TypeFlags.SIGNED |
TypeFlags.SMALL |
TypeFlags.INTEGER |
TypeFlags.VALUE, 16
);
2017-12-16 17:54:53 +01:00
/** A 32-bit signed integer. */
static readonly i32: Type = new Type(TypeKind.I32,
TypeFlags.SIGNED |
TypeFlags.INTEGER |
TypeFlags.VALUE, 32
);
2017-12-16 17:54:53 +01:00
/** A 64-bit signed integer. */
static readonly i64: Type = new Type(TypeKind.I64,
TypeFlags.SIGNED |
TypeFlags.LONG |
TypeFlags.INTEGER |
TypeFlags.VALUE, 64
);
2017-12-16 17:54:53 +01:00
/** A 32-bit signed size. WASM32 only. */
static readonly isize32: Type = new Type(TypeKind.ISIZE,
TypeFlags.SIGNED |
TypeFlags.SIZE |
TypeFlags.INTEGER |
TypeFlags.VALUE, 32
);
2017-12-16 17:54:53 +01:00
/** A 64-bit signed size. WASM64 only. */
static readonly isize64: Type = new Type(TypeKind.ISIZE,
TypeFlags.SIGNED |
TypeFlags.LONG |
TypeFlags.SIZE |
TypeFlags.INTEGER |
TypeFlags.VALUE, 64
);
2017-12-16 17:54:53 +01:00
/** An 8-bit unsigned integer. */
static readonly u8: Type = new Type(TypeKind.U8,
TypeFlags.UNSIGNED |
TypeFlags.SMALL |
TypeFlags.INTEGER |
TypeFlags.VALUE, 8
);
2017-12-16 17:54:53 +01:00
/** A 16-bit unsigned integer. */
static readonly u16: Type = new Type(TypeKind.U16,
TypeFlags.UNSIGNED |
TypeFlags.SMALL |
TypeFlags.INTEGER |
TypeFlags.VALUE, 16
);
2017-12-16 17:54:53 +01:00
/** A 32-bit unsigned integer. */
static readonly u32: Type = new Type(TypeKind.U32,
TypeFlags.UNSIGNED |
TypeFlags.INTEGER |
TypeFlags.VALUE, 32
);
2017-12-16 17:54:53 +01:00
/** A 64-bit unsigned integer. */
static readonly u64: Type = new Type(TypeKind.U64,
TypeFlags.UNSIGNED |
TypeFlags.LONG |
TypeFlags.INTEGER |
TypeFlags.VALUE, 64
);
2017-12-16 17:54:53 +01:00
/** A 32-bit unsigned size. WASM32 only. */
static readonly usize32: Type = new Type(TypeKind.USIZE,
TypeFlags.UNSIGNED |
TypeFlags.SIZE |
TypeFlags.INTEGER |
TypeFlags.VALUE, 32
);
2017-12-16 17:54:53 +01:00
/** A 64-bit unsigned size. WASM64 only. */
static readonly usize64: Type = new Type(TypeKind.USIZE,
TypeFlags.UNSIGNED |
TypeFlags.LONG |
TypeFlags.SIZE |
TypeFlags.INTEGER |
TypeFlags.VALUE, 64
);
2017-12-16 17:54:53 +01:00
/** A 1-bit unsigned integer. */
static readonly bool: Type = new Type(TypeKind.BOOL,
TypeFlags.UNSIGNED |
TypeFlags.SMALL |
TypeFlags.INTEGER |
TypeFlags.VALUE, 1
);
2017-12-16 17:54:53 +01:00
/** A 32-bit float. */
static readonly f32: Type = new Type(TypeKind.F32,
TypeFlags.SIGNED |
TypeFlags.FLOAT |
TypeFlags.VALUE, 32
);
2017-12-16 17:54:53 +01:00
/** A 64-bit float. */
static readonly f64: Type = new Type(TypeKind.F64,
TypeFlags.SIGNED |
TypeFlags.LONG |
TypeFlags.FLOAT |
TypeFlags.VALUE, 64
);
2017-12-16 17:54:53 +01:00
/** No return type. */
static readonly void: Type = new Type(TypeKind.VOID, TypeFlags.NONE, 0);
2017-10-02 12:52:15 +02:00
}
2017-12-24 03:19:47 +01:00
/** Converts an array of types to an array of native types. */
export function typesToNativeTypes(types: Type[]): NativeType[] {
var k = types.length;
var ret = new Array<NativeType>(k);
2018-02-25 00:13:39 +01:00
for (var i = 0; i < k; ++i) {
2017-12-24 03:19:47 +01:00
ret[i] = types[i].toNativeType();
2018-02-25 00:13:39 +01:00
}
2017-12-24 03:19:47 +01:00
return ret;
}
/** Converts an array of types to its combined string representation. */
export function typesToString(types: Type[]): string {
var k = types.length;
2018-02-25 00:13:39 +01:00
if (!k) return "";
var sb = new Array<string>(k);
2018-02-25 00:13:39 +01:00
for (var i = 0; i < k; ++i) {
sb[i] = types[i].toString();
2018-02-25 00:13:39 +01:00
}
return sb.join(", ");
2017-10-07 14:29:43 +02:00
}