diff --git a/assembly.d.ts b/assembly.d.ts index 1b85909e..424aacb2 100644 --- a/assembly.d.ts +++ b/assembly.d.ts @@ -1,5 +1,3 @@ -// types - /** An 8-bit signed integer. */ declare type i8 = number; /** A 16-bit signed integer. */ @@ -27,78 +25,16 @@ declare type f32 = number; /** A 64-bit float. */ declare type f64 = number; -// builtins - -/** Performs the sign-agnostic rotate left operation on a 32-bit or 64-bit integer. */ -declare function rotl(value: T, shift: T): T; -/** Performs the sign-agnostic rotate right operation on a 32-bit or 64-bit integer. */ -declare function rotr(value: T, shift: T): T; -/** Performs the sign-agnostic count leading zero bits operation on a 32-bit or 64-bit integer. All zero bits are considered leading if the value is zero. */ -declare function clz(value: T): T; -/** Performs the sign-agnostic count tailing zero bits operation on a 32-bit or 64-bit integer. All zero bits are considered trailing if the value is zero. */ -declare function ctz(value: T): T; -/** Performs the sign-agnostic count number of one bits operation on a 32-bit or 64-bit integer. */ -declare function popcnt(value: T): T; - -/** Computes the absolute value of a 32-bit or 64-bit float. */ -declare function abs(value: T): T; -/** Performs the ceiling operation on a 32-bit or 64-bit float. */ -declare function ceil(value: T): T; -/** Performs the floor operation on a 32-bit or 64-bit float. */ -declare function floor(value: T): T; -/** Calculates the square root of a 32-bit or 64-bit float. */ -declare function sqrt(value: T): T; -/** Rounds to the nearest integer towards zero of a 32-bit or 64-bit float. */ -declare function trunc(value: T): T; -/** Rounds to the nearest integer tied to even of a 32-bit or 64-bit float. */ -declare function nearest(value: T): T; -/** Determines the minimum of two 32-bit or 64-bit floats. If either operand is `NaN`, returns `NaN`. */ -declare function min(left: T, right: T): T; -/** Determines the maximum of two 32-bit or 64-bit floats. If either operand is `NaN`, returns `NaN`. */ -declare function max(left: T, right: T): T; -/** Composes a 32-bit or 64-bit float from the magnitude of `x` and the sign of `y`. */ -declare function copysign(x: T, y: T): T; - -/** Reinterprets the bits of a value of type `T1` as type `T2`. Valid reinterpretations are i32 to/from f32 and i64 to/from f64. */ -declare function reinterpret(value: T1): T2; -/** Returns the current memory size in units of pages. One page is 64kb. */ -declare function current_memory(): i32; -/** Grows linear memory by a given unsigned delta of pages. One page is 64kb. Returns the previous memory size in units of pages or `-1` on failure. */ -declare function grow_memory(value: i32): i32; -/** Emits an unreachable operation that results in a runtime error when executed. */ -declare function unreachable(): void; - -/** Loads a value of the specified type from memory. */ -declare function load(offset: usize): T; -/** Stores a value of the specified type to memory. */ -declare function store(offset: usize, value: T): void; -/** Determines the byte size of the specified core or class type. Compiles to a constant. */ -declare function sizeof(): usize; -/** Gets the underlying pointer value of a class type. */ -declare function pointerof(cls: T): usize; -/** Creates a class type from its underlying pointer value. */ -declare function classof(ptr: usize): T; - -// standard library - -/** NaN (not a number) as a 32-bit or 64-bit float depending on context. */ -declare const NaN: number; -/** Positive infinity as a 32-bit or 64-bit float depending on context. */ -declare const Infinity: number; - -/** Tests if a 32-bit or 64-bit float is NaN. */ -declare function isNaN(value: T): bool; -/** Tests if a 32-bit or 64-bit float is finite, that is not NaN or +/-Infinity. */ -declare function isFinite(value: T): bool; - /** A decorator marking a function or class as global. */ declare function global(name?: string): any; /** A decorator marking a function as ideally being inlined. */ declare function inline(): any; /** A decorator marking a class that manually manages its memory. */ declare function allocates(): any; + declare function operator(token: string, fn: any): any; +/// /// /// /// diff --git a/src/compiler.ts b/src/compiler.ts index 8d001157..45c79fc5 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -1,6 +1,6 @@ import { PATH_DELIMITER } from "./constants"; import { DiagnosticCode, DiagnosticMessage, DiagnosticEmitter } from "./diagnostics"; -import { Module, MemorySegment, ExpressionRef, UnaryOp, BinaryOp, NativeType, FunctionTypeRef } from "./module"; +import { Module, MemorySegment, ExpressionRef, UnaryOp, BinaryOp, HostOp, NativeType, FunctionTypeRef } from "./module"; import { Program, ClassPrototype, Class, Element, ElementKind, Enum, FunctionPrototype, Function, Global, Local, Namespace, Parameter } from "./program"; import { CharCode, I64, U64, normalizePath, sb } from "./util"; import { Token, Range } from "./tokenizer"; @@ -285,7 +285,7 @@ export class Compiler extends DiagnosticEmitter { } compileGlobal(element: Global): bool { - if (element.compiled) + if (element.isCompiled) return true; const declaration: VariableLikeDeclarationStatement | null = element.declaration; let type: Type | null = element.type; @@ -335,7 +335,7 @@ export class Compiler extends DiagnosticEmitter { this.startFunctionBody.push(this.module.createSetGlobal(internalName, initializer)); } else this.module.addGlobal(internalName, NativeType.I32, element.isMutable, initializer); - return element.compiled = true; + return element.isCompiled = true; } // enums @@ -348,9 +348,9 @@ export class Compiler extends DiagnosticEmitter { } compileEnum(element: Enum): void { - if (element.compiled) + if (element.isCompiled) return; - element.compiled = true; + element.isCompiled = true; let previousInternalName: string | null = null; for (let [key, val] of element.members) { if (val.hasConstantValue) { @@ -401,7 +401,7 @@ export class Compiler extends DiagnosticEmitter { } compileFunction(instance: Function): void { - if (instance.compiled) + if (instance.isCompiled) return; const declaration: FunctionDeclaration | null = instance.template.declaration; @@ -412,7 +412,7 @@ export class Compiler extends DiagnosticEmitter { this.error(DiagnosticCode.Function_implementation_is_missing_or_not_immediately_following_the_declaration, declaration.identifier.range); return; } - instance.compiled = true; + instance.isCompiled = true; // compile statements const previousFunction: Function = this.currentFunction; @@ -1221,7 +1221,7 @@ export class Compiler extends DiagnosticEmitter { left = this.compileExpression(expression.left, contextualType, false); right = this.compileExpression(expression.right, this.currentType); if (this.currentType.isAnyFloat) - throw new Error("not implemented"); // TODO: internal fmod + throw new Error("not implemented"); // TODO: internal fmod, possibly simply imported from JS op = this.currentType.isLongInteger ? BinaryOp.RemI64 : BinaryOp.RemI32; @@ -1337,21 +1337,24 @@ export class Compiler extends DiagnosticEmitter { } compileCallExpression(expression: CallExpression, contextualType: Type): ExpressionRef { - // TODO - /* const callee: Expression = expression.expression; - switch (callee.kind) { - case NodeKind.THIS: - // use current class - case NodeKind.PROPERTYACCESS: - // compile, use class in currentType? - case NodeKind.IDENTIFIER: - // lookup identifier? - } */ const element: Element | null = this.program.resolveElement(expression.expression, this.currentFunction); // reports if (!element) return this.module.createUnreachable(); if (element.kind == ElementKind.FUNCTION_PROTOTYPE) { - const functionInstance: Function | null = (element).resolveInclTypeArguments(expression.typeArguments, this.currentFunction.contextualTypeArguments, expression); // reports + const functionPrototype: FunctionPrototype = element; + let functionInstance: Function | null = null; + if (functionPrototype.isBuiltin) { + sb.length = 0; + for (let i: i32 = 0, k: i32 = expression.typeArguments.length; i < k; ++i) { + let type: Type | null = this.program.resolveType(expression.typeArguments[i], this.currentFunction.contextualTypeArguments, true); // reports + if (!type) + return this.module.createUnreachable(); + sb.push(type.toString()); + } + functionInstance = functionPrototype.instances.get(sb.join(",")); + } else { + functionInstance = (element).resolveInclTypeArguments(expression.typeArguments, this.currentFunction.contextualTypeArguments, expression); // reports + } if (!functionInstance) return this.module.createUnreachable(); return this.compileCall(functionInstance, expression.arguments, expression); @@ -1362,9 +1365,6 @@ export class Compiler extends DiagnosticEmitter { /** Compiles a call to a function. If an instance method, `this` is the first element in `argumentExpressions`. */ compileCall(functionInstance: Function, argumentExpressions: Expression[], reportNode: Node): ExpressionRef { - // when called, the function is considered reachable -> compile - if (!functionInstance.compiled) - this.compileFunction(functionInstance); // validate and compile arguments const parameters: Parameter[] = functionInstance.parameters; @@ -1385,7 +1385,7 @@ export class Compiler extends DiagnosticEmitter { const initializer: Expression | null = parameters[i].initializer; if (initializer) { // omitted, uses initializer // FIXME: here, the initializer is compiled in the caller's scope. - // a solution could be to use a stub for each possible overload, calling the + // a solution could be to use a stub for each possible overload, calling the // full function with optional arguments being part of the stub's body. operands[i] = this.compileExpression(initializer, parameters[i].type); } else { // too few arguments @@ -1398,8 +1398,187 @@ export class Compiler extends DiagnosticEmitter { } } - // finally compile the call this.currentType = functionInstance.returnType; + + let tempLocal: Local; + if (functionInstance.isBuiltin) { + 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": + return this.module.createUnreachable(); + + case "sizeof": // T.size + if (this.options.target == Target.WASM64) + return this.module.createI64(Math.ceil(functionInstance.typeArguments[0].size / 8), 0); + return this.module.createI32(Math.ceil(functionInstance.typeArguments[0].size / 8)); + + 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.F64) + ); + } + break; + + case "isFinite": // value != value ? false : abs(value) != Infinity + if (functionInstance.typeArguments[0] == Type.f64) { + tempLocal = this.currentFunction.addLocal(Type.f64); + return this.module.createSelect( + this.module.createBinary(BinaryOp.NeF64, + this.module.createTeeLocal(tempLocal.index, operands[0]), + this.module.createGetLocal(tempLocal.index, NativeType.F64) + ), + this.module.createI32(0), + this.module.createBinary(BinaryOp.NeF64, + this.module.createUnary(UnaryOp.AbsF64, + this.module.createGetLocal(tempLocal.index, NativeType.F64) + ), + this.module.createF64(Infinity) + ) + ); + } else if (functionInstance.typeArguments[0] == Type.f32) { + tempLocal = this.currentFunction.addLocal(Type.f32); + return this.module.createSelect( + this.module.createBinary(BinaryOp.NeF32, + this.module.createTeeLocal(tempLocal.index, operands[0]), + this.module.createGetLocal(tempLocal.index, NativeType.F32) + ), + this.module.createI32(0), + this.module.createBinary(BinaryOp.NeF32, + this.module.createUnary(UnaryOp.AbsF32, + this.module.createGetLocal(tempLocal.index, NativeType.F32) + ), + this.module.createF32(Infinity) + ) + ); + } + break; + } + this.error(DiagnosticCode.Operation_not_supported, reportNode.range); + return this.module.createUnreachable(); + } + + if (!functionInstance.isCompiled) + this.compileFunction(functionInstance); + + // imported function + if (functionInstance.isImport) + return this.module.createCallImport(functionInstance.internalName, operands, typeToNativeType(functionInstance.returnType)); + + // internal function return this.module.createCall(functionInstance.internalName, operands, typeToNativeType(functionInstance.returnType)); } @@ -1498,8 +1677,8 @@ export class Compiler extends DiagnosticEmitter { case LiteralKind.FLOAT: { const floatValue: f64 = (expression).value; - if (contextualType == Type.f32 && (Math.fround(floatValue) as f64) == floatValue) - return this.module.createF32(floatValue); + if (contextualType == Type.f32) + return this.module.createF32(Math.fround(floatValue)); this.currentType = Type.f64; return this.module.createF64(floatValue); } diff --git a/src/diagnosticMessages.generated.ts b/src/diagnosticMessages.generated.ts index 42c7af2c..73f75f43 100644 --- a/src/diagnosticMessages.generated.ts +++ b/src/diagnosticMessages.generated.ts @@ -4,6 +4,7 @@ export enum DiagnosticCode { Conversion_from_type_0_to_1_requires_an_explicit_cast = 100, Basic_type_0_cannot_be_nullable = 101, Operation_not_supported = 102, + Operation_is_unsafe = 103, Unterminated_string_literal = 1002, Identifier_expected = 1003, _0_expected = 1005, @@ -70,6 +71,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string { case 100: return "Conversion from type '{0}' to '{1}' requires an explicit cast."; case 101: return "Basic type '{0}' cannot be nullable."; case 102: return "Operation not supported."; + case 103: return "Operation is unsafe."; case 1002: return "Unterminated string literal."; case 1003: return "Identifier expected."; case 1005: return "'{0}' expected."; diff --git a/src/diagnosticMessages.json b/src/diagnosticMessages.json index 3c9bc79f..eef00a2e 100644 --- a/src/diagnosticMessages.json +++ b/src/diagnosticMessages.json @@ -2,6 +2,7 @@ "Conversion from type '{0}' to '{1}' requires an explicit cast.": 100, "Basic type '{0}' cannot be nullable.": 101, "Operation not supported.": 102, + "Operation is unsafe.": 103, "Unterminated string literal.": 1002, "Identifier expected.": 1003, diff --git a/src/program.ts b/src/program.ts index 367c17ea..111eff03 100644 --- a/src/program.ts +++ b/src/program.ts @@ -81,22 +81,8 @@ export class Program extends DiagnosticEmitter { /** Initializes the program and its elements prior to compilation. */ initialize(target: Target = Target.WASM32): void { this.target = target; - this.types = new Map([ // replaces typesStub - ["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); const queuedExports: Map = new Map(); const queuedImports: QueuedImport[] = new Array(); @@ -621,7 +607,9 @@ export abstract class Element { program: Program; internalName: string; globalExportName: string | null = null; - compiled: bool = false; + isCompiled: bool = false; + isImport: bool = false; + isBuiltin: bool = false; constructor(program: Program, internalName: string) { this.program = program; @@ -811,7 +799,7 @@ export class FunctionPrototype extends Element { let resolvedTypeArguments: Type[] | null; if (this.isGeneric) { if (!this.declaration) - throw new Error("not implemented"); // generic builtin + throw new Error("missing declaration"); resolvedTypeArguments = this.program.resolveTypeArguments(this.declaration.typeParameters, typeArgumentNodes, contextualTypeArguments, alternativeReportNode); if (!resolvedTypeArguments) return null; @@ -851,22 +839,23 @@ export class Function extends Element { private breakMinor: i32 = 0; /** Constructs a new concrete function. */ - constructor(template: FunctionPrototype, internalName: string, typeArguments: Type[], parameters: Parameter[], returnType: Type, instanceMethodOf: Class | null) { - super(template.program, internalName); - this.template = template; + constructor(prototype: FunctionPrototype, internalName: string, typeArguments: Type[], parameters: Parameter[], returnType: Type, instanceMethodOf: Class | null) { + super(prototype.program, internalName); + this.template = prototype; this.typeArguments = typeArguments; this.parameters = parameters; this.returnType = returnType; this.instanceMethodOf = instanceMethodOf; + this.isBuiltin = prototype.isBuiltin; let localIndex: i32 = 0; if (instanceMethodOf) { - this.locals.set("this", new Local(template.program, "this", localIndex++, instanceMethodOf.type)); + this.locals.set("this", new Local(prototype.program, "this", localIndex++, instanceMethodOf.type)); for (let [name, type] of instanceMethodOf.contextualTypeArguments) this.contextualTypeArguments.set(name, type); } for (let i: i32 = 0, k: i32 = parameters.length; i < k; ++i) { const parameter: Parameter = parameters[i]; - this.locals.set(parameter.name, new Local(template.program, parameter.name, localIndex++, parameter.type)); + this.locals.set(parameter.name, new Local(prototype.program, parameter.name, localIndex++, parameter.type)); } } @@ -1015,6 +1004,10 @@ export class Class extends Namespace { this.contextualTypeArguments.set(typeParameters[i].identifier.name, typeArguments[i]); } } + + toString(): string { + throw new Error("not implemented"); + } } /** A yet unresvoled interface. */ @@ -1039,3 +1032,100 @@ export class Interface extends Class { super(template, internalName, typeArguments, base); } } + +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 genericInt: Type[] = [ Type.i32, Type.i64 ]; + const genericFloat: Type[] = [ Type.f32, Type.f64 ]; + + addGenericUnaryBuiltin(program, "clz", genericInt); + addGenericUnaryBuiltin(program, "ctz", genericInt); + addGenericUnaryBuiltin(program, "popcnt", genericInt); + addGenericBinaryBuiltin(program, "rotl", genericInt); + addGenericBinaryBuiltin(program, "rotr", genericInt); + + addGenericUnaryBuiltin(program, "abs", genericFloat); + addGenericUnaryBuiltin(program, "ceil", genericFloat); + addGenericUnaryBuiltin(program, "copysign", genericFloat); + addGenericUnaryBuiltin(program, "floor", genericFloat); + addGenericBinaryBuiltin(program, "max", genericFloat); + addGenericBinaryBuiltin(program, "min", genericFloat); + addGenericUnaryBuiltin(program, "nearest", genericFloat); + addGenericUnaryBuiltin(program, "sqrt", genericFloat); + addGenericUnaryBuiltin(program, "trunc", genericFloat); + + addBuiltin(program, "current_memory", [], Type.i32); + addBuiltin(program, "grow_memory", [ Type.i32 ], Type.i32); + addBuiltin(program, "unreachable", [], Type.void); + + addGenericUnaryTestBuiltin(program, "isNaN", genericFloat); + addGenericUnaryTestBuiltin(program, "isFinite", genericFloat); + + // TODO: load, store, sizeof + // sizeof, for example, has varying Ts but really shouldn't provide an instance for each class +} + +function addBuiltin(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, Type.bool, null)); +} + +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); +} + +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); +} + +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); +} diff --git a/src/types.ts b/src/types.ts index 6af8591f..fea29639 100644 --- a/src/types.ts +++ b/src/types.ts @@ -69,6 +69,7 @@ export class Type { case TypeKind.I8: return "i8"; case TypeKind.I16: return "i16"; case TypeKind.I32: return "i32"; + case TypeKind.I64: return "i64"; case TypeKind.ISIZE: return "isize"; case TypeKind.U8: return "u8"; case TypeKind.U16: return "u16"; diff --git a/std/builtins.d.ts b/std/builtins.d.ts new file mode 100644 index 00000000..2b844fc5 --- /dev/null +++ b/std/builtins.d.ts @@ -0,0 +1,59 @@ +/// + +/** Performs the sign-agnostic count leading zero bits operation on a 32-bit or 64-bit integer. All zero bits are considered leading if the value is zero. */ +declare function clz(value: T): T; +/** Performs the sign-agnostic count tailing zero bits operation on a 32-bit or 64-bit integer. All zero bits are considered trailing if the value is zero. */ +declare function ctz(value: T): T; +/** Performs the sign-agnostic count number of one bits operation on a 32-bit or 64-bit integer. */ +declare function popcnt(value: T): T; +/** Performs the sign-agnostic rotate left operation on a 32-bit or 64-bit integer. */ +declare function rotl(value: T, shift: T): T; +/** Performs the sign-agnostic rotate right operation on a 32-bit or 64-bit integer. */ +declare function rotr(value: T, shift: T): T; + +/** Computes the absolute value of a 32-bit or 64-bit float. */ +declare function abs(value: T): T; +/** Performs the ceiling operation on a 32-bit or 64-bit float. */ +declare function ceil(value: T): T; +/** Composes a 32-bit or 64-bit float from the magnitude of `x` and the sign of `y`. */ +declare function copysign(x: T, y: T): T; +/** Performs the floor operation on a 32-bit or 64-bit float. */ +declare function floor(value: T): T; +/** Determines the maximum of two 32-bit or 64-bit floats. If either operand is `NaN`, returns `NaN`. */ +declare function max(left: T, right: T): T; +/** Determines the minimum of two 32-bit or 64-bit floats. If either operand is `NaN`, returns `NaN`. */ +declare function min(left: T, right: T): T; +/** Rounds to the nearest integer tied to even of a 32-bit or 64-bit float. */ +declare function nearest(value: T): T; +/** Reinterprets the bits of a value of type `T1` as type `T2`. Valid reinterpretations are i32 to/from f32 and i64 to/from f64. */ +declare function reinterpret(value: T1): T2; +/** Calculates the square root of a 32-bit or 64-bit float. */ +declare function sqrt(value: T): T; +/** Rounds to the nearest integer towards zero of a 32-bit or 64-bit float. */ +declare function trunc(value: T): T; + +/** Returns the current memory size in units of pages. One page is 64kb. */ +declare function current_memory(): i32; +/** Grows linear memory by a given unsigned delta of pages. One page is 64kb. Returns the previous memory size in units of pages or `-1` on failure. */ +declare function grow_memory(value: i32): i32; +/** Emits an unreachable operation that results in a runtime error when executed. */ +declare function unreachable(): void; + +/** Loads a value of the specified type from memory. */ +declare function load(offset: usize): T; +/** Stores a value of the specified type to memory. */ +declare function store(offset: usize, value: T): void; +/** Determines the byte size of the specified core or class type. Compiles to a constant. */ +declare function sizeof(): usize; + +// standard library + +/** NaN (not a number) as a 32-bit or 64-bit float depending on context. */ +declare const NaN: number; +/** Positive infinity as a 32-bit or 64-bit float depending on context. */ +declare const Infinity: number; + +/** Tests if a 32-bit or 64-bit float is NaN. */ +declare function isNaN(value: T): bool; +/** Tests if a 32-bit or 64-bit float is finite, that is not NaN or +/-Infinity. */ +declare function isFinite(value: T): bool; diff --git a/tests/compiler.ts b/tests/compiler.ts index e994e73f..f22bd0be 100644 --- a/tests/compiler.ts +++ b/tests/compiler.ts @@ -31,9 +31,15 @@ const files: Map = new Map([ return -1; } import { sub } from "../other"; - export function what(): void { + export function doCall(): void { sub(1,2); } + export function doNaN(value: f32): bool { + return isNaN(0.3); + } + export function doRotl(value: u16): u8 { + return rotl(value, 2); + } `], ["../other", @@ -58,7 +64,7 @@ do { const program = parser.finish(); const compiler = new Compiler(program); const module = compiler.compile(); -console.log(program.elements.keys()); +// console.log(program.elements.keys()); // module.optimize(); module.validate();