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 // 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. */ /** 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. */ /** 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. */ /** 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. */ /** 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. */ /** 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. */ /** 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. */ /** 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`. */ /** 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. */ /** 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`. */ /** 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`. */ /** 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. */ /** 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. */ /** 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. */ /** 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. */ /** 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. */ /** Returns the current memory size in units of pages. One page is 64kb. */
declare function current_memory(): i32; declare function current_memory(): i32;
@ -76,14 +76,14 @@ declare function store<T>(offset: usize, value: T): void;
declare function sizeof<T>(): usize; declare function sizeof<T>(): usize;
/** NaN (not a number) as a 32-bit or 64-bit float depending on context. */ /** 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. */ /** 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. */ /** 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. */ /** 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`. */ /** Traps if the specified value is `false`. */
declare function assert(isTrue: bool): void; declare function assert(isTrue: bool): void;

View File

@ -88,6 +88,8 @@ export class Options {
noEmit: bool = false; noEmit: bool = false;
/** If true, compiles everything instead of just reachable code. */ /** If true, compiles everything instead of just reachable code. */
noTreeShaking: bool = false; noTreeShaking: bool = false;
/** If true, replaces assertions with nops. */
noDebug: bool = false;
} }
const enum ConversionKind { const enum ConversionKind {
@ -1398,16 +1400,73 @@ export class Compiler extends DiagnosticEmitter {
const functionPrototype: FunctionPrototype = <FunctionPrototype>element; const functionPrototype: FunctionPrototype = <FunctionPrototype>element;
let functionInstance: Function | null = null; let functionInstance: Function | null = null;
if (functionPrototype.isBuiltin) { if (functionPrototype.isBuiltin) {
const k: i32 = expression.typeArguments.length;
const resolvedTypeArguments: Type[] = new Array(k);
sb.length = 0; sb.length = 0;
for (let i: i32 = 0, k: i32 = expression.typeArguments.length; i < k; ++i) { for (let i: i32 = 0; i < k; ++i) {
let type: Type | null = this.program.resolveType(expression.typeArguments[i], this.currentFunction.contextualTypeArguments, true); // reports let resolvedType: Type | null = this.program.resolveType(expression.typeArguments[i], this.currentFunction.contextualTypeArguments, true); // reports
if (!type) if (!resolvedType)
return this.module.createUnreachable(); return this.module.createUnreachable();
sb.push(type.toString()); resolvedTypeArguments[i] = resolvedType;
sb.push(resolvedType.toString());
} }
functionInstance = <Function | null>functionPrototype.instances.get(sb.join(",")); functionInstance = <Function | null>functionPrototype.instances.get(sb.join(","));
if (!functionInstance) { 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 { } else {
// TODO: infer type arguments from parameter types if omitted // TODO: infer type arguments from parameter types if omitted
@ -1458,8 +1517,8 @@ export class Compiler extends DiagnosticEmitter {
this.currentType = functionInstance.returnType; this.currentType = functionInstance.returnType;
let tempLocal: Local;
if (functionInstance.isBuiltin) { if (functionInstance.isBuiltin) {
let tempLocal: Local;
switch (functionInstance.template.internalName) { switch (functionInstance.template.internalName) {
case "clz": // i32/i64.clz case "clz": // i32/i64.clz
@ -1570,11 +1629,6 @@ export class Compiler extends DiagnosticEmitter {
case "unreachable": case "unreachable":
return this.module.createUnreachable(); 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 case "isNaN": // value != value
if (functionInstance.typeArguments[0] == Type.f64) { if (functionInstance.typeArguments[0] == Type.f64) {
tempLocal = this.currentFunction.addLocal(Type.f64); tempLocal = this.currentFunction.addLocal(Type.f64);
@ -1626,10 +1680,12 @@ export class Compiler extends DiagnosticEmitter {
break; break;
case "assert": case "assert":
return this.module.createIf( return this.options.noDebug
this.module.createUnary(UnaryOp.EqzI32, operands[0]), ? this.module.createNop()
this.module.createUnreachable() : this.module.createIf(
); this.module.createUnary(UnaryOp.EqzI32, operands[0]),
this.module.createUnreachable()
);
} }
this.error(DiagnosticCode.Operation_not_supported, reportNode.range); this.error(DiagnosticCode.Operation_not_supported, reportNode.range);
return this.module.createUnreachable(); 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 f64 = number;
declare type bool = boolean; declare type bool = boolean;
// Raw memory access (here: Binaryen memory, T=u8) // Raw memory access (here: Binaryen memory)
declare function store<T>(ptr: usize, val: u8): void; declare function store<T = u8>(ptr: usize, val: T): void;
declare function load<T>(ptr: usize): u8; 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, "sqrt", genericFloat);
addGenericUnaryBuiltin(program, "trunc", genericFloat); addGenericUnaryBuiltin(program, "trunc", genericFloat);
addBuiltin(program, "current_memory", [], usize); addSimpleBuiltin(program, "current_memory", [], usize);
addBuiltin(program, "grow_memory", [ usize ], usize); addSimpleBuiltin(program, "grow_memory", [ usize ], usize);
addBuiltin(program, "unreachable", [], Type.void); addSimpleBuiltin(program, "unreachable", [], Type.void);
addGenericUnaryTestBuiltin(program, "isNaN", genericFloat); addGenericUnaryTestBuiltin(program, "isNaN", genericFloat);
addGenericUnaryTestBuiltin(program, "isFinite", genericFloat); addGenericUnaryTestBuiltin(program, "isFinite", genericFloat);
addBuiltin(program, "assert", [ Type.bool ], Type.void); addSimpleBuiltin(program, "assert", [ Type.bool ], Type.void);
// TODO: load, store, sizeof addGenericAnyBuiltin(program, "sizeof");
// sizeof, for example, has varying Ts but really shouldn't provide an instance for each class 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); let prototype: FunctionPrototype = new FunctionPrototype(program, name, null, null);
prototype.isGeneric = false; prototype.isGeneric = false;
prototype.isBuiltin = true; prototype.isBuiltin = true;
@ -1173,6 +1175,7 @@ function addBuiltin(program: Program, name: string, parameterTypes: Type[], retu
program.elements.set(name, prototype); 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 { function addGenericUnaryBuiltin(program: Program, name: string, types: Type[]): void {
let prototype: FunctionPrototype = new FunctionPrototype(program, name, null, null); let prototype: FunctionPrototype = new FunctionPrototype(program, name, null, null);
prototype.isGeneric = true; prototype.isGeneric = true;
@ -1184,6 +1187,7 @@ function addGenericUnaryBuiltin(program: Program, name: string, types: Type[]):
program.elements.set(name, prototype); 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 { function addGenericBinaryBuiltin(program: Program, name: string, types: Type[]): void {
let prototype: FunctionPrototype = new FunctionPrototype(program, name, null, null); let prototype: FunctionPrototype = new FunctionPrototype(program, name, null, null);
prototype.isGeneric = true; prototype.isGeneric = true;
@ -1195,6 +1199,7 @@ function addGenericBinaryBuiltin(program: Program, name: string, types: Type[]):
program.elements.set(name, prototype); program.elements.set(name, prototype);
} }
/** Adds a generic unary builtin that alwways returns `bool`. */
function addGenericUnaryTestBuiltin(program: Program, name: string, types: Type[]): void { function addGenericUnaryTestBuiltin(program: Program, name: string, types: Type[]): void {
let prototype: FunctionPrototype = new FunctionPrototype(program, name, null, null); let prototype: FunctionPrototype = new FunctionPrototype(program, name, null, null);
prototype.isGeneric = true; prototype.isGeneric = true;
@ -1205,3 +1210,12 @@ function addGenericUnaryTestBuiltin(program: Program, name: string, types: Type[
} }
program.elements.set(name, prototype); 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; kind: TypeKind;
size: i32; size: i32;
byteSize: i32;
classType: Class | null; classType: Class | null;
nullable: bool = false; nullable: bool = false;
nullableType: Type | null = null; // cached, of this type nullableType: Type | null = null; // cached, of this type
@ -36,6 +37,7 @@ export class Type {
constructor(kind: TypeKind, size: i32) { constructor(kind: TypeKind, size: i32) {
this.kind = kind; this.kind = kind;
this.size = size; this.size = size;
this.byteSize = Math.ceil(<f64>size / 8);
this.classType = null; this.classType = null;
} }

View File

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

View File

@ -104,3 +104,20 @@ s = grow_memory(1);
if (0) unreachable(); if (0) unreachable();
assert(true); 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) (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 isNaN
isFinite isFinite
assert assert
sizeof
load
store
builtins/b builtins/b
builtins/i builtins/i
builtins/I builtins/I

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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