Implement explicit this type (#373)

* Add backing classes for basic types (I32...)
* Move standard numeric constants to backing classes
This commit is contained in:
Daniel Wirtz 2019-01-09 12:45:29 +01:00 committed by GitHub
parent d5f72e32d7
commit f714afab3c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 8028 additions and 1596 deletions

View File

@ -153,7 +153,12 @@ export function compileCall(
);
return module.createUnreachable();
}
let element = compiler.resolver.resolveExpression(operands[0], compiler.currentFunction, ReportMode.SWALLOW);
let element = compiler.resolver.resolveExpression(
operands[0],
compiler.currentFunction,
Type.void,
ReportMode.SWALLOW
);
return module.createI32(element ? 1 : 0);
}
case "isConstant": { // isConstant(expression) -> bool

View File

@ -5421,33 +5421,22 @@ export class Compiler extends DiagnosticEmitter {
if (thisArg) {
let parent = assert(instance.parent);
assert(parent.kind == ElementKind.CLASS);
if (getExpressionId(thisArg) == ExpressionId.GetLocal) {
flow.addScopedLocalAlias(
getGetLocalIndex(thisArg),
(<Class>parent).type,
"this"
);
let parentBase = (<Class>parent).base;
if (parentBase) {
flow.addScopedLocalAlias(
getGetLocalIndex(thisArg),
parentBase.type,
"super"
);
}
} else {
let thisLocal = flow.addScopedLocal((<Class>parent).type, "this", false);
let thisType = assert(instance.signature.thisType);
let classType = thisType.classReference;
let superType = classType
? classType.base
? classType.base.type
: null
: null;
if (getExpressionId(thisArg) == ExpressionId.GetLocal) { // reuse this var
flow.addScopedLocalAlias(getGetLocalIndex(thisArg), thisType, "this");
if (superType) flow.addScopedLocalAlias(getGetLocalIndex(thisArg), superType, "super");
} else { // use a temp var
let thisLocal = flow.addScopedLocal(thisType, "this", false);
body.push(
module.createSetLocal(thisLocal.index, thisArg)
);
let parentBase = (<Class>parent).base;
if (parentBase) {
flow.addScopedLocalAlias(
thisLocal.index,
parentBase.type,
"super"
);
}
if (superType) flow.addScopedLocalAlias(thisLocal.index, superType, "super");
}
}
var parameterTypes = signature.parameterTypes;
@ -5895,7 +5884,7 @@ export class Compiler extends DiagnosticEmitter {
}
compileElementAccessExpression(expression: ElementAccessExpression, contextualType: Type): ExpressionRef {
var target = this.resolver.resolveElementAccess(expression, this.currentFunction); // reports
var target = this.resolver.resolveElementAccess(expression, this.currentFunction, contextualType); // reports
if (!target) return this.module.createUnreachable();
switch (target.kind) {
case ElementKind.CLASS: {
@ -6003,7 +5992,7 @@ export class Compiler extends DiagnosticEmitter {
if (currentFunction.is(CommonFlags.INSTANCE)) {
let parent = assert(currentFunction.parent);
assert(parent.kind == ElementKind.CLASS);
let thisType = (<Class>parent).type;
let thisType = assert(currentFunction.signature.thisType);
if (currentFunction.is(CommonFlags.CONSTRUCTOR)) {
if (!flow.is(FlowFlags.ALLOCATES)) {
flow.set(FlowFlags.ALLOCATES);
@ -6194,84 +6183,16 @@ export class Compiler extends DiagnosticEmitter {
intValue
);
}
switch (contextualType.kind) {
// compile to contextualType if matching
case TypeKind.I8: {
if (i64_is_i8(intValue)) return module.createI32(i64_low(intValue));
break;
}
case TypeKind.U8: {
if (i64_is_u8(intValue)) return module.createI32(i64_low(intValue));
break;
}
case TypeKind.I16: {
if (i64_is_i16(intValue)) return module.createI32(i64_low(intValue));
break;
}
case TypeKind.U16: {
if (i64_is_u16(intValue)) return module.createI32(i64_low(intValue));
break;
}
case TypeKind.I32: {
if (i64_is_i32(intValue)) return module.createI32(i64_low(intValue));
break;
}
case TypeKind.U32: {
if (i64_is_u32(intValue)) return module.createI32(i64_low(intValue));
break;
}
case TypeKind.BOOL: {
if (i64_is_bool(intValue)) return module.createI32(i64_low(intValue));
break;
}
case TypeKind.ISIZE: {
if (!this.options.isWasm64) {
if (i64_is_i32(intValue)) return module.createI32(i64_low(intValue));
break;
}
return module.createI64(i64_low(intValue), i64_high(intValue));
}
case TypeKind.USIZE: {
if (!this.options.isWasm64) {
if (i64_is_u32(intValue)) return module.createI32(i64_low(intValue));
break;
}
return module.createI64(i64_low(intValue), i64_high(intValue));
}
case TypeKind.I64:
case TypeKind.U64: {
return module.createI64(i64_low(intValue), i64_high(intValue));
}
case TypeKind.F32: {
if (i64_is_f32(intValue)) return module.createF32(i64_to_f32(intValue));
break;
}
case TypeKind.F64: {
if (i64_is_f64(intValue)) return module.createF64(i64_to_f64(intValue));
break;
}
case TypeKind.VOID: {
break; // compiles to best fitting type below, being dropped
}
default: {
assert(false);
return module.createUnreachable();
}
}
// otherwise compile to best fitting native type
if (i64_is_i32(intValue)) {
this.currentType = Type.i32;
return module.createI32(i64_low(intValue));
} else if (i64_is_u32(intValue)) {
this.currentType = Type.u32;
return module.createI32(i64_low(intValue));
} else {
this.currentType = Type.i64;
return module.createI64(i64_low(intValue), i64_high(intValue));
let type = this.resolver.determineIntegerLiteralType(intValue, contextualType);
this.currentType = type;
switch (type.kind) {
case TypeKind.ISIZE: if (!this.options.isWasm64) return module.createI32(i64_low(intValue));
case TypeKind.I64: return module.createI64(i64_low(intValue), i64_high(intValue));
case TypeKind.USIZE: if (!this.options.isWasm64) return module.createI32(i64_low(intValue));
case TypeKind.U64: return module.createI64(i64_low(intValue), i64_high(intValue));
case TypeKind.F32: return module.createF32(i64_to_f32(intValue));
case TypeKind.F64: return module.createF64(i64_to_f64(intValue));
default: return module.createI32(i64_low(intValue));
}
}
case LiteralKind.STRING: {
@ -6749,7 +6670,7 @@ export class Compiler extends DiagnosticEmitter {
): ExpressionRef {
var module = this.module;
var target = this.resolver.resolvePropertyAccess(propertyAccess, this.currentFunction); // reports
var target = this.resolver.resolvePropertyAccess(propertyAccess, this.currentFunction, contextualType); // reports
if (!target) return module.createUnreachable();
switch (target.kind) {

View File

@ -328,6 +328,8 @@ export class Program extends DiagnosticEmitter {
fileLevelExports: Map<string,Element> = new Map();
/** Module-level exports by exported name. */
moduleLevelExports: Map<string,ModuleExport> = new Map();
/** Classes backing basic types like `i32`. */
basicClasses: Map<TypeKind,Class> = new Map();
/** ArrayBuffer instance reference. */
arrayBufferInstance: Class | null = null;
@ -641,6 +643,21 @@ export class Program extends DiagnosticEmitter {
}
}
// register classes backing basic types
this.registerBasicClass(TypeKind.I8, "I8");
this.registerBasicClass(TypeKind.I16, "I16");
this.registerBasicClass(TypeKind.I32, "I32");
this.registerBasicClass(TypeKind.I64, "I64");
this.registerBasicClass(TypeKind.ISIZE, "Isize");
this.registerBasicClass(TypeKind.U8, "U8");
this.registerBasicClass(TypeKind.U16, "U16");
this.registerBasicClass(TypeKind.U32, "U32");
this.registerBasicClass(TypeKind.U64, "U64");
this.registerBasicClass(TypeKind.USIZE, "Usize");
this.registerBasicClass(TypeKind.BOOL, "Bool");
this.registerBasicClass(TypeKind.F32, "F32");
this.registerBasicClass(TypeKind.F64, "F64");
// register 'start'
{
let element = assert(this.elementsLookup.get("start"));
@ -727,6 +744,15 @@ export class Program extends DiagnosticEmitter {
}
}
private registerBasicClass(typeKind: TypeKind, className: string): void {
if (this.elementsLookup.has(className)) {
let element = assert(this.elementsLookup.get(className));
assert(element.kind == ElementKind.CLASS_PROTOTYPE);
let classElement = this.resolver.resolveClass(<ClassPrototype>element, null);
if (classElement) this.basicClasses.set(typeKind, classElement);
}
}
/** Sets a constant integer value. */
setConstantInteger(globalName: string, type: Type, value: I64): void {
assert(type.is(TypeFlags.INTEGER));

View File

@ -45,14 +45,18 @@ import {
LiteralKind,
ParenthesizedExpression,
AssertionExpression,
Expression
Expression,
IntegerLiteralExpression,
UnaryPrefixExpression,
UnaryPostfixExpression
} from "./ast";
import {
Type,
Signature,
typesToString,
TypeKind
TypeKind,
TypeFlags
} from "./types";
import {
@ -65,6 +69,10 @@ import {
makeMap
} from "./util";
import {
Token
} from "./tokenizer";
/** Indicates whether errors are reported or not. */
export enum ReportMode {
/** Report errors. */
@ -83,8 +91,6 @@ export class Resolver extends DiagnosticEmitter {
currentThisExpression: Expression | null = null;
/** Element expression of the previously resolved element access. */
currentElementExpression : Expression | null = null;
/** Whether the last resolved type has been resolved from a placeholder, i.e. `T`. */
currentTypeIsPlaceholder: bool = false;
/** Constructs the resolver for the specified program. */
constructor(program: Program) {
@ -419,11 +425,12 @@ export class Resolver extends DiagnosticEmitter {
resolvePropertyAccess(
propertyAccess: PropertyAccessExpression,
contextualFunction: Function,
contextualType: Type,
reportMode: ReportMode = ReportMode.REPORT
): Element | null {
// start by resolving the lhs target (expression before the last dot)
var targetExpression = propertyAccess.expression;
var target = this.resolveExpression(targetExpression, contextualFunction, reportMode); // reports
var target = this.resolveExpression(targetExpression, contextualFunction, contextualType, reportMode); // reports
if (!target) return null;
// at this point we know exactly what the target is, so look up the element within
@ -438,11 +445,16 @@ export class Resolver extends DiagnosticEmitter {
assert(type != Type.void);
let classReference = type.classReference;
if (!classReference) {
this.error(
DiagnosticCode.Property_0_does_not_exist_on_type_1,
propertyAccess.property.range, propertyName, (<VariableLikeElement>target).type.toString()
);
return null;
let basicClasses = this.program.basicClasses;
if (!type.is(TypeFlags.REFERENCE) && basicClasses.has(type.kind)) {
classReference = assert(basicClasses.get(type.kind));
} else {
this.error(
DiagnosticCode.Property_0_does_not_exist_on_type_1,
propertyAccess.property.range, propertyName, (<VariableLikeElement>target).type.toString()
);
return null;
}
}
target = classReference;
break;
@ -545,10 +557,11 @@ export class Resolver extends DiagnosticEmitter {
resolveElementAccess(
elementAccess: ElementAccessExpression,
contextualFunction: Function,
contextualType: Type,
reportMode: ReportMode = ReportMode.REPORT
): Element | null {
var targetExpression = elementAccess.expression;
var target = this.resolveExpression(targetExpression, contextualFunction, reportMode);
var target = this.resolveExpression(targetExpression, contextualFunction, contextualType, reportMode);
if (!target) return null;
switch (target.kind) {
case ElementKind.GLOBAL: if (!this.ensureResolvedLazyGlobal(<Global>target, reportMode)) return null;
@ -596,9 +609,75 @@ export class Resolver extends DiagnosticEmitter {
return null;
}
determineIntegerLiteralType(
intValue: I64,
contextualType: Type
): Type {
if (!contextualType.is(TypeFlags.REFERENCE)) {
// compile to contextualType if matching
switch (contextualType.kind) {
case TypeKind.I8: {
if (i64_is_i8(intValue)) return Type.i8;
break;
}
case TypeKind.U8: {
if (i64_is_u8(intValue)) return Type.u8;
break;
}
case TypeKind.I16: {
if (i64_is_i16(intValue)) return Type.i16;
break;
}
case TypeKind.U16: {
if (i64_is_u16(intValue)) return Type.u16;
break;
}
case TypeKind.I32: {
if (i64_is_i32(intValue)) return Type.i32;
break;
}
case TypeKind.U32: {
if (i64_is_u32(intValue)) return Type.u32;
break;
}
case TypeKind.BOOL: {
if (i64_is_bool(intValue)) return Type.bool;
break;
}
case TypeKind.ISIZE: {
if (!this.program.options.isWasm64) {
if (i64_is_i32(intValue)) return Type.isize32;
break;
}
return Type.isize64;
}
case TypeKind.USIZE: {
if (!this.program.options.isWasm64) {
if (i64_is_u32(intValue)) return Type.usize32;
break;
}
return Type.usize64;
}
case TypeKind.I64: return Type.i64;
case TypeKind.U64: return Type.u64;
case TypeKind.F32: return Type.f32;
case TypeKind.F64: return Type.f64;
case TypeKind.VOID: break; // best fitting below
default: assert(false);
}
}
// otherwise compile to best fitting native type
if (i64_is_i32(intValue)) return Type.i32;
if (i64_is_u32(intValue)) return Type.u32;
return Type.i64;
}
resolveExpression(
expression: Expression,
contextualFunction: Function,
contextualType: Type = Type.void,
reportMode: ReportMode = ReportMode.REPORT
): Element | null {
while (expression.kind == NodeKind.PARENTHESIZED) {
@ -611,17 +690,80 @@ export class Resolver extends DiagnosticEmitter {
contextualFunction.flow.contextualTypeArguments,
reportMode
);
if (type) {
let classType = type.classReference;
if (classType) {
this.currentThisExpression = null;
this.currentElementExpression = null;
return classType;
if (!type) return null;
let classType = type.classReference;
if (!classType) return null;
this.currentThisExpression = null;
this.currentElementExpression = null;
return classType;
}
case NodeKind.UNARYPREFIX: {
// TODO: overloads
switch ((<UnaryPrefixExpression>expression).operator) {
case Token.MINUS: {
let operand = (<UnaryPrefixExpression>expression).operand;
// implicitly negate if an integer literal to distinguish between i32/u32/i64
if (operand.kind == NodeKind.LITERAL && (<LiteralExpression>operand).literalKind == LiteralKind.INTEGER) {
let type = this.determineIntegerLiteralType(
i64_sub(i64_zero, (<IntegerLiteralExpression>operand).value),
contextualType
);
return assert(this.program.basicClasses.get(type.kind));
}
return this.resolveExpression(
operand,
contextualFunction,
contextualType,
reportMode
);
}
case Token.PLUS:
case Token.PLUS_PLUS:
case Token.MINUS_MINUS: {
return this.resolveExpression(
(<UnaryPrefixExpression>expression).operand,
contextualFunction,
contextualType,
reportMode
);
}
case Token.EXCLAMATION: {
return assert(this.program.basicClasses.get(TypeKind.BOOL));
}
case Token.TILDE: {
let resolvedOperand = this.resolveExpression(
(<UnaryPrefixExpression>expression).operand,
contextualFunction,
contextualType,
reportMode
);
if (!resolvedOperand) return null;
throw new Error("not implemented"); // TODO: should all elements have a corresponding type right away?
}
default: assert(false);
}
return null;
}
case NodeKind.BINARY: { // TODO: string concatenation, mostly
case NodeKind.UNARYPOSTFIX: {
// TODO: overloads
switch ((<UnaryPostfixExpression>expression).operator) {
case Token.PLUS_PLUS:
case Token.MINUS_MINUS: {
return this.resolveExpression(
(<UnaryPostfixExpression>expression).operand,
contextualFunction,
contextualType,
reportMode
);
}
default: assert(false);
}
return null;
}
case NodeKind.BINARY: {
// TODO: all sorts of unary and binary expressions, which means looking up overloads and
// evaluating their return types, knowing the semantics of different operators etc.
// should probably share that code with the compiler somehow, as it also does exactly this.
throw new Error("not implemented");
}
case NodeKind.THIS: { // -> Class / ClassPrototype
@ -675,6 +817,27 @@ export class Resolver extends DiagnosticEmitter {
}
case NodeKind.LITERAL: {
switch ((<LiteralExpression>expression).literalKind) {
case LiteralKind.INTEGER: {
return assert(
this.program.basicClasses.get(
this.determineIntegerLiteralType(
(<IntegerLiteralExpression>expression).value,
contextualType
).kind
)
);
}
case LiteralKind.FLOAT: {
this.currentThisExpression = expression;
this.currentElementExpression = null;
return assert(
this.program.basicClasses.get(
contextualType == Type.f32
? TypeKind.F32
: TypeKind.F64
)
);
}
case LiteralKind.STRING: {
this.currentThisExpression = expression;
this.currentElementExpression = null;
@ -688,6 +851,7 @@ export class Resolver extends DiagnosticEmitter {
return this.resolvePropertyAccess(
<PropertyAccessExpression>expression,
contextualFunction,
contextualType,
reportMode
);
}
@ -695,12 +859,13 @@ export class Resolver extends DiagnosticEmitter {
return this.resolveElementAccess(
<ElementAccessExpression>expression,
contextualFunction,
contextualType,
reportMode
);
}
case NodeKind.CALL: {
let targetExpression = (<CallExpression>expression).expression;
let target = this.resolveExpression(targetExpression, contextualFunction, reportMode);
let target = this.resolveExpression(targetExpression, contextualFunction, contextualType, reportMode);
if (!target) return null;
if (target.kind == ElementKind.FUNCTION_PROTOTYPE) {
let instance = this.resolveFunctionInclTypeArguments(
@ -800,8 +965,21 @@ export class Resolver extends DiagnosticEmitter {
reportMode
);
if (!classInstance) return null;
thisType = classInstance.type;
let explicitThisType = signatureNode.explicitThisType;
if (explicitThisType) {
thisType = this.resolveType(explicitThisType, contextualTypeArguments, reportMode);
if (!thisType) return null;
} else {
thisType = classInstance.type;
}
contextualTypeArguments.set("this", thisType);
} else {
if (signatureNode.explicitThisType) {
this.error(
DiagnosticCode._this_cannot_be_referenced_in_current_location,
signatureNode.explicitThisType.range
); // recoverable
}
}
// resolve signature node

View File

@ -47,16 +47,12 @@
export namespace i8 {
export const MIN_VALUE: i8 = -128;
export const MAX_VALUE: i8 = 127;
@inline export function parseInt(value: string, radix: i32 = 0): i8 { return <i8>parseI32(value, radix) }
@inline export function parseFloat(value: string): i8 { return <i8>parseFloat(value) }
}
@builtin export declare function i16(value: void): i16;
export namespace i16 {
export const MIN_VALUE: i16 = -32768;
export const MAX_VALUE: i16 = 32767;
@inline export function parseInt(value: string, radix: i32 = 0): i16 { return <i16>parseI32(value, radix) }
@inline export function parseFloat(value: string): i16 { return <i16>parseFloat(value) }
}
@builtin export declare function i32(value: void): i32;
@ -77,8 +73,6 @@ export namespace i32 {
@builtin export declare function store8(offset: usize, value: i32, constantOffset?: usize): void;
@builtin export declare function store16(offset: usize, value: i32, constantOffset?: usize): void;
@builtin export declare function store(offset: usize, value: i32, constantOffset?: usize): void;
@inline export function parseInt(value: string, radix: i32 = 0): i32 { return <i32>parseI32(value, radix) }
@inline export function parseFloat(value: string): i32 { return <i32>parseFloat(value) }
}
@builtin export declare function i64(value: void): i64;
@ -102,8 +96,6 @@ export namespace i64 {
@builtin export declare function store16(offset: usize, value: i64, constantOffset?: usize): void;
@builtin export declare function store32(offset: usize, value: i64, constantOffset?: usize): void;
@builtin export declare function store(offset: usize, value: i64, constantOffset?: usize): void;
@inline export function parseInt(value: string, radix: i32 = 0): i64 { return <i64>parseI64(value, radix) }
@inline export function parseFloat(value: string): i64 { return <i64>parseFloat(value) }
}
@builtin export declare function isize(value: void): isize;
@ -114,40 +106,30 @@ export namespace isize {
export const MAX_VALUE: isize = sizeof<i32>() == sizeof<isize>()
? 2147483647
: <isize>9223372036854775807;
@inline export function parseInt(value: string, radix: i32 = 0): isize { return <isize>parseI64(value, radix) }
@inline export function parseFloat(value: string): isize { return <isize>parseFloat(value) }
}
@builtin export declare function u8(value: void): u8;
export namespace u8 {
export const MIN_VALUE: u8 = 0;
export const MAX_VALUE: u8 = 255;
@inline export function parseInt(value: string, radix: i32 = 0): u8 { return <u8>parseI32(value, radix) }
@inline export function parseFloat(value: string): u8 { return <u8>parseFloat(value) }
}
@builtin export declare function u16(value: void): u16;
export namespace u16 {
export const MIN_VALUE: u16 = 0;
export const MAX_VALUE: u16 = 65535;
@inline export function parseInt(value: string, radix: i32 = 0): u16 { return <u16>parseI32(value, radix) }
@inline export function parseFloat(value: string): u16 { return <u16>parseFloat(value) }
}
@builtin export declare function u32(value: void): u32;
export namespace u32 {
export const MIN_VALUE: u32 = 0;
export const MAX_VALUE: u32 = 4294967295;
@inline export function parseInt(value: string, radix: i32 = 0): u32 { return <u32>parseI32(value, radix) }
@inline export function parseFloat(value: string): u32 { return <u32>parseFloat(value) }
}
@builtin export declare function u64(value: void): u64;
export namespace u64 {
export const MIN_VALUE: u64 = 0;
export const MAX_VALUE: u64 = 18446744073709551615;
@inline export function parseInt(value: string, radix: i32 = 0): u64 { return <u64>parseI64(value, radix) }
@inline export function parseFloat(value: string): u64 { return <u64>parseFloat(value) }
}
@builtin export declare function usize(value: void): usize;
@ -156,8 +138,6 @@ export namespace usize {
export const MAX_VALUE: usize = sizeof<u32>() == sizeof<usize>()
? 4294967295
: <usize>18446744073709551615;
@inline export function parseInt(value: string, radix: i32 = 0): usize { return <usize>parseI64(value, radix) }
@inline export function parseFloat(value: string): usize { return <usize>parseFloat(value) }
}
@builtin export declare function bool(value: void): bool;
@ -174,9 +154,6 @@ export namespace f32 {
export const MIN_NORMAL_VALUE = reinterpret<f32>(0x00800000); // 0x1p-126f
export const MIN_SAFE_INTEGER: f32 = -16777215;
export const MAX_SAFE_INTEGER: f32 = 16777215;
export const POSITIVE_INFINITY: f32 = Infinity;
export const NEGATIVE_INFINITY: f32 = -Infinity;
export const NaN: f32 = NaN;
@builtin export declare function abs(value: f32): f32;
@builtin export declare function ceil(value: f32): f32;
@builtin export declare function copysign(x: f32, y: f32): f32;
@ -189,12 +166,6 @@ export namespace f32 {
@builtin export declare function sqrt(value: f32): f32;
@builtin export declare function store(offset: usize, value: f32, constantOffset?: usize): void;
@builtin export declare function trunc(value: f32): f32;
@inline export function isNaN(value: f32): bool { return isNaN<f32>(value) }
@inline export function isFinite(value: f32): bool { return isFinite<f32>(value) }
@inline export function isSafeInteger(value: f32): bool { return abs<f32>(value) <= f32.MAX_SAFE_INTEGER && trunc<f32>(value) == value }
@inline export function isInteger(value: f32): bool { return isFinite<f32>(value) && trunc<f32>(value) == value }
@inline export function parseInt(value: string, radix: i32 = 0): f32 { return <f32>parseI64(value, radix) }
@inline export function parseFloat(value: string): f32 { return <f32>parseFloat(value) }
}
@builtin export declare function f64(value: void): f64;
@ -205,9 +176,6 @@ export namespace f64 {
export const MIN_NORMAL_VALUE = reinterpret<f64>(0x0010000000000000); // 0x1p-1022
export const MIN_SAFE_INTEGER: f64 = -9007199254740991;
export const MAX_SAFE_INTEGER: f64 = 9007199254740991;
export const POSITIVE_INFINITY: f64 = Infinity;
export const NEGATIVE_INFINITY: f64 = -Infinity;
export const NaN: f64 = NaN;
@builtin export declare function abs(value: f64): f64;
@builtin export declare function ceil(value: f64): f64;
@builtin export declare function copysign(x: f64, y: f64): f64;
@ -220,12 +188,6 @@ export namespace f64 {
@builtin export declare function sqrt(value: f64): f64;
@builtin export declare function store(offset: usize, value: f64, constantOffset?: usize): void;
@builtin export declare function trunc(value: f64): f64;
@inline export function isNaN(value: f64): bool { return isNaN<f64>(value) }
@inline export function isFinite(value: f64): bool { return isFinite<f64>(value) }
@inline export function isSafeInteger(value: f64): bool { return abs<f64>(value) <= f64.MAX_SAFE_INTEGER && trunc<f64>(value) == value }
@inline export function isInteger(value: f64): bool { return isFinite<f64>(value) && trunc<f64>(value) == value }
@inline export function parseInt(value: string, radix: i32 = 0): f64 { return <f64>parseI64(value, radix) }
@inline export function parseFloat(value: string): f64 { return parseFloat(value) }
}
@builtin export declare function start(): void;

View File

@ -238,7 +238,7 @@ declare namespace i64 {
export function parseInt(string: string, radix?: i32): i64;
}
/** Converts any other numeric value to a 32-bit (in WASM32) respectivel 64-bit (in WASM64) signed integer. */
declare var isize: i32 | i64;
declare var isize: typeof i32 | typeof i64;
/** Converts any other numeric value to an 8-bit unsigned integer. */
declare function u8(value: i8 | i16 | i32 | i64 | isize | u8 | u16 | u32 | u64 | usize | bool | f32 | f64): i8;
declare namespace u8 {
@ -288,7 +288,7 @@ declare namespace u64 {
export function parseInt(string: string, radix?: i32): u64;
}
/** Converts any other numeric value to a 32-bit (in WASM32) respectivel 64-bit (in WASM64) unsigned integer. */
declare var usize: u32 | u64;
declare var usize: typeof u32 | typeof u64;
/** Converts any other numeric value to a 1-bit unsigned integer. */
declare function bool(value: i8 | i16 | i32 | i64 | isize | u8 | u16 | u32 | u64 | usize | bool | f32 | f64): bool;
declare namespace bool {
@ -330,7 +330,7 @@ declare namespace f32 {
export function isInteger(value: f32): bool;
/** Converts a string to a floating-point number. */
export function parseFloat(string: string): f32;
/** Converts A string to an integer. */
/** Converts a string to an integer. */
export function parseInt(string: string, radix?: i32): f32;
}
/** Converts any other numeric value to a 64-bit float. */
@ -352,22 +352,81 @@ declare namespace f64 {
export function load(offset: usize, constantOffset?: usize): f64;
/** Stores a 64-bit float to memory. */
export function store(offset: usize, value: f64, constantOffset?: usize): void;
/** Returns a boolean value that indicates whether a value is the reserved value NaN (not a number). */
export function isNaN(value: f32): bool;
/** Returns true if passed value is finite. */
export function isFinite(value: f32): bool;
/** Returns true if the value passed is a safe integer. */
export function isSafeInteger(value: f64): bool;
/** Returns true if the value passed is an integer, false otherwise. */
export function isInteger(value: f64): bool;
/** Converts a string to a floating-point number. */
export function parseFloat(string: string): f64;
/** Converts A string to an integer. */
export function parseInt(string: string, radix?: i32): f64;
}
/** Macro type evaluating to the underlying native WebAssembly type. */
declare type NATIVE<T> = T;
/** Pseudo-class representing the backing class of integer types. */
declare class _Integer {
/** Smallest representable value. */
static readonly MIN_VALUE: number;
/** Largest representable value. */
static readonly MAX_VALUE: number;
/** Converts a string to an integer of this type. */
static parseInt(value: string, radix?: number): number;
/** Converts this integer to a string. */
toString(): string;
}
/** Pseudo-class representing the backing class of floating-point types. */
declare class _Float {
/** Difference between 1 and the smallest representable value greater than 1. */
static readonly EPSILON: f32 | f64;
/** Smallest representable value. */
static readonly MIN_VALUE: f32 | f64;
/** Largest representable value. */
static readonly MAX_VALUE: f32 | f64;
/** Smallest safely representable integer value. */
static readonly MIN_SAFE_INTEGER: f32 | f64;
/** Largest safely representable integer value. */
static readonly MAX_SAFE_INTEGER: f32 | f64;
/** Value representing positive infinity. */
static readonly POSITIVE_INFINITY: f32 | f64;
/** Value representing negative infinity. */
static readonly NEGATIVE_INFINITY: f32 | f64;
/** Value representing 'not a number'. */
static readonly NaN: f32 | f64;
/** Returns a boolean value that indicates whether a value is the reserved value NaN (not a number). */
static isNaN(value: f32 | f64): bool;
/** Returns true if passed value is finite. */
static isFinite(value: f32 | f64): bool;
/** Returns true if the value passed is a safe integer. */
static isSafeInteger(value: f32 | f64): bool;
/** Returns true if the value passed is an integer, false otherwise. */
static isInteger(value: f32 | f64): bool;
/** Converts A string to an integer. */
static parseInt(value: string, radix?: i32): f32 | f64;
/** Converts a string to a floating-point number. */
static parseFloat(value: string): f32 | f64;
/** Converts this floating-point number to a string. */
toString(this: f64): string;
}
/** Backing class of signed 8-bit integers. */
declare const I8: typeof _Integer;
/** Backing class of signed 16-bit integers. */
declare const I16: typeof _Integer;
/** Backing class of signed 32-bit integers. */
declare const I32: typeof _Integer;
/** Backing class of signed 64-bit integers. */
declare const I64: typeof _Integer;
/** Backing class of signed size integers. */
declare const Isize: typeof _Integer;
/** Backing class of unsigned 8-bit integers. */
declare const U8: typeof _Integer;
/** Backing class of unsigned 16-bit integers. */
declare const U16: typeof _Integer;
/** Backing class of unsigned 32-bit integers. */
declare const U32: typeof _Integer;
/** Backing class of unsigned 64-bit integers. */
declare const U64: typeof _Integer;
/** Backing class of unsigned size integers. */
declare const Usize: typeof _Integer;
/** Backing class of 32-bit floating-point values. */
declare const F32: typeof _Float;
/** Backing class of 64-bit floating-point values. */
declare const F64: typeof _Float;
// User-defined diagnostic macros
/** Emits a user-defined diagnostic error when encountered. */

View File

@ -1,4 +1,35 @@
// export abstract class Iterator<T> {
// abstract get done(): bool;
// abstract next(): T;
// }
export abstract class Iterable<T> {
// ?
}
@sealed
export abstract class Iterator<T> {
// private constructor(iterable: Iterable<T>) {
// }
// TODO: these need to evaluate the classId at the respective reference in order to obtain the
// next value, i.e. arrays work differently than maps. we'd then have:
//
// ╒═══════════════════ Iterator layout (32-bit) ══════════════════╕
// 3 2 1
// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 bits
// ├─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┤
// │ index │
// ├─────────────────────────────────────────────────────────┬───┬─┤
// │ reference │ 0 │D│
// └─────────────────────────────────────────────────────────┴───┴─┘
// D: Done flag
// get value(this: u64): T {
// ?
// }
// next(this: u64): Iterator<T> {
// ?
// }
done(this: u64): bool {
return <bool>(this & 1);
}
}

271
std/assembly/number.ts Normal file
View File

@ -0,0 +1,271 @@
import {
itoa,
dtoa
} from "./internal/number";
import {
isNaN as builtin_isNaN,
isFinite as builtin_isFinite
} from "./builtins";
@sealed
export abstract class I8 {
static readonly MIN_VALUE: i8 = i8.MIN_VALUE;
static readonly MAX_VALUE: i8 = i8.MAX_VALUE;
static parseInt(value: string, radix: i32 = 0): i8 {
return <i8>parseI32(value, radix);
}
toString(this: i8): String {
// TODO: radix
return itoa(this);
}
}
@sealed
export abstract class I16 {
static readonly MIN_VALUE: i16 = i16.MIN_VALUE;
static readonly MAX_VALUE: i16 = i16.MAX_VALUE;
static parseInt(value: string, radix: i32 = 0): i16 {
return <i16>parseI32(value, radix);
}
toString(this: i16): String {
// TODO: radix
return itoa(this);
}
}
@sealed
export abstract class I32 {
static readonly MIN_VALUE: i32 = i32.MIN_VALUE;
static readonly MAX_VALUE: i32 = i32.MAX_VALUE;
static parseInt(value: string, radix: i32 = 0): i32 {
return <i32>parseI32(value, radix);
}
toString(this: i32): String {
// TODO: radix
return itoa(this);
}
}
@sealed
export abstract class I64 {
static readonly MIN_VALUE: i64 = i64.MIN_VALUE;
static readonly MAX_VALUE: i64 = i64.MAX_VALUE;
static parseInt(value: string, radix: i32 = 0): i64 {
return <i64>parseI64(value, radix);
}
toString(this: i64): String {
// TODO: radix
return itoa(this);
}
}
@sealed
export abstract class Isize {
static readonly MIN_VALUE: isize = isize.MIN_VALUE;
static readonly MAX_VALUE: isize = isize.MAX_VALUE;
static parseInt(value: string, radix: i32 = 0): isize {
return <isize>parseI64(value, radix);
}
toString(this: isize): String {
// TODO: radix
return itoa(this);
}
}
@sealed
export abstract class U8 {
static readonly MIN_VALUE: u8 = u8.MIN_VALUE;
static readonly MAX_VALUE: u8 = u8.MAX_VALUE;
static parseInt(value: string, radix: i32 = 0): u8 {
return <u8>parseI32(value, radix);
}
toString(this: u8): String {
// TODO: radix
return itoa(this);
}
}
@sealed
export abstract class U16 {
static readonly MIN_VALUE: u16 = u16.MIN_VALUE;
static readonly MAX_VALUE: u16 = u16.MAX_VALUE;
static parseInt(value: string, radix: i32 = 0): u16 {
return <u16>parseI32(value, radix);
}
toString(this: u16): String {
// TODO: radix
return itoa(this);
}
}
@sealed
export abstract class U32 {
static readonly MIN_VALUE: u32 = u32.MIN_VALUE;
static readonly MAX_VALUE: u32 = u32.MAX_VALUE;
static parseInt(value: string, radix: i32 = 0): u32 {
return <u32>parseI32(value, radix);
}
toString(this: u32): String {
// TODO: radix
return itoa(this);
}
}
@sealed
export abstract class U64 {
static readonly MIN_VALUE: u64 = u64.MIN_VALUE;
static readonly MAX_VALUE: u64 = u64.MAX_VALUE;
static parseInt(value: string, radix: i32 = 0): u64 {
return <u64>parseI64(value, radix);
}
toString(this: u64): String {
// TODO: radix
return itoa(this);
}
}
@sealed
export abstract class Usize {
static readonly MIN_VALUE: usize = usize.MIN_VALUE;
static readonly MAX_VALUE: usize = usize.MAX_VALUE;
static parseInt(value: string, radix: i32 = 0): usize {
return <usize>parseI64(value, radix);
}
toString(this: usize): String {
// TODO: radix
return itoa(this);
}
}
@sealed
export abstract class Bool {
static readonly MIN_VALUE: bool = bool.MIN_VALUE;
static readonly MAX_VALUE: bool = bool.MAX_VALUE;
toString(this: bool): String {
// TODO: radix?
return this ? "true" : "false";
}
}
@sealed
export abstract class Boolean extends Bool {}
@sealed
export abstract class F32 {
static readonly EPSILON: f32 = f32.EPSILON;
static readonly MIN_VALUE: f32 = f32.MIN_VALUE;
static readonly MAX_VALUE: f32 = f32.MAX_VALUE;
static readonly MIN_SAFE_INTEGER: f32 = f32.MIN_SAFE_INTEGER;
static readonly MAX_SAFE_INTEGER: f32 = f32.MAX_SAFE_INTEGER;
static readonly POSITIVE_INFINITY: f32 = Infinity;
static readonly NEGATIVE_INFINITY: f32 = -Infinity;
static readonly NaN: f32 = NaN;
static isNaN(value: f32): bool {
return isNaN<f32>(value);
}
static isFinite(value: f32): bool {
return isFinite<f32>(value);
}
static isSafeInteger(value: f32): bool {
return abs<f32>(value) <= f32.MAX_SAFE_INTEGER && trunc<f32>(value) == value;
}
static isInteger(value: f32): bool {
return isFinite<f32>(value) && trunc<f32>(value) == value;
}
static parseInt(value: string, radix: i32 = 0): f32 {
return <f32>parseI64(value, radix);
}
static parseFloat(value: string): f32 {
return <f32>parseFloat(value);
}
toString(this: f32): String {
// TODO: radix
return dtoa(this);
}
}
@sealed
export abstract class F64 {
static readonly EPSILON: f64 = f64.EPSILON;
static readonly MIN_VALUE: f64 = f64.MIN_VALUE;
static readonly MAX_VALUE: f64 = f64.MAX_VALUE;
static readonly MIN_SAFE_INTEGER: f64 = f64.MIN_SAFE_INTEGER;
static readonly MAX_SAFE_INTEGER: f64 = f64.MAX_SAFE_INTEGER;
static readonly POSITIVE_INFINITY: f64 = Infinity;
static readonly NEGATIVE_INFINITY: f64 = -Infinity;
static readonly NaN: f64 = NaN;
static isNaN(value: f64): bool {
return builtin_isNaN<f64>(value);
}
static isFinite(value: f64): bool {
return builtin_isFinite<f64>(value);
}
static isSafeInteger(value: f64): bool {
return abs<f64>(value) <= f64.MAX_SAFE_INTEGER && trunc<f64>(value) == value;
}
static isInteger(value: f64): bool {
return builtin_isFinite<f64>(value) && trunc<f64>(value) == value;
}
static parseInt(value: string, radix: i32 = 0): f64 {
return <f64>parseI64(value, radix);
}
static parseFloat(value: string): f64 {
return parseFloat(value);
}
toString(this: f64): String {
// TODO: radix
return dtoa(this);
}
}
@sealed
export abstract class Number extends F64 {}

View File

@ -64,10 +64,7 @@ Object.defineProperties(
"MAX_VALUE": { value: Math.fround(3.4028235e+38), writable: false },
"MIN_NORMAL_VALUE": { value: Math.fround(1.17549435e-38), writable: false },
"MIN_SAFE_INTEGER": { value: -16777215, writable: false },
"MAX_SAFE_INTEGER": { value: 16777215, writable: false },
"POSITIVE_INFINITY": { value: Infinity, writable: false },
"NEGATIVE_INFINITY": { value: -Infinity, writable: false },
"NaN": { value: NaN, writable: false }
"MAX_SAFE_INTEGER": { value: 16777215, writable: false }
});
Object.defineProperties(
@ -78,10 +75,7 @@ Object.defineProperties(
"MAX_VALUE": { value: 1.7976931348623157e+308, writable: false },
"MIN_NORMAL_VALUE": { value: 2.2250738585072014e-308 , writable: false },
"MIN_SAFE_INTEGER": { value: -9007199254740991, writable: false },
"MAX_SAFE_INTEGER": { value: 9007199254740991, writable: false },
"POSITIVE_INFINITY": { value: Infinity, writable: false },
"NEGATIVE_INFINITY": { value: -Infinity, writable: false },
"NaN": { value: NaN, writable: false }
"MAX_SAFE_INTEGER": { value: 9007199254740991, writable: false }
});
globalScope["clz"] = Math.clz32;

View File

@ -314,27 +314,6 @@ assert(f32.MAX_VALUE == 3.40282347e+38);
assert(f32.MIN_SAFE_INTEGER == -16777215);
assert(f32.MAX_SAFE_INTEGER == 16777215);
assert(f32.EPSILON == 1.19209290e-07);
assert(isNaN<f32>(f32.NaN));
assert(f32.isSafeInteger(f32.MIN_SAFE_INTEGER - 1) == false);
assert(f32.isSafeInteger(f32.MIN_SAFE_INTEGER) == true);
assert(f32.isSafeInteger(+0.0) == true);
assert(f32.isSafeInteger(-0.0) == true);
assert(f32.isSafeInteger(NaN) == false);
assert(f32.isSafeInteger(Infinity) == false);
assert(f32.isSafeInteger(f32.MAX_SAFE_INTEGER) == true);
assert(f32.isSafeInteger(f32.MAX_SAFE_INTEGER + 1) == false);
assert(f32.isSafeInteger(0.5) == false);
assert(f32.isInteger(+0.0) == true);
assert(f32.isInteger(-0.0) == true);
assert(f32.isInteger(NaN) == false);
assert(f32.isInteger(Infinity) == false);
assert(f32.isInteger(f32.EPSILON) == false);
assert(f32.isInteger(+1.0) == true);
assert(f32.isInteger(-1.0) == true);
assert(f32.isInteger(f32.MIN_SAFE_INTEGER) == true);
assert(f32.isInteger(f32.MAX_SAFE_INTEGER) == true);
assert(f32.isInteger(+0.5) == false);
assert(f32.isInteger(-1.5) == false);
assert(f64.MIN_NORMAL_VALUE == 2.2250738585072014e-308);
assert(f64.MIN_VALUE == 5e-324);
@ -342,27 +321,6 @@ assert(f64.MAX_VALUE == 1.7976931348623157e+308);
assert(f64.MIN_SAFE_INTEGER == -9007199254740991);
assert(f64.MAX_SAFE_INTEGER == 9007199254740991);
assert(f64.EPSILON == 2.2204460492503131e-16);
assert(isNaN<f64>(f64.NaN));
assert(f64.isSafeInteger(f64.MIN_SAFE_INTEGER - 1) == false);
assert(f64.isSafeInteger(f64.MIN_SAFE_INTEGER) == true);
assert(f64.isSafeInteger(+0.0) == true);
assert(f64.isSafeInteger(-0.0) == true);
assert(f64.isSafeInteger(NaN) == false);
assert(f64.isSafeInteger(Infinity) == false);
assert(f64.isSafeInteger(f64.MAX_SAFE_INTEGER) == true);
assert(f64.isSafeInteger(f64.MAX_SAFE_INTEGER + 1) == false);
assert(f64.isSafeInteger(0.5) == false);
assert(f64.isInteger(+0.0) == true);
assert(f64.isInteger(-0.0) == true);
assert(f64.isInteger(NaN) == false);
assert(f64.isInteger(Infinity) == false);
assert(f64.isInteger(f64.EPSILON) == false);
assert(f64.isInteger(+1.0) == true);
assert(f64.isInteger(-1.0) == true);
assert(f64.isInteger(f64.MIN_SAFE_INTEGER) == true);
assert(f64.isInteger(f64.MAX_SAFE_INTEGER) == true);
assert(f64.isInteger(+0.5) == false);
assert(f64.isInteger(-1.5) == false);
// inline-assembler

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

69
tests/compiler/number.ts Normal file
View File

@ -0,0 +1,69 @@
import "allocator/arena";
// basic class bindings
// variable
var a = 1;
assert(a.toString() == "1");
// literal
assert(2..toString() == "2.0");
assert((3).toString() == "3");
// unary prefix
assert((-5).toString() == "-5");
assert((+4).toString() == "4");
assert((++a).toString() == "2");
assert((--a).toString() == "1");
assert((!0).toString() == "true");
assert((!1).toString() == "false");
// assert((~a).toString() == "-2");
// unary postfix
assert((a++).toString() == "1");
assert((a--).toString() == "2");
// float
assert(isNaN<f32>(F32.NaN));
assert(F32.isSafeInteger(f32.MIN_SAFE_INTEGER - 1) == false);
assert(F32.isSafeInteger(f32.MIN_SAFE_INTEGER) == true);
assert(F32.isSafeInteger(+0.0) == true);
assert(F32.isSafeInteger(-0.0) == true);
assert(F32.isSafeInteger(NaN) == false);
assert(F32.isSafeInteger(Infinity) == false);
assert(F32.isSafeInteger(f32.MAX_SAFE_INTEGER) == true);
assert(F32.isSafeInteger(f32.MAX_SAFE_INTEGER + 1) == false);
assert(F32.isSafeInteger(0.5) == false);
assert(F32.isInteger(+0.0) == true);
assert(F32.isInteger(-0.0) == true);
assert(F32.isInteger(NaN) == false);
assert(F32.isInteger(Infinity) == false);
assert(F32.isInteger(f32.EPSILON) == false);
assert(F32.isInteger(+1.0) == true);
assert(F32.isInteger(-1.0) == true);
assert(F32.isInteger(f32.MIN_SAFE_INTEGER) == true);
assert(F32.isInteger(f32.MAX_SAFE_INTEGER) == true);
assert(F32.isInteger(+0.5) == false);
assert(F32.isInteger(-1.5) == false);
assert(isNaN<f64>(F64.NaN));
assert(F64.isSafeInteger(f64.MIN_SAFE_INTEGER - 1) == false);
assert(F64.isSafeInteger(f64.MIN_SAFE_INTEGER) == true);
assert(F64.isSafeInteger(+0.0) == true);
assert(F64.isSafeInteger(-0.0) == true);
assert(F64.isSafeInteger(NaN) == false);
assert(F64.isSafeInteger(Infinity) == false);
assert(F64.isSafeInteger(f64.MAX_SAFE_INTEGER) == true);
assert(F64.isSafeInteger(f64.MAX_SAFE_INTEGER + 1) == false);
assert(F64.isSafeInteger(0.5) == false);
assert(F64.isInteger(+0.0) == true);
assert(F64.isInteger(-0.0) == true);
assert(F64.isInteger(NaN) == false);
assert(F64.isInteger(Infinity) == false);
assert(F64.isInteger(f64.EPSILON) == false);
assert(F64.isInteger(+1.0) == true);
assert(F64.isInteger(-1.0) == true);
assert(F64.isInteger(f64.MIN_SAFE_INTEGER) == true);
assert(F64.isInteger(f64.MAX_SAFE_INTEGER) == true);
assert(F64.isInteger(+0.5) == false);
assert(F64.isInteger(-1.5) == false);

File diff suppressed because it is too large Load Diff