MichaReiser/speedy.js

View on GitHub
packages/compiler/src/code-generation/value/abstract-function-reference.ts

Summary

Maintainability
A
2 hrs
Test Coverage
import * as assert from "assert";
import * as llvm from "llvm-node";
import * as ts from "typescript";
import {CodeGenerationDiagnostics} from "../../code-generation-diagnostic";
import {CompilationContext} from "../../compilation-context";
import {CodeGenerationContext} from "../code-generation-context";
import {invoke} from "../util/functions";
import {llvmArrayValue} from "../util/llvm-array-helpers";
import {getArrayElementType, getCallSignature, isFunctionType} from "../util/types";
import {TypePlace} from "../util/typescript-to-llvm-type-converter";
import {FunctionPointer, FunctionReference} from "./function-reference";
import {ObjectReference} from "./object-reference";
import {createResolvedFunctionFromSignature, ResolvedFunction, ResolvedFunctionParameter} from "./resolved-function";
import {Undefined} from "./undefined";
import {AssignableValue, Value} from "./value";

/**
 * Base class for function references. Handles the coercion of the argument values to the expected types of the function parametes
 * and as well as handling var arg parameters correctly.
 */
export abstract class AbstractFunctionReference implements FunctionReference {

    /**
     * Creates a new instance
     * @param classType the type of the class to which this function belongs (static or instance method) or absent, if it is
     * a stand alone function.
     */
    constructor(protected classType?: ts.ObjectType) {}

    /**
     * Returns the default resolved function in case this function reference is dereferenced (e.g. assigned to a variable).
     * @throws in case this function is overloaded and therefore, cannot be dereferenced
     */
    protected abstract getResolvedFunction(callerContext: CodeGenerationContext): ResolvedFunction;

    /**
     * Returns the llvm function for the given resolved function and passed arguments
     * @param resolvedFunction the resolved function
     * @param context the context
     * @param passedArguments the arguments passed
     */
    protected abstract getLLVMFunction(resolvedFunction: ResolvedFunction, context: CodeGenerationContext, passedArguments?: llvm.Value[]): FunctionPointer;

    invoke(callExpression: ts.CallExpression | ts.NewExpression, callerContext: CodeGenerationContext): void | Value {
        const resolvedSignature = callerContext.typeChecker.getResolvedSignature(callExpression);
        const resolvedFunction = this.getResolvedFunctionFromSignature(resolvedSignature, callerContext.compilationContext);

        const passedArguments = toLlvmArgumentValues(callExpression.arguments || [] as ts.Expression[], resolvedFunction, callerContext);
        return this.invokeResolvedFunction(resolvedFunction, passedArguments, callerContext);
    }

    invokeWith(args: llvm.Value[], callerContext: CodeGenerationContext): void | Value {
        return this.invokeResolvedFunction(this.getResolvedFunction(callerContext), args, callerContext);
    }

    protected getResolvedFunctionFromSignature(signature: ts.Signature, compilationContext: CompilationContext): ResolvedFunction {
        return createResolvedFunctionFromSignature(signature, compilationContext, this.classType);
    }

    private invokeResolvedFunction(resolvedFunction: ResolvedFunction, args: llvm.Value[], callerContext: CodeGenerationContext) {
        const llvmFunction = this.getLLVMFunction(resolvedFunction, callerContext, args);
        assert(llvmFunction.type.isPointerTy() && (llvmFunction.type as llvm.PointerType).elementType.isFunctionTy(), "Expected pointer to a function type");

        const callArguments = this.getCallArguments(resolvedFunction, args, callerContext);
        let name: string | undefined;

        if (!(resolvedFunction.returnType.flags & ts.TypeFlags.Void)) {
            name = resolvedFunction.functionName ? `${resolvedFunction.functionName}ReturnValue` : undefined;
        }

        const returnType = callerContext.toLLVMType(resolvedFunction.returnType, TypePlace.RETURN_VALUE);
        let dereferenceableSize: number | undefined;

        if (resolvedFunction.returnType.flags & ts.TypeFlags.Object) {
            const classReference = callerContext.resolveClass(resolvedFunction.returnType)!;
            dereferenceableSize = classReference.getTypeStoreSize(resolvedFunction.returnType as ts.ObjectType, callerContext);
        }

        const returnValue = invoke(llvmFunction, callArguments, returnType, callerContext, { name, dereferenceableSize });

        if (resolvedFunction.returnType.flags & ts.TypeFlags.Void) {
            return Undefined.create(callerContext);
        }

        return callerContext.value(returnValue, resolvedFunction.returnType);
    }

