sizeof, load and store builtins

This commit is contained in:
dcodeIO 2017-12-04 02:00:48 +01:00
parent 017efc71b6
commit 63a67e7c67
17 changed files with 214 additions and 44 deletions

38
assembly.d.ts vendored
View File

@ -30,36 +30,36 @@ declare type f64 = number;
// builtins
/** 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<T extends number>(value: T): T;
declare function clz<T = i32 | i64>(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<T>(value: T): T;
declare function ctz<T = i32 | i64>(value: T): T;
/** Performs the sign-agnostic count number of one bits operation on a 32-bit or 64-bit integer. */
declare function popcnt<T>(value: T): T;
declare function popcnt<T = i32 | i64>(value: T): T;
/** Performs the sign-agnostic rotate left operation on a 32-bit or 64-bit integer. */
declare function rotl<T>(value: T, shift: T): T;
declare function rotl<T = i32 | i64>(value: T, shift: T): T;
/** Performs the sign-agnostic rotate right operation on a 32-bit or 64-bit integer. */
declare function rotr<T>(value: T, shift: T): T;
declare function rotr<T = i32 | i64>(value: T, shift: T): T;
/** Computes the absolute value of a 32-bit or 64-bit float. */
declare function abs<T>(value: T): T;
declare function abs<T = f32 | f64>(value: T): T;
/** Performs the ceiling operation on a 32-bit or 64-bit float. */
declare function ceil<T>(value: T): T;
declare function ceil<T = f32 | f64>(value: T): T;
/** Composes a 32-bit or 64-bit float from the magnitude of `x` and the sign of `y`. */
declare function copysign<T>(x: T, y: T): T;
declare function copysign<T = f32 | f64>(x: T, y: T): T;
/** Performs the floor operation on a 32-bit or 64-bit float. */
declare function floor<T>(value: T): T;
declare function floor<T = f32 | f64>(value: T): T;
/** Determines the maximum of two 32-bit or 64-bit floats. If either operand is `NaN`, returns `NaN`. */
declare function max<T>(left: T, right: T): T;
declare function max<T = f32 | f64>(left: T, right: T): T;
/** Determines the minimum of two 32-bit or 64-bit floats. If either operand is `NaN`, returns `NaN`. */
declare function min<T>(left: T, right: T): T;
declare function min<T = f32 | f64>(left: T, right: T): T;
/** Rounds to the nearest integer tied to even of a 32-bit or 64-bit float. */
declare function nearest<T>(value: T): T;
declare function nearest<T = f32 | f64>(value: T): T;
/** Reinterprets the bits of a value of type `T1` as type `T2`. Valid reinterpretations are i32 to/from f32 and i64 to/from f64. */
declare function reinterpret<T1,T2>(value: T1): T2;
declare function reinterpret<T1 = i32 | i64 | f32 | f64, T2 = i32 | i64 | f32 | f64>(value: T1): T2;
/** Calculates the square root of a 32-bit or 64-bit float. */
declare function sqrt<T>(value: T): T;
declare function sqrt<T = f32 | f64>(value: T): T;
/** Rounds to the nearest integer towards zero of a 32-bit or 64-bit float. */
declare function trunc<T>(value: T): T;
declare function trunc<T = f32 | f64>(value: T): T;
/** Returns the current memory size in units of pages. One page is 64kb. */
declare function current_memory(): i32;
@ -76,14 +76,14 @@ declare function store<T>(offset: usize, value: T): void;
declare function sizeof<T>(): usize;
/** NaN (not a number) as a 32-bit or 64-bit float depending on context. */
declare const NaN: number;
declare const NaN: f32 | f64;
/** Positive infinity as a 32-bit or 64-bit float depending on context. */
declare const Infinity: number;
declare const Infinity: f32 | f64;
/** Tests if a 32-bit or 64-bit float is NaN. */
declare function isNaN<T>(value: T): bool;
declare function isNaN<T = f32 | f64>(value: T): bool;
/** Tests if a 32-bit or 64-bit float is finite, that is not NaN or +/-Infinity. */
declare function isFinite<T>(value: T): bool;
declare function isFinite<T = f32 | f64>(value: T): bool;
/** Traps if the specified value is `false`. */
declare function assert(isTrue: bool): void;

