Skip to content

Commit

Permalink
feat: Use integer power for integer types (#1146)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: The result of a `**` binary operation is now the common denominator integer if both operands are integers. Previously, the result was a float as if calling `Math/f.pow`.
  • Loading branch information
MaxGraey authored Jun 13, 2020
1 parent 085aa7f commit 8c2e9cc
Show file tree
Hide file tree
Showing 19 changed files with 2,311 additions and 3,197 deletions.
2 changes: 2 additions & 0 deletions src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,8 @@ export namespace CommonNames {
export const trace = "trace";
export const seed = "seed";
export const pow = "pow";
export const ipow32 = "ipow32";
export const ipow64 = "ipow64";
export const mod = "mod";
export const alloc = "__alloc";
export const realloc = "__realloc";
Expand Down
246 changes: 182 additions & 64 deletions src/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3855,6 +3855,8 @@ export class Compiler extends DiagnosticEmitter {
private f64ModInstance: Function | null = null;
private f32PowInstance: Function | null = null;
private f64PowInstance: Function | null = null;
private i32PowInstance: Function | null = null;
private i64PowInstance: Function | null = null;

private compileBinaryExpression(
expression: BinaryExpression,
Expand Down Expand Up @@ -4786,82 +4788,198 @@ export class Compiler extends DiagnosticEmitter {
);
return this.module.unreachable();
}
if (compound) {
leftExpr = this.ensureSmallIntegerWrap(leftExpr, leftType);
rightExpr = this.compileExpression(right, leftType, Constraints.CONV_IMPLICIT);
rightType = commonType = this.currentType;
} else {
rightExpr = this.compileExpression(right, leftType);
rightType = this.currentType;
commonType = Type.commonDenominator(leftType, rightType, false);
if (commonType) {
leftExpr = this.convertExpression(leftExpr,
leftType, commonType,
false, true, // !
left
);
leftType = commonType;
rightExpr = this.convertExpression(rightExpr,
rightType, commonType,
false, true, // !
right
);
rightType = commonType;
} else {
this.error(
DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2,
expression.range, "**", leftType.toString(), rightType.toString()
);
this.currentType = contextualType;
return module.unreachable();
}
}

let targetType = leftType;
let instance: Function | null;

// Mathf.pow if lhs is f32 (result is f32)
if (this.currentType.kind == TypeKind.F32) {
rightExpr = this.compileExpression(right, Type.f32, Constraints.CONV_IMPLICIT);
rightType = this.currentType;
instance = this.f32PowInstance;
if (!instance) {
let namespace = this.program.lookupGlobal(CommonNames.Mathf);
if (!namespace) {
this.error(
DiagnosticCode.Cannot_find_name_0,
expression.range, "Mathf"
);
switch (commonType.kind) {
case TypeKind.BOOL: {
expr = module.select(
module.i32(1),
module.binary(BinaryOp.EqI32, rightExpr, module.i32(0)),
leftExpr
);
break;
}
case TypeKind.I8:
case TypeKind.U8:
case TypeKind.I16:
case TypeKind.U16:
case TypeKind.I32:
case TypeKind.U32: {
instance = this.i32PowInstance;
if (!instance) {
let prototype = this.program.lookupGlobal(CommonNames.ipow32);
if (!prototype) {
this.error(
DiagnosticCode.Cannot_find_name_0,
expression.range, "ipow32"
);
expr = module.unreachable();
break;
}
assert(prototype.kind == ElementKind.FUNCTION_PROTOTYPE);
this.i32PowInstance = instance = this.resolver.resolveFunction(<FunctionPrototype>prototype, null);
}
if (!instance || !this.compileFunction(instance)) {
expr = module.unreachable();
break;
} else {
expr = this.makeCallDirect(instance, [ leftExpr, rightExpr ], expression);
if (commonType.size != 32) {
expr = this.ensureSmallIntegerWrap(expr, commonType);
}
}
let namespaceMembers = namespace.members;
if (!namespaceMembers || !namespaceMembers.has(CommonNames.pow)) {
this.error(
DiagnosticCode.Cannot_find_name_0,
expression.range, "Mathf.pow"
);
break;
}
case TypeKind.I64:
case TypeKind.U64: {
instance = this.i64PowInstance;
if (!instance) {
let prototype = this.program.lookupGlobal(CommonNames.ipow64);
if (!prototype) {
this.error(
DiagnosticCode.Cannot_find_name_0,
expression.range, "ipow64"
);
expr = module.unreachable();
break;
}
assert(prototype.kind == ElementKind.FUNCTION_PROTOTYPE);
this.i64PowInstance = instance = this.resolver.resolveFunction(<FunctionPrototype>prototype, null);
}
if (!instance || !this.compileFunction(instance)) {
expr = module.unreachable();
break;
} else {
expr = this.makeCallDirect(instance, [ leftExpr, rightExpr ], expression);
}
let prototype = assert(namespaceMembers.get(CommonNames.pow));
assert(prototype.kind == ElementKind.FUNCTION_PROTOTYPE);
this.f32PowInstance = instance = this.resolver.resolveFunction(<FunctionPrototype>prototype, null);
break;
}

// Math.pow otherwise (result is f64)
// TODO: should the result be converted back?
} else {
leftExpr = this.convertExpression(leftExpr,
this.currentType, Type.f64,
false, false,
left
);
leftType = this.currentType;
rightExpr = this.compileExpression(right, Type.f64, Constraints.CONV_IMPLICIT);
rightType = this.currentType;
instance = this.f64PowInstance;
if (!instance) {
let namespace = this.program.lookupGlobal(CommonNames.Math);
if (!namespace) {
this.error(
DiagnosticCode.Cannot_find_name_0,
expression.range, "Math"
);
case TypeKind.ISIZE:
case TypeKind.USIZE: {
let isWasm64 = this.options.isWasm64;
instance = isWasm64 ? this.i64PowInstance : this.i32PowInstance;
if (!instance) {
let prototype = this.program.lookupGlobal(isWasm64 ? CommonNames.ipow64 : CommonNames.ipow32);
if (!prototype) {
this.error(
DiagnosticCode.Cannot_find_name_0,
expression.range, isWasm64 ? "ipow64" : "ipow32"
);
expr = module.unreachable();
break;
}
assert(prototype.kind == ElementKind.FUNCTION_PROTOTYPE);
instance = this.resolver.resolveFunction(<FunctionPrototype>prototype, null);
if (isWasm64) {
this.i64PowInstance = instance;
} else {
this.i32PowInstance = instance;
}
}
if (!instance || !this.compileFunction(instance)) {
expr = module.unreachable();
break;
} else {
expr = this.makeCallDirect(instance, [ leftExpr, rightExpr ], expression);
}
let namespaceMembers = namespace.members;
if (!namespaceMembers || !namespaceMembers.has(CommonNames.pow)) {
this.error(
DiagnosticCode.Cannot_find_name_0,
expression.range, "Math.pow"
);
break;
}
case TypeKind.F32: {
instance = this.f32PowInstance;
if (!instance) {
let namespace = this.program.lookupGlobal(CommonNames.Mathf);
if (!namespace) {
this.error(
DiagnosticCode.Cannot_find_name_0,
expression.range, "Mathf"
);
expr = module.unreachable();
break;
}
let namespaceMembers = namespace.members;
if (!namespaceMembers || !namespaceMembers.has(CommonNames.pow)) {
this.error(
DiagnosticCode.Cannot_find_name_0,
expression.range, "Mathf.pow"
);
expr = module.unreachable();
break;
}
let prototype = assert(namespaceMembers.get(CommonNames.pow));
assert(prototype.kind == ElementKind.FUNCTION_PROTOTYPE);
this.f32PowInstance = instance = this.resolver.resolveFunction(<FunctionPrototype>prototype, null);
}
if (!instance || !this.compileFunction(instance)) {
expr = module.unreachable();
break;
} else {
expr = this.makeCallDirect(instance, [ leftExpr, rightExpr ], expression);
}
let prototype = assert(namespaceMembers.get(CommonNames.pow));
assert(prototype.kind == ElementKind.FUNCTION_PROTOTYPE);
this.f64PowInstance = instance = this.resolver.resolveFunction(<FunctionPrototype>prototype, null);
break;
}
}
if (!instance || !this.compileFunction(instance)) {
expr = module.unreachable();
} else {
expr = this.makeCallDirect(instance, [ leftExpr, rightExpr ], expression);
if (compound && targetType != this.currentType) {
// this yields a proper error if target is i32 for example
expr = this.convertExpression(expr, this.currentType, targetType, false, false, expression);
// Math.pow otherwise (result is f64)
case TypeKind.F64: {
instance = this.f64PowInstance;
if (!instance) {
let namespace = this.program.lookupGlobal(CommonNames.Math);
if (!namespace) {
this.error(
DiagnosticCode.Cannot_find_name_0,
expression.range, "Math"
);
expr = module.unreachable();
break;
}
let namespaceMembers = namespace.members;
if (!namespaceMembers || !namespaceMembers.has(CommonNames.pow)) {
this.error(
DiagnosticCode.Cannot_find_name_0,
expression.range, "Math.pow"
);
expr = module.unreachable();
break;
}
let prototype = assert(namespaceMembers.get(CommonNames.pow));
assert(prototype.kind == ElementKind.FUNCTION_PROTOTYPE);
this.f64PowInstance = instance = this.resolver.resolveFunction(<FunctionPrototype>prototype, null);
}
if (!instance || !this.compileFunction(instance)) {
expr = module.unreachable();
} else {
expr = this.makeCallDirect(instance, [ leftExpr, rightExpr ], expression);
}
break;
}
default: {
assert(false);
expr = module.unreachable();
break;
}
}
break;
Expand Down
1 change: 1 addition & 0 deletions src/glue/js/i64.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ declare function i64_high(value: i64): i32;
declare function i64_add(left: i64, right: i64): i64;
declare function i64_sub(left: i64, right: i64): i64;
declare function i64_mul(left: i64, right: i64): i64;
declare function i64_pow(left: i64, right: i64): i64;
declare function i64_div(left: i64, right: i64): i64;
declare function i64_div_u(left: i64, right: i64): i64;
declare function i64_rem(left: i64, right: i64): i64;
Expand Down
31 changes: 28 additions & 3 deletions src/glue/js/i64.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@

const Long = global.Long || require("long");

global.i64_zero = Long.ZERO;

global.i64_one = Long.ONE;
global.i64_zero = Long.ZERO;
global.i64_one = Long.ONE;
global.i64_neg_one = Long.fromInt(-1);

global.i64_new = function(lo, hi) {
return Long.fromBits(lo, hi);
Expand All @@ -35,6 +35,31 @@ global.i64_mul = function(left, right) {
return left.mul(right);
};

global.i64_pow = function(left, right) {
var rightLo = right.low;
var rightHi = right.high;
if (rightHi <= 0) {
if (rightHi < 0) {
if (left.eq(global.i64_neg_one)) {
return rightLo & 1 ? left : Long.ONE;
}
return left.eq(Long.ONE) ? left : Long.ZERO;
}
if (rightLo == 0) return Long.ONE;
if (rightLo == 1) return left;
if (rightLo == 2) return left.mul(left);
}
var result = Long.ONE;
while (rightLo | rightHi) {
if (rightLo & 1) result = result.mul(left);
right = right.shru(1);
left = left.mul(left);
rightLo = right.low;
rightHi = right.high;
}
return result;
};

global.i64_div = function(left, right) {
return left.div(right);
};
Expand Down
21 changes: 21 additions & 0 deletions src/glue/wasm/i64.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
// @ts-ignore: decorator
@global const i64_one: i64 = 1;

// @ts-ignore: decorator
@global const i64_neg_one: i64 = -1;

// @ts-ignore: decorator
@global
function i64_new(lo: i32, hi: i32 = 0): i64 {
Expand Down Expand Up @@ -46,6 +49,24 @@ function i64_mul(left: i64, right: i64): i64 {
return left * right;
}

// @ts-ignore: decorator
@global
function i64_pow(left: i64, right: i64): i64 {
if (right <= 0) {
if (left == -1) return select<i64>(-1, 1, right & 1);
return i64(right == 0) | i64(left == 1);
}
if (right == 1) return left;
if (right == 2) return left * left;
var result: i64 = 1;
while (right) {
if (right & 1) result *= left;
right >>>= 1;
left *= left;
}
return result;
}

// @ts-ignore: decorator
@global
function i64_div(left: i64, right: i64): i64 {
Expand Down
Loading

0 comments on commit 8c2e9cc

Please sign in to comment.