    /**
     * Gets the call arguments for invoking the specified function
     * @param resolvedFunction the specific signature of the function to call
     * @param passedArguments the parameters passed in the invoke statement
     * @param callerContext the callers code generation context
     * @return the values that are to be passed to the llvm function
     */
    protected getCallArguments(resolvedFunction: ResolvedFunction, passedArguments: llvm.Value[], callerContext: CodeGenerationContext): llvm.Value[] {
        const result: llvm.Value[] = [];

        for (let i = 0; i < resolvedFunction.parameters.length; ++i) {
            const parameter = resolvedFunction.parameters[i];

            let arg: llvm.Value | undefined;

            if (passedArguments.length > i) {
                arg = passedArguments[i];
            } else if (parameter.initializer) {
                arg = callerContext.generateValue(parameter.initializer).generateIR(callerContext);
            } else if (parameter.optional) {
                break;
            } else if (!parameter.variadic) {
                assert(false, `Missing value for not optional parameter ${parameter.name}`);
                break;
            }

            if (parameter.variadic) {
                const arrayType = (parameter.type as ts.GenericType);
                const elementType = getArrayElementType(arrayType);
                const elementLLVMType = callerContext.toLLVMType(elementType, TypePlace.PARAMETER);
                const elements = llvmArrayValue(passedArguments.slice(i), elementLLVMType, callerContext, parameter.name);

                result.push(
                    elements,
                    llvm.ConstantInt.get(callerContext.llvmContext, passedArguments.length - i, undefined, false)
                );

                break;
            } else {
                result.push(arg!);
            }
        }

        return result;
    }

    isAssignable(): this is AssignableValue {
        return false;
    }

    isObject(): this is ObjectReference {
        return false;
    }

    dereference(): this {
        return this;
    }

    generateIR(context: CodeGenerationContext): llvm.Value {
        return this.getLLVMFunction(this.getResolvedFunction(context), context);
    }

    castImplicit(type: ts.Type, context: CodeGenerationContext): Value | undefined {
        assert(isFunctionType(type), "Target type needs to be a function type");

        const signature = getCallSignature(type);
        const resolvedFunction = this.getResolvedFunction(context);
        const parameters = signature.getParameters();
        const declaredParameters = signature.getDeclaration().parameters;

        const parameterTypes = resolvedFunction.parameters.map((p, i) => context.typeChecker.getTypeOfSymbolAtLocation(parameters[i], declaredParameters[i]));

        const returnTypeEqual = signature.getReturnType() === resolvedFunction.returnType;
        const parameterTypesEqual = resolvedFunction.parameters.length === parameterTypes.length &&
            resolvedFunction.parameters.every((parameter, i) => parameter.type === parameterTypes[i]);

        if (returnTypeEqual && parameterTypesEqual) {
            return this; // no cast needed
        }

        // casting functions is not yet supported
        return undefined;
    }
}

function toLlvmArgumentValues(args: ts.Expression[], resolvedFunction: ResolvedFunction, callerContext: CodeGenerationContext) {
    const values: llvm.Value[] = [];
    for (let i = 0; i < Math.min(args.length, resolvedFunction.parameters.length); ++i) {
        const parameter = resolvedFunction.parameters[i];

        if (parameter.variadic) {
            const llvmVarArgs = toLlvmVariadicArgument(args.slice(i), parameter, callerContext);
            values.push(...llvmVarArgs);
        } else {
            const argumentValue = toLlvmArgumentValue(args[i], parameter, callerContext);

            values.push(argumentValue);
        }
    }

    return values;
}

function toLlvmVariadicArgument(varArgs: ts.Expression[], parameter: ResolvedFunctionParameter, callerContext: CodeGenerationContext) {
    const elementType = getArrayElementType(parameter.type);
    const llvmVarArgs = new Array<llvm.Value>(varArgs.length);

    for (let j = 0; j < varArgs.length; ++j) {
        const varArgNode = varArgs[j];
        const castedElement = callerContext.generateValue(varArgNode).castImplicit(elementType, callerContext);
        if (!castedElement) {
            throw CodeGenerationDiagnostics.unsupportedImplicitCastOfArgument(
                varArgNode,
                callerContext.typeChecker.typeToString(elementType),
                callerContext.typeChecker.typeToString(callerContext.typeChecker.getTypeAtLocation(varArgNode))
            );
        }

        llvmVarArgs[j] = castedElement.generateIR(callerContext);
    }
    return llvmVarArgs;
}

function toLlvmArgumentValue(arg: ts.Expression, parameter: ResolvedFunctionParameter, callerContext: CodeGenerationContext) {
    const castedElement = callerContext.generateValue(arg).castImplicit(parameter.type, callerContext);
    const argumentType = callerContext.typeChecker.getTypeAtLocation(arg);

    if (!castedElement) {
        throw CodeGenerationDiagnostics.unsupportedImplicitCastOfArgument(
            arg,
            callerContext.typeChecker.typeToString(parameter.type),
            callerContext.typeChecker.typeToString(argumentType)
        );
    }

    return castedElement.generateIR(callerContext);
}