View File

@ -88,6 +88,8 @@ export class Options {
noEmit: bool = false;
/** If true, compiles everything instead of just reachable code. */
noTreeShaking: bool = false;
/** If true, replaces assertions with nops. */
noDebug: bool = false;
}
const enum ConversionKind {
@ -1398,16 +1400,73 @@ export class Compiler extends DiagnosticEmitter {
const functionPrototype: FunctionPrototype = <FunctionPrototype>element;
let functionInstance: Function | null = null;
if (functionPrototype.isBuiltin) {
const k: i32 = expression.typeArguments.length;
const resolvedTypeArguments: Type[] = new Array(k);
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)
for (let i: i32 = 0; i < k; ++i) {
let resolvedType: Type | null = this.program.resolveType(expression.typeArguments[i], this.currentFunction.contextualTypeArguments, true); // reports
if (!resolvedType)
return this.module.createUnreachable();
sb.push(type.toString());
resolvedTypeArguments[i] = resolvedType;
sb.push(resolvedType.toString());
}
functionInstance = <Function | null>functionPrototype.instances.get(sb.join(","));
if (!functionInstance) {
// TODO: sizeof, load, store, see program.ts/initializeBuiltins
let arg0: ExpressionRef, arg1: ExpressionRef;
if (functionPrototype.internalName == "sizeof") { // no parameters
this.currentType = this.options.target == Target.WASM64 ? Type.usize64 : Type.usize32;
if (k != 1) {
this.error(DiagnosticCode.Expected_0_type_arguments_but_got_1, expression.range, "1", k.toString());
return this.module.createUnreachable();
}
if (expression.arguments.length != 0) {
this.error(DiagnosticCode.Expected_0_arguments_but_got_1, expression.range, "0", expression.arguments.length.toString());
return this.module.createUnreachable();
}
return this.options.target == Target.WASM64
? this.module.createI64(resolvedTypeArguments[0].byteSize, 0)
: this.module.createI32(resolvedTypeArguments[0].byteSize);
} else if (functionPrototype.internalName == "load") {
this.currentType = resolvedTypeArguments[0];
if (k != 1) {
this.error(DiagnosticCode.Expected_0_type_arguments_but_got_1, expression.range, "1", k.toString());
return this.module.createUnreachable();
}
if (expression.arguments.length != 1) {
this.error(DiagnosticCode.Expected_0_arguments_but_got_1, expression.range, "1", expression.arguments.length.toString());
return this.module.createUnreachable();
}
arg0 = this.compileExpression(expression.arguments[0], Type.usize32, ConversionKind.IMPLICIT); // reports
this.currentType = resolvedTypeArguments[0];
if (!arg0)
return this.module.createUnreachable();
return this.module.createLoad(resolvedTypeArguments[0].byteSize, resolvedTypeArguments[0].isSignedInteger, arg0, typeToNativeType(resolvedTypeArguments[0]));
} else if (functionPrototype.internalName == "store") {
this.currentType = Type.void;
if (k != 1) {
this.error(DiagnosticCode.Expected_0_type_arguments_but_got_1, expression.range, "1", k.toString());
return this.module.createUnreachable();
}
if (expression.arguments.length != 2) {
this.error(DiagnosticCode.Expected_0_arguments_but_got_1, expression.range, "2", expression.arguments.length.toString());
return this.module.createUnreachable();
}
arg0 = this.compileExpression(expression.arguments[0], Type.usize32, ConversionKind.IMPLICIT); // reports
this.currentType = Type.void;
if (!arg0)
return this.module.createUnreachable();
arg1 = this.compileExpression(expression.arguments[1], resolvedTypeArguments[0], ConversionKind.IMPLICIT);
this.currentType = Type.void;
if (!arg1)
return this.module.createUnreachable();
return this.module.createStore(resolvedTypeArguments[0].byteSize, arg0, arg1, typeToNativeType(resolvedTypeArguments[0]));
}
this.error(DiagnosticCode.Operation_not_supported, expression.range);
return this.module.createUnreachable();
}
} else {
// TODO: infer type arguments from parameter types if omitted
@ -1458,8 +1517,8 @@ export class Compiler extends DiagnosticEmitter {
this.currentType = functionInstance.returnType;
let tempLocal: Local;
if (functionInstance.isBuiltin) {
let tempLocal: Local;
switch (functionInstance.template.internalName) {
case "clz": // i32/i64.clz
@ -1570,11 +1629,6 @@ export class Compiler extends DiagnosticEmitter {
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);
@ -1626,10 +1680,12 @@ export class Compiler extends DiagnosticEmitter {
break;
case "assert":
return this.module.createIf(
this.module.createUnary(UnaryOp.EqzI32, operands[0]),
this.module.createUnreachable()
);
return this.options.noDebug
? this.module.createNop()
: this.module.createIf(
this.module.createUnary(UnaryOp.EqzI32, operands[0]),
this.module.createUnreachable()
);
}
this.error(DiagnosticCode.Operation_not_supported, reportNode.range);
return this.module.createUnreachable();

6
src/glue/js.d.ts vendored
View File

@ -11,6 +11,6 @@ declare type f32 = number;
declare type f64 = number;
declare type bool = boolean;
// Raw memory access (here: Binaryen memory, T=u8)
declare function store<T>(ptr: usize, val: u8): void;
declare function load<T>(ptr: usize): u8;
// Raw memory access (here: Binaryen memory)
declare function store<T = u8>(ptr: usize, val: T): void;
declare function load<T = u8>(ptr: usize): T;

View File

@ -1149,19 +1149,21 @@ function initializeBuiltins(program: Program): void {
addGenericUnaryBuiltin(program, "sqrt", genericFloat);
addGenericUnaryBuiltin(program, "trunc", genericFloat);
addBuiltin(program, "current_memory", [], usize);
addBuiltin(program, "grow_memory", [ usize ], usize);
addBuiltin(program, "unreachable", [], Type.void);
addSimpleBuiltin(program, "current_memory", [], usize);
addSimpleBuiltin(program, "grow_memory", [ usize ], usize);
addSimpleBuiltin(program, "unreachable", [], Type.void);
addGenericUnaryTestBuiltin(program, "isNaN", genericFloat);
addGenericUnaryTestBuiltin(program, "isFinite", genericFloat);
addBuiltin(program, "assert", [ Type.bool ], Type.void);
addSimpleBuiltin(program, "assert", [ Type.bool ], Type.void);
// TODO: load, store, sizeof
// sizeof, for example, has varying Ts but really shouldn't provide an instance for each class
addGenericAnyBuiltin(program, "sizeof");
addGenericAnyBuiltin(program, "load");
addGenericAnyBuiltin(program, "store");
}
function addBuiltin(program: Program, name: string, parameterTypes: Type[], returnType: Type) {
/** Adds a simple (non-generic) builtin. */
function addSimpleBuiltin(program: Program, name: string, parameterTypes: Type[], returnType: Type) {
let prototype: FunctionPrototype = new FunctionPrototype(program, name, null, null);
prototype.isGeneric = false;
prototype.isBuiltin = true;
@ -1173,6 +1175,7 @@ function addBuiltin(program: Program, name: string, parameterTypes: Type[], retu
program.elements.set(name, prototype);
}
/** Adds a generic unary builtin that takes and returns a value of its generic type. */
function addGenericUnaryBuiltin(program: Program, name: string, types: Type[]): void {
let prototype: FunctionPrototype = new FunctionPrototype(program, name, null, null);
prototype.isGeneric = true;
@ -1184,6 +1187,7 @@ function addGenericUnaryBuiltin(program: Program, name: string, types: Type[]):
program.elements.set(name, prototype);
}
/** Adds a generic binary builtin that takes two and returns a value of its generic type. */
function addGenericBinaryBuiltin(program: Program, name: string, types: Type[]): void {
let prototype: FunctionPrototype = new FunctionPrototype(program, name, null, null);
prototype.isGeneric = true;
@ -1195,6 +1199,7 @@ function addGenericBinaryBuiltin(program: Program, name: string, types: Type[]):
program.elements.set(name, prototype);
}
/** Adds a generic unary builtin that alwways returns `bool`. */
function addGenericUnaryTestBuiltin(program: Program, name: string, types: Type[]): void {
let prototype: FunctionPrototype = new FunctionPrototype(program, name, null, null);
prototype.isGeneric = true;
@ -1205,3 +1210,12 @@ function addGenericUnaryTestBuiltin(program: Program, name: string, types: Type[
}
program.elements.set(name, prototype);
}
/** Adds a special generic builtin that takes any type argument. */
function addGenericAnyBuiltin(program: Program, name: string): void {
let prototype: FunctionPrototype = new FunctionPrototype(program, name, null, null);
prototype.isGeneric = true;
prototype.isBuiltin = true;
program.elements.set(name, prototype);
// instances are hard coded in compiler.ts
}

View File

@ -29,6 +29,7 @@ export class Type {
kind: TypeKind;
size: i32;
byteSize: i32;
classType: Class | null;
nullable: bool = false;
nullableType: Type | null = null; // cached, of this type
@ -36,6 +37,7 @@ export class Type {
constructor(kind: TypeKind, size: i32) {
this.kind = kind;
this.size = size;
this.byteSize = Math.ceil(<f64>size / 8);
this.classType = null;
}

View File

@ -838,6 +838,9 @@
isNaN
isFinite
assert
sizeof
load
store
binary/b
binary/i
binary/I

View File

@ -104,3 +104,20 @@ s = grow_memory(1);
if (0) unreachable();
assert(true);
sizeof<u8>();
sizeof<u16>();
sizeof<u32>();
sizeof<u64>();
sizeof<usize>();
sizeof<bool>();
sizeof<i8>();
sizeof<i16>();
sizeof<i32>();
sizeof<i64>();
sizeof<isize>();
sizeof<f32>();
sizeof<f64>();
i = load<i32>(4);
store<i32>(4, i);

View File

@ -475,6 +475,54 @@
)
(unreachable)
)
(drop
(i32.const 1)
)
(drop
(i32.const 2)
)
(drop
(i32.const 4)
)
(drop
(i32.const 8)
)
(drop
(i32.const 4)
)
(drop
(i32.const 1)
)
(drop
(i32.const 1)
)
(drop
(i32.const 2)
)
(drop
(i32.const 4)
)
(drop
(i32.const 8)
)
(drop
(i32.const 4)
)
(drop
(i32.const 4)
)
(drop
(i32.const 8)
)
(set_global $builtins/i
(i32.load
(i32.const 4)
)
)
(i32.store
(i32.const 4)
(get_global $builtins/i)
)
)
)
(;
@ -499,6 +547,9 @@
isNaN
isFinite
assert
sizeof
load
store
builtins/b
builtins/i
builtins/I

View File

@ -77,6 +77,9 @@
isNaN
isFinite
assert
sizeof
load
store
do/loopDo
do/loopDoInDo
[program.exports]

View File

@ -46,6 +46,9 @@
isNaN
isFinite
assert
sizeof
load
store
export/add
export/sub
export/a

View File

@ -68,6 +68,9 @@
isNaN
isFinite
assert
sizeof
load
store
if/ifThenElse
if/ifThen
if/ifThenElseBlock

View File

@ -60,6 +60,9 @@
isNaN
isFinite
assert
sizeof
load
store
export/add
export/sub
export/a

View File

@ -161,6 +161,9 @@
isNaN
isFinite
assert
sizeof
load
store
[program.exports]
;)

View File

@ -62,6 +62,9 @@
isNaN
isFinite
assert
sizeof
load
store
export/add
export/sub
export/a

View File

@ -167,6 +167,9 @@
isNaN
isFinite
assert
sizeof
load
store
switch/doSwitch
switch/doSwitchDefaultFirst
switch/doSwitchDefaultOmitted

View File

@ -655,6 +655,9 @@
isNaN
isFinite
assert
sizeof
load
store
unary/i
unary/I
unary/f

View File

@ -86,6 +86,9 @@
isNaN
isFinite
assert
sizeof
load
store
while/loopWhile
while/loopWhileInWhile
[program.exports]