Skip to content

Commit 8c2e9cc

Browse files
authored
feat: Use integer power for integer types (#1146)
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`.
1 parent 085aa7f commit 8c2e9cc

19 files changed

+2311
-3197
lines changed

Diff for: src/common.ts

+2
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,8 @@ export namespace CommonNames {
214214
export const trace = "trace";
215215
export const seed = "seed";
216216
export const pow = "pow";
217+
export const ipow32 = "ipow32";
218+
export const ipow64 = "ipow64";
217219
export const mod = "mod";
218220
export const alloc = "__alloc";
219221
export const realloc = "__realloc";

Diff for: src/compiler.ts

+182-64
Original file line numberDiff line numberDiff line change
@@ -3855,6 +3855,8 @@ export class Compiler extends DiagnosticEmitter {
38553855
private f64ModInstance: Function | null = null;
38563856
private f32PowInstance: Function | null = null;
38573857
private f64PowInstance: Function | null = null;
3858+
private i32PowInstance: Function | null = null;
3859+
private i64PowInstance: Function | null = null;
38583860

38593861
private compileBinaryExpression(
38603862
expression: BinaryExpression,
@@ -4786,82 +4788,198 @@ export class Compiler extends DiagnosticEmitter {
47864788
);
47874789
return this.module.unreachable();
47884790
}
4791+
if (compound) {
4792+
leftExpr = this.ensureSmallIntegerWrap(leftExpr, leftType);
4793+
rightExpr = this.compileExpression(right, leftType, Constraints.CONV_IMPLICIT);
4794+
rightType = commonType = this.currentType;
4795+
} else {
4796+
rightExpr = this.compileExpression(right, leftType);
4797+
rightType = this.currentType;
4798+
commonType = Type.commonDenominator(leftType, rightType, false);
4799+
if (commonType) {
4800+
leftExpr = this.convertExpression(leftExpr,
4801+
leftType, commonType,
4802+
false, true, // !
4803+
left
4804+
);
4805+
leftType = commonType;
4806+
rightExpr = this.convertExpression(rightExpr,
4807+
rightType, commonType,
4808+
false, true, // !
4809+
right
4810+
);
4811+
rightType = commonType;
4812+
} else {
4813+
this.error(
4814+
DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2,
4815+
expression.range, "**", leftType.toString(), rightType.toString()
4816+
);
4817+
this.currentType = contextualType;
4818+
return module.unreachable();
4819+
}
4820+
}
47894821

4790-
let targetType = leftType;
47914822
let instance: Function | null;
4792-
4793-
// Mathf.pow if lhs is f32 (result is f32)
4794-
if (this.currentType.kind == TypeKind.F32) {
4795-
rightExpr = this.compileExpression(right, Type.f32, Constraints.CONV_IMPLICIT);
4796-
rightType = this.currentType;
4797-
instance = this.f32PowInstance;
4798-
if (!instance) {
4799-
let namespace = this.program.lookupGlobal(CommonNames.Mathf);
4800-
if (!namespace) {
4801-
this.error(
4802-
DiagnosticCode.Cannot_find_name_0,
4803-
expression.range, "Mathf"
4804-
);
4823+
switch (commonType.kind) {
4824+
case TypeKind.BOOL: {
4825+
expr = module.select(
4826+
module.i32(1),
4827+
module.binary(BinaryOp.EqI32, rightExpr, module.i32(0)),
4828+
leftExpr
4829+
);
4830+
break;
4831+
}
4832+
case TypeKind.I8:
4833+
case TypeKind.U8:
4834+
case TypeKind.I16:
4835+
case TypeKind.U16:
4836+
case TypeKind.I32:
4837+
case TypeKind.U32: {
4838+
instance = this.i32PowInstance;
4839+
if (!instance) {
4840+
let prototype = this.program.lookupGlobal(CommonNames.ipow32);
4841+
if (!prototype) {
4842+
this.error(
4843+
DiagnosticCode.Cannot_find_name_0,
4844+
expression.range, "ipow32"
4845+
);
4846+
expr = module.unreachable();
4847+
break;
4848+
}
4849+
assert(prototype.kind == ElementKind.FUNCTION_PROTOTYPE);
4850+
this.i32PowInstance = instance = this.resolver.resolveFunction(<FunctionPrototype>prototype, null);
4851+
}
4852+
if (!instance || !this.compileFunction(instance)) {
48054853
expr = module.unreachable();
4806-
break;
4854+
} else {
4855+
expr = this.makeCallDirect(instance, [ leftExpr, rightExpr ], expression);
4856+
if (commonType.size != 32) {
4857+
expr = this.ensureSmallIntegerWrap(expr, commonType);
4858+
}
48074859
}
4808-
let namespaceMembers = namespace.members;
4809-
if (!namespaceMembers || !namespaceMembers.has(CommonNames.pow)) {
4810-
this.error(
4811-
DiagnosticCode.Cannot_find_name_0,
4812-
expression.range, "Mathf.pow"
4813-
);
4860+
break;
4861+
}
4862+
case TypeKind.I64:
4863+
case TypeKind.U64: {
4864+
instance = this.i64PowInstance;
4865+
if (!instance) {
4866+
let prototype = this.program.lookupGlobal(CommonNames.ipow64);
4867+
if (!prototype) {
4868+
this.error(
4869+
DiagnosticCode.Cannot_find_name_0,
4870+
expression.range, "ipow64"
4871+
);
4872+
expr = module.unreachable();
4873+
break;
4874+
}
4875+
assert(prototype.kind == ElementKind.FUNCTION_PROTOTYPE);
4876+
this.i64PowInstance = instance = this.resolver.resolveFunction(<FunctionPrototype>prototype, null);
4877+
}
4878+
if (!instance || !this.compileFunction(instance)) {
48144879
expr = module.unreachable();
4815-
break;
4880+
} else {
4881+
expr = this.makeCallDirect(instance, [ leftExpr, rightExpr ], expression);
48164882
}
4817-
let prototype = assert(namespaceMembers.get(CommonNames.pow));
4818-
assert(prototype.kind == ElementKind.FUNCTION_PROTOTYPE);
4819-
this.f32PowInstance = instance = this.resolver.resolveFunction(<FunctionPrototype>prototype, null);
4883+
break;
48204884
}
4821-
4822-
// Math.pow otherwise (result is f64)
4823-
// TODO: should the result be converted back?
4824-
} else {
4825-
leftExpr = this.convertExpression(leftExpr,
4826-
this.currentType, Type.f64,
4827-
false, false,
4828-
left
4829-
);
4830-
leftType = this.currentType;
4831-
rightExpr = this.compileExpression(right, Type.f64, Constraints.CONV_IMPLICIT);
4832-
rightType = this.currentType;
4833-
instance = this.f64PowInstance;
4834-
if (!instance) {
4835-
let namespace = this.program.lookupGlobal(CommonNames.Math);
4836-
if (!namespace) {
4837-
this.error(
4838-
DiagnosticCode.Cannot_find_name_0,
4839-
expression.range, "Math"
4840-
);
4885+
case TypeKind.ISIZE:
4886+
case TypeKind.USIZE: {
4887+
let isWasm64 = this.options.isWasm64;
4888+
instance = isWasm64 ? this.i64PowInstance : this.i32PowInstance;
4889+
if (!instance) {
4890+
let prototype = this.program.lookupGlobal(isWasm64 ? CommonNames.ipow64 : CommonNames.ipow32);
4891+
if (!prototype) {
4892+
this.error(
4893+
DiagnosticCode.Cannot_find_name_0,
4894+
expression.range, isWasm64 ? "ipow64" : "ipow32"
4895+
);
4896+
expr = module.unreachable();
4897+
break;
4898+
}
4899+
assert(prototype.kind == ElementKind.FUNCTION_PROTOTYPE);
4900+
instance = this.resolver.resolveFunction(<FunctionPrototype>prototype, null);
4901+
if (isWasm64) {
4902+
this.i64PowInstance = instance;
4903+
} else {
4904+
this.i32PowInstance = instance;
4905+
}
4906+
}
4907+
if (!instance || !this.compileFunction(instance)) {
48414908
expr = module.unreachable();
4842-
break;
4909+
} else {
4910+
expr = this.makeCallDirect(instance, [ leftExpr, rightExpr ], expression);
48434911
}
4844-
let namespaceMembers = namespace.members;
4845-
if (!namespaceMembers || !namespaceMembers.has(CommonNames.pow)) {
4846-
this.error(
4847-
DiagnosticCode.Cannot_find_name_0,
4848-
expression.range, "Math.pow"
4849-
);
4912+
break;
4913+
}
4914+
case TypeKind.F32: {
4915+
instance = this.f32PowInstance;
4916+
if (!instance) {
4917+
let namespace = this.program.lookupGlobal(CommonNames.Mathf);
4918+
if (!namespace) {
4919+
this.error(
4920+
DiagnosticCode.Cannot_find_name_0,
4921+
expression.range, "Mathf"
4922+
);
4923+
expr = module.unreachable();
4924+
break;
4925+
}
4926+
let namespaceMembers = namespace.members;
4927+
if (!namespaceMembers || !namespaceMembers.has(CommonNames.pow)) {
4928+
this.error(
4929+
DiagnosticCode.Cannot_find_name_0,
4930+
expression.range, "Mathf.pow"
4931+
);
4932+
expr = module.unreachable();
4933+
break;
4934+
}
4935+
let prototype = assert(namespaceMembers.get(CommonNames.pow));
4936+
assert(prototype.kind == ElementKind.FUNCTION_PROTOTYPE);
4937+
this.f32PowInstance = instance = this.resolver.resolveFunction(<FunctionPrototype>prototype, null);
4938+
}
4939+
if (!instance || !this.compileFunction(instance)) {
48504940
expr = module.unreachable();
4851-
break;
4941+
} else {
4942+
expr = this.makeCallDirect(instance, [ leftExpr, rightExpr ], expression);
48524943
}
4853-
let prototype = assert(namespaceMembers.get(CommonNames.pow));
4854-
assert(prototype.kind == ElementKind.FUNCTION_PROTOTYPE);
4855-
this.f64PowInstance = instance = this.resolver.resolveFunction(<FunctionPrototype>prototype, null);
4944+
break;
48564945
}
4857-
}
4858-
if (!instance || !this.compileFunction(instance)) {
4859-
expr = module.unreachable();
4860-
} else {
4861-
expr = this.makeCallDirect(instance, [ leftExpr, rightExpr ], expression);
4862-
if (compound && targetType != this.currentType) {
4863-
// this yields a proper error if target is i32 for example
4864-
expr = this.convertExpression(expr, this.currentType, targetType, false, false, expression);
4946+
// Math.pow otherwise (result is f64)
4947+
case TypeKind.F64: {
4948+
instance = this.f64PowInstance;
4949+
if (!instance) {
4950+
let namespace = this.program.lookupGlobal(CommonNames.Math);
4951+
if (!namespace) {
4952+
this.error(
4953+
DiagnosticCode.Cannot_find_name_0,
4954+
expression.range, "Math"
4955+
);
4956+
expr = module.unreachable();
4957+
break;
4958+
}
4959+
let namespaceMembers = namespace.members;
4960+
if (!namespaceMembers || !namespaceMembers.has(CommonNames.pow)) {
4961+
this.error(
4962+
DiagnosticCode.Cannot_find_name_0,
4963+
expression.range, "Math.pow"
4964+
);
4965+
expr = module.unreachable();
4966+
break;
4967+
}
4968+
let prototype = assert(namespaceMembers.get(CommonNames.pow));
4969+
assert(prototype.kind == ElementKind.FUNCTION_PROTOTYPE);
4970+
this.f64PowInstance = instance = this.resolver.resolveFunction(<FunctionPrototype>prototype, null);
4971+
}
4972+
if (!instance || !this.compileFunction(instance)) {
4973+
expr = module.unreachable();
4974+
} else {
4975+
expr = this.makeCallDirect(instance, [ leftExpr, rightExpr ], expression);
4976+
}
4977+
break;
4978+
}
4979+
default: {
4980+
assert(false);
4981+
expr = module.unreachable();
4982+
break;
48654983
}
48664984
}
48674985
break;

Diff for: src/glue/js/i64.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ declare function i64_high(value: i64): i32;
1515
declare function i64_add(left: i64, right: i64): i64;
1616
declare function i64_sub(left: i64, right: i64): i64;
1717
declare function i64_mul(left: i64, right: i64): i64;
18+
declare function i64_pow(left: i64, right: i64): i64;
1819
declare function i64_div(left: i64, right: i64): i64;
1920
declare function i64_div_u(left: i64, right: i64): i64;
2021
declare function i64_rem(left: i64, right: i64): i64;

Diff for: src/glue/js/i64.js

+28-3
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77

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

10-
global.i64_zero = Long.ZERO;
11-
12-
global.i64_one = Long.ONE;
10+
global.i64_zero = Long.ZERO;
11+
global.i64_one = Long.ONE;
12+
global.i64_neg_one = Long.fromInt(-1);
1313

1414
global.i64_new = function(lo, hi) {
1515
return Long.fromBits(lo, hi);
@@ -35,6 +35,31 @@ global.i64_mul = function(left, right) {
3535
return left.mul(right);
3636
};
3737

38+
global.i64_pow = function(left, right) {
39+
var rightLo = right.low;
40+
var rightHi = right.high;
41+
if (rightHi <= 0) {
42+
if (rightHi < 0) {
43+
if (left.eq(global.i64_neg_one)) {
44+
return rightLo & 1 ? left : Long.ONE;
45+
}
46+
return left.eq(Long.ONE) ? left : Long.ZERO;
47+
}
48+
if (rightLo == 0) return Long.ONE;
49+
if (rightLo == 1) return left;
50+
if (rightLo == 2) return left.mul(left);
51+
}
52+
var result = Long.ONE;
53+
while (rightLo | rightHi) {
54+
if (rightLo & 1) result = result.mul(left);
55+
right = right.shru(1);
56+
left = left.mul(left);
57+
rightLo = right.low;
58+
rightHi = right.high;
59+
}
60+
return result;
61+
};
62+
3863
global.i64_div = function(left, right) {
3964
return left.div(right);
4065
};

Diff for: src/glue/wasm/i64.ts

+21
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
// @ts-ignore: decorator
1111
@global const i64_one: i64 = 1;
1212

13+
// @ts-ignore: decorator
14+
@global const i64_neg_one: i64 = -1;
15+
1316
// @ts-ignore: decorator
1417
@global
1518
function i64_new(lo: i32, hi: i32 = 0): i64 {
@@ -46,6 +49,24 @@ function i64_mul(left: i64, right: i64): i64 {
4649
return left * right;
4750
}
4851

52+
// @ts-ignore: decorator
53+
@global
54+
function i64_pow(left: i64, right: i64): i64 {
55+
if (right <= 0) {
56+
if (left == -1) return select<i64>(-1, 1, right & 1);
57+
return i64(right == 0) | i64(left == 1);
58+
}
59+
if (right == 1) return left;
60+
if (right == 2) return left * left;
61+
var result: i64 = 1;
62+
while (right) {
63+
if (right & 1) result *= left;
64+
right >>>= 1;
65+
left *= left;
66+
}
67+
return result;
68+
}
69+
4970
// @ts-ignore: decorator
5071
@global
5172
function i64_div(left: i64, right: i64): i64 {

0 commit comments

Comments
 (0)