diff --git a/src/llvm/CompilerIntrinsics.js b/src/llvm/CompilerIntrinsics.js index 838ac324ff..b30f4c25db 100644 --- a/src/llvm/CompilerIntrinsics.js +++ b/src/llvm/CompilerIntrinsics.js @@ -22,10 +22,11 @@ import { llvmContext } from "./llvm-context.js"; export class Intrinsics { +_module: Module; - _memcpy: ?LLVMFunction; - _memcmp: ?LLVMFunction; _stringType: ?StructType; _uint32Type: ?StructType; + _memcpy: ?LLVMFunction; + _memcmp: ?LLVMFunction; + _pow: ?LLVMFunction; constructor(module: Module) { this._module = module; @@ -92,4 +93,14 @@ export class Intrinsics { this._memcmp = memcmp; return memcmp; } + + get pow(): LLVMFunction { + if (this._pow) return this._pow; + let returnType = LLVMType.getDoubleTy(llvmContext); + let args = [LLVMType.getDoubleTy(llvmContext), LLVMType.getDoubleTy(llvmContext)]; + let fnType = FunctionType.get(returnType, args, false); + let pow = LLVMFunction.create(fnType, LinkageTypes.ExternalLinkage, "llvm.pow.f64", this._module); + this._pow = pow; + return pow; + } } diff --git a/src/llvm/builders/Expression.js b/src/llvm/builders/Expression.js index 618c70e5e4..dd980bd041 100644 --- a/src/llvm/builders/Expression.js +++ b/src/llvm/builders/Expression.js @@ -10,17 +10,18 @@ /* @flow */ import type { CompilerState } from "../CompilerState.js"; -import type { BabelNodeExpression, BabelNodeBinaryExpression } from "@babel/types"; +import type { BabelNodeExpression, BabelNodeBinaryExpression, BabelNodeUnaryExpression } from "@babel/types"; import invariant from "../../invariant.js"; import { CompilerDiagnostic, FatalError } from "../../errors.js"; -import { Value as LLVMValue, IRBuilder } from "llvm-node"; -import { Value, NumberValue, IntegralValue, StringValue } from "../../values/index.js"; +import { Value as LLVMValue, Type as LLVMType, Constant, ConstantInt, IRBuilder } from "llvm-node"; +import { llvmContext } from "../llvm-context.js"; +import { Value } from "../../values/index.js"; import * as t from "@babel/types"; import { buildFromValue } from "./Value.js"; import { buildAppendString, buildCompareString, getStringPtr } from "./StringValue.js"; -import { buildToBoolean } from "./To.js"; +import { buildToBoolean, buildToInt32, buildToUint32, buildToNumber, buildToString } from "./To.js"; export function valueToExpression(value: Value): BabelNodeExpression { // Hack. We use an identifier to hold the LLVM value so that @@ -31,9 +32,123 @@ export function valueToExpression(value: Value): BabelNodeExpression { return identifier; } -function valueFromExpression(expr: BabelNodeExpression): Value { - invariant(expr.type === "Identifier" && expr.name === "LLVM_VALUE" && expr.decorators); - return (expr.decorators: Value); +function buildStrictEquality( + state: CompilerState, + x: LLVMValue, + y: LLVMValue, + comparison: "eq" | "ne", + builder: IRBuilder +) { + invariant( + x.type.equals(y.type), + "strict equality should only be done on the same type, otherwise it is always false" + ); + if (x.type.isIntegerTy(1)) { + // Boolean + return comparison === "eq" ? builder.createICmpEQ(x, y) : builder.createICmpNE(x, y); + } else if (x.type.isDoubleTy()) { + // Number + return comparison === "eq" ? builder.createFCmpOEQ(x, y) : builder.createFCmpONE(x, y); + } else if (x.type.isIntegerTy(32)) { + // Integral + return comparison === "eq" ? builder.createICmpEQ(x, y) : builder.createICmpNE(x, y); + } else if (state.intrinsics.isUint32Type(x.type)) { + // Unsigned Integral + return comparison === "eq" ? builder.createICmpEQ(x, y) : builder.createICmpNE(x, y); + } else if (state.intrinsics.isStringType(x.type)) { + // String + return buildCompareString(state, x, y, comparison, builder); + } else { + invariant(false); + } +} + +function isNumericType(state: CompilerState, type: LLVMType) { + return type.isDoubleTy() || type.isIntegerTy(32) || state.intrinsics.isUint32Type(type); +} + +function buildAbstractEquality( + state: CompilerState, + x: LLVMValue, + y: LLVMValue, + comparison: "eq" | "ne", + builder: IRBuilder +) { + // 1. If Type(x) is the same as Type(y), then + if (x.type.equals(y.type)) { + // a. Return the result of performing Strict Equality Comparison x === y. + return buildStrictEquality(state, x, y, comparison, builder); + } + + if (isNumericType(state, x.type) && isNumericType(state, y.type)) { + // If both are numbers but different numeric types, we convert them + // both to doubles for comparison since that preserves the shared precision. + return buildStrictEquality( + state, + buildToNumber(state, x, builder), + buildToNumber(state, y, builder), + comparison, + builder + ); + } + + let xIsNull = x instanceof Constant && x.isNullValue(); + let yIsNull = y instanceof Constant && y.isNullValue(); + + // 2. If x is null and y is undefined, return true. + invariant(!xIsNull || !y.type.isVoidTy(), "should have been constant folded by the interpreter"); + + // 3. If x is undefined and y is null, return true. + invariant(!x.type.isVoidTy() || !yIsNull, "should have been constant folded by the interpreter"); + + // 4. If Type(x) is Number and Type(y) is String, return the result of the comparison x == ToNumber(y). + if (isNumericType(state, x.type) && state.intrinsics.isStringType(y.type)) { + // We convert any integers to the double representation for comparison. + return buildAbstractEquality( + state, + buildToNumber(state, x, builder), + buildToNumber(state, y, builder), + comparison, + builder + ); + } + + // 5. If Type(x) is String and Type(y) is Number, return the result of the comparison ToNumber(x) == y. + if (state.intrinsics.isStringType(x.type) && isNumericType(state, y.type)) { + // We convert any integers to the double representation for comparison. + return buildAbstractEquality( + state, + buildToNumber(state, x, builder), + buildToNumber(state, y, builder), + comparison, + builder + ); + } + + // 6. If Type(x) is Boolean, return the result of the comparison ToNumber(x) == y. + if (x.type.isIntegerTy(1)) { + return buildAbstractEquality(state, buildToNumber(state, x, builder), x, comparison, builder); + } + + // 7. If Type(y) is Boolean, return the result of the comparison x == ToNumber(y). + if (y.type.isIntegerTy(1)) { + return buildAbstractEquality(state, x, buildToNumber(state, y, builder), comparison, builder); + } + + // 8. If Type(x) is either String, Number, or Symbol and Type(y) is Object, return the result of the comparison x == ToPrimitive(y). + // 9. If Type(x) is Object and Type(y) is either String, Number, or Symbol, return the result of the comparison ToPrimitive(x) == y. + invariant(false, "objects should have been resolved to primitives."); +} + +function buildAbstractRelationalComparison( + state: CompilerState, + x: LLVMValue, + y: LLVMValue, + comparison: ">" | "<" | ">=" | "<=", + builder: IRBuilder +) { + // TODO: implement + invariant(false, "TODO: implement abstract relational comparison"); } function buildFromBinaryExpression( @@ -41,103 +156,140 @@ function buildFromBinaryExpression( expr: BabelNodeBinaryExpression, builder: IRBuilder ): LLVMValue { - let leftValue = valueFromExpression(expr.left); - let rightValue = valueFromExpression(expr.right); - let left = buildFromValue(state, leftValue, builder); - let right = buildFromValue(state, rightValue, builder); - let type = leftValue.getType(); - if (type !== rightValue.getType()) { - let error = new CompilerDiagnostic( - `Cannot apply the ${expr.operator} operator to values of different types.`, - expr.loc, - "PP2000", - "FatalError" - ); - state.realm.handleError(error); - throw new FatalError(); - } + let left = buildFromExpression(state, expr.left, builder); + let right = buildFromExpression(state, expr.right, builder); switch (expr.operator) { - case "==": - case "===": { - if (type === IntegralValue) { - return builder.createICmpEQ(left, right); - } else if (type === NumberValue) { - return builder.createFCmpOEQ(left, right); - } else if (type === StringValue) { - return buildCompareString(state, left, right, "eq", builder); - } else { - let error = new CompilerDiagnostic( - `The equality operator for ${type.name} is not yet implemented for LLVM.`, - expr.loc, - "PP2000", - "FatalError" - ); - state.realm.handleError(error); - throw new FatalError(); - } - } - case "!=": - case "!==": { - if (type === IntegralValue) { - return builder.createICmpNE(left, right); - } else if (type === NumberValue) { - return builder.createFCmpONE(left, right); - } else if (type === StringValue) { - return buildCompareString(state, left, right, "ne", builder); - } else { - let error = new CompilerDiagnostic( - `The equality operator for ${type.name} is not yet implemented for LLVM.`, - expr.loc, - "PP2000", - "FatalError" - ); - state.realm.handleError(error); - throw new FatalError(); - } + case "===": + case "==": { + // The interpreter will have simplified this if they are different types. + // We still need to coerce the different numeric types, so we end up with + // the same semantics as abstract equality. + return buildAbstractEquality(state, left, right, "eq", builder); } - case "+": { - if (type === IntegralValue) { - return builder.createAdd(left, right); - } else if (type === NumberValue) { - return builder.createFAdd(left, right); - } else if (type === StringValue) { - return buildAppendString(state, left, right, builder); - } else { - let error = new CompilerDiagnostic( - `The + operator for ${type.name} is not yet implemented for LLVM.`, - expr.loc, - "PP2000", - "FatalError" - ); - state.realm.handleError(error); - throw new FatalError(); - } + case "!==": + case "!=": { + return buildAbstractEquality(state, left, right, "ne", builder); } - case "-": - case "/": - case "%": - case "*": - case "**": - case "&": - case "|": - case ">>": - case ">>>": - case "<<": - case "^": case ">": case "<": case ">=": - case "<=": + case "<=": { + return buildAbstractRelationalComparison(state, left, right, expr.operator, builder); + } + case "+": { + if (state.intrinsics.isStringType(left.type) || state.intrinsics.isStringType(right.type)) { + let leftStr = buildToString(state, left, builder); + let rightStr = buildToString(state, right, builder); + return buildAppendString(state, leftStr, rightStr, builder); + } + // TODO: Keep Integral type if wrapped by conversion back to integral? See asm.js + let leftNum = buildToNumber(state, left, builder); + let rightNum = buildToNumber(state, right, builder); + return builder.createFAdd(leftNum, rightNum); + } + case "-": { + // TODO: Keep Integral type? + let leftNum = buildToNumber(state, left, builder); + let rightNum = buildToNumber(state, right, builder); + return builder.createFSub(leftNum, rightNum); + } + case "/": { + // TODO: Keep Integral type if wrapped by conversion back to integral? See asm.js + let leftNum = buildToNumber(state, left, builder); + let rightNum = buildToNumber(state, right, builder); + return builder.createFDiv(leftNum, rightNum); + } + case "%": { + // TODO: Keep Integral type? + let leftNum = buildToNumber(state, left, builder); + let rightNum = buildToNumber(state, right, builder); + return builder.createFRem(leftNum, rightNum); + } + case "*": { + // TODO: Keep Integral type if wrapped by conversion back to integral? See asm.js + let leftNum = buildToNumber(state, left, builder); + let rightNum = buildToNumber(state, right, builder); + return builder.createFMul(leftNum, rightNum); + } + case "**": { + // TODO: Keep Integral type if wrapped by conversion back to integral? See asm.js + let leftNum = buildToNumber(state, left, builder); + let rightNum = buildToNumber(state, right, builder); + return builder.createCall(state.intrinsics.pow, [leftNum, rightNum]); + } + case "&": { + let leftInt = buildToInt32(state, left, builder); + let rightInt = buildToInt32(state, right, builder); + return builder.createAnd(leftInt, rightInt); + } + case "|": { + let leftInt = buildToInt32(state, left, builder); + let rightInt = buildToInt32(state, right, builder); + return builder.createOr(leftInt, rightInt); + } + case "^": { + let leftInt = buildToInt32(state, left, builder); + let rightInt = buildToInt32(state, right, builder); + return builder.createXor(leftInt, rightInt); + } + case ">>>": { + let leftInt = builder.createExtractValue(buildToUint32(state, left, builder), [0]); + let rightInt = builder.createExtractValue(buildToUint32(state, right, builder), [0]); + let maskedRightInt = builder.createAnd(rightInt, ConstantInt.get(llvmContext, 0x1f)); + return builder.createLShr(leftInt, maskedRightInt); + } + case ">>": { + let leftInt = buildToInt32(state, left, builder); + let rightInt = builder.createExtractValue(buildToUint32(state, right, builder), [0]); + let maskedRightInt = builder.createAnd(rightInt, ConstantInt.get(llvmContext, 0x1f)); + return builder.createAShr(leftInt, maskedRightInt); + } + case "<<": { + let leftInt = buildToInt32(state, left, builder); + let rightInt = builder.createExtractValue(buildToUint32(state, right, builder), [0]); + let maskedRightInt = builder.createAnd(rightInt, ConstantInt.get(llvmContext, 0x1f)); + return builder.createShl(leftInt, maskedRightInt); + } + default: { let error = new CompilerDiagnostic( - `The ${expr.operator} operator is not yet implemented for LLVM.`, + `The ${expr.operator} operator is not supported for LLVM.`, expr.loc, "PP2000", "FatalError" ); state.realm.handleError(error); throw new FatalError(); - default: - invariant(false, "unknown operator: " + expr.operator); + } + } +} + +function buildFromUnaryExpression(state: CompilerState, expr: BabelNodeUnaryExpression, builder: IRBuilder): LLVMValue { + let value = buildFromExpression(state, expr.argument, builder); + switch (expr.operator) { + case "+": { + // TODO: Keep Integral type? + return buildToNumber(state, value, builder); + } + case "-": { + // TODO: Keep Integral type? + return builder.createFNeg(buildToNumber(state, value, builder)); + } + case "~": { + return builder.createNot(buildToInt32(state, value, builder)); + } + case "!": { + return builder.createNot(buildToBoolean(state, value, builder)); + } + default: { + let error = new CompilerDiagnostic( + `The ${expr.operator} operator is not supported for LLVM.`, + expr.loc, + "PP2000", + "FatalError" + ); + state.realm.handleError(error); + throw new FatalError(); + } } } @@ -180,6 +332,9 @@ export function buildFromExpression(state: CompilerState, expr: BabelNodeExpress case "BinaryExpression": { return buildFromBinaryExpression(state, expr, builder); } + case "UnaryExpression": { + return buildFromUnaryExpression(state, expr, builder); + } case "ConditionalExpression": { let condition = buildToBoolean(state, buildFromExpression(state, expr.test, builder), builder); let consequentValue = buildFromExpression(state, expr.consequent, builder);