MichaReiser/speedy.js

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

Summary

Maintainability
A
1 hr
Test Coverage
import * as assert from "assert";
import * as llvm from "llvm-node";
import * as ts from "typescript";
import {CodeGenerationContext} from "../code-generation-context";
import {NameMangler, Parameter} from "../name-mangler";
import {TypePlace, TypeScriptToLLVMTypeConverter} from "../util/typescript-to-llvm-type-converter";
import {ObjectReference} from "./object-reference";
import {ResolvedFunction} from "./resolved-function";

export interface FunctionProperties {
    linkage: llvm.LinkageTypes;
    readonly: boolean;
    readnone: boolean;
    alwaysInline: boolean;
    noInline: boolean;
    noUnwind: boolean;
    visibility: llvm.VisibilityTypes;
}

/**
 * Factory for creating the declarations of llvm functions
 */
export class FunctionFactory {

    /**
     * Creates a new instance that uses the given name mangler
     * @param nameMangler the name mangler to use
     * @param typeConverter the type converter used to convert the return and argument types to llvm types
     */
    constructor(private nameMangler: NameMangler, private typeConverter: TypeScriptToLLVMTypeConverter) {
    }

    /**
     * Creates the declaration (and if needed the definition) for the given resolved function called with the specified number of arguments
     * @param resolvedFunction the function to call (specific function, not overload)
     * @param numberOfArguments the number of arguments passed to the function (to know where optional arguments are unset)
     * @param context the code generation context
     * @param properties properties of the function to create, e.g. is the function readonly, what is the linkage
     * @return {Function} the existing function instance or the newly declared function
     */
    getOrCreate(resolvedFunction: ResolvedFunction,
                numberOfArguments: number,
                context: CodeGenerationContext,
                properties?: Partial<FunctionProperties>): llvm.Function {
        return this.getOrCreateStaticOrInstanceFunction(resolvedFunction, numberOfArguments, context, this.getInitializedProperties(properties));
    }

    /**
     * Creates the declaration (and if needed the definition) for the given resolved function called with the specified number of arguments.
     * The declared function is a instance method that needs to be called with the object instance as first argument
     * @param objectReference the object to which the method belongs
     * @param resolvedFunction the resolved function (not overloaded)
     * @param numberOfArguments the number of arguments passed to the function
     * @param context the code generation context
     * @param properties properties of the function to create, e.g. is the function readonly, what is the linkage
     * @return {Function} the existing or newly declared function
     */
    getOrCreateInstanceMethod(objectReference: ObjectReference,
                              resolvedFunction: ResolvedFunction,
                              numberOfArguments: number,
                              context: CodeGenerationContext,
                              properties?: Partial<FunctionProperties>) {
        assert(resolvedFunction.instanceMethod && resolvedFunction.classType, "Resolved function needs to be an instance method");

        return this.getOrCreateStaticOrInstanceFunction(
            resolvedFunction,
            numberOfArguments,
            context,
            this.getInitializedProperties(properties),
            objectReference
        );
    }

    private getInitializedProperties(properties?: Partial<FunctionProperties>): FunctionProperties {
        return Object.assign({}, this.getDefaultFunctionProperties(), properties);
    }

    protected getDefaultFunctionProperties(): FunctionProperties {
        return {
            linkage: llvm.LinkageTypes.LinkOnceODRLinkage,
            readonly: false,
            readnone: false,
            alwaysInline: false,
            noInline: false,
            noUnwind: false,
            visibility: llvm.VisibilityTypes.Default
        };
    }

    private getOrCreateStaticOrInstanceFunction(resolvedFunction: ResolvedFunction,
                                                numberOfArguments: number,
                                                context: CodeGenerationContext,
                                                properties: FunctionProperties,
                                                objectReference?: ObjectReference) {
        const mangledName = this.getMangledFunctionName(resolvedFunction, numberOfArguments);
        let fn = context.module.getFunction(mangledName);

        if (!fn) {
            fn = this.createFunction(mangledName, resolvedFunction, numberOfArguments, context, properties, objectReference);
        }

        return fn;
    }

    protected createFunction(mangledName: string,
                             resolvedFunction: ResolvedFunction,
                             numberOfArguments: number,
                             context: CodeGenerationContext,
                             properties: FunctionProperties,
                             objectReference?: ObjectReference) {
        const llvmArgumentTypes = this.toLlvmArgumentTypes(resolvedFunction, numberOfArguments, context, objectReference);
        const functionType = llvm.FunctionType.get(this.typeConverter.convert(resolvedFunction.returnType, TypePlace.RETURN_VALUE), llvmArgumentTypes, false);
        const fn = llvm.Function.create(functionType, properties.linkage, mangledName, context.module);
        fn.visibility = properties.visibility;

        for (const attribute of this.getFunctionAttributes(properties)) {
            fn.addFnAttr(attribute);
        }

        this.attributeParameters(fn, resolvedFunction, context, objectReference);

        if (resolvedFunction.returnType.flags & ts.TypeFlags.Object) {
            const classReference = context.resolveClass(resolvedFunction.returnType)!;
            // If object can be undefined, or null
            fn.addDereferenceableAttr(0, classReference.getTypeStoreSize(resolvedFunction.returnType as ts.ObjectType, context));
        }

        if (resolvedFunction.returnType.flags & ts.TypeFlags.BooleanLike) {
            fn.addAttribute(0, llvm.Attribute.AttrKind.ZExt);
        }

        return fn;
    }

    private getFunctionAttributes(properties: FunctionProperties) {
        const attributes = [];

        if (properties.noUnwind) {
            attributes.push(llvm.Attribute.AttrKind.NoUnwind);
        }

        if (properties.alwaysInline) {
            attributes.push(llvm.Attribute.AttrKind.AlwaysInline);
        }

        if (properties.noInline) {
            attributes.push(llvm.Attribute.AttrKind.NoInline);
        }

        if (properties.readonly) {
            attributes.push(llvm.Attribute.AttrKind.ReadOnly);
        }

        if (properties.readnone) {
            attributes.push(llvm.Attribute.AttrKind.ReadNone);
        }

        return attributes;
    }

    private attributeParameters(fn: llvm.Function, resolvedFunction: ResolvedFunction, context: CodeGenerationContext, object?: ObjectReference) {
        const parameters = fn.getArguments();
        let argumentOffset = 0;

        if (object) {
            const self = parameters[0];
            self.addAttr(llvm.Attribute.AttrKind.ReadOnly);
            self.addDereferenceableAttr(object.getTypeStoreSize(context));
            argumentOffset = 1;
        }

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

            if (parameterDefinition.variadic) {
                break;
            }

            if (parameterDefinition.type.flags & ts.TypeFlags.BooleanLike) {
                parameter.addAttr(llvm.Attribute.AttrKind.ZExt);
            }

            if (parameterDefinition.type.flags & ts.TypeFlags.Object) {
                const classReference = context.resolveClass(parameterDefinition.type as ts.ObjectType);

                if (classReference) {
                    parameter.addDereferenceableAttr(classReference.getTypeStoreSize(parameterDefinition.type as ts.ObjectType, context));
                }
            }
        }
    }

    private getMangledFunctionName(resolvedFunction: ResolvedFunction, numberOfArguments: number) {
        const usedParameters: Parameter[] = [];

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

            if (parameter.optional && !parameter.initializer && numberOfArguments <= i) {
                break; // Optional parameter that is not set. Therefore, this parameter is not actually used
            }
            usedParameters.push(parameter);
        }

        return this.mangleFunctionName(resolvedFunction, usedParameters);
    }

    protected mangleFunctionName(resolvedFunction: ResolvedFunction, usedParameters: Parameter[]) {
        if (resolvedFunction.classType) {
            return this.nameMangler.mangleMethodName(
                resolvedFunction.classType,
                resolvedFunction.functionName!,
                usedParameters,
                resolvedFunction.sourceFile
            );
        }

        return this.nameMangler.mangleFunctionName(resolvedFunction.functionName, usedParameters, resolvedFunction.sourceFile);
    }

    private toLlvmArgumentTypes(resolvedFunction: ResolvedFunction,
                                numberOfArguments: number,
                                context: CodeGenerationContext,
                                objectReference?: ObjectReference) {
        const argumentTypes = objectReference ? [this.typeConverter.convert(objectReference.type, TypePlace.THIS)] : [];

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

            if (parameter.variadic) {
                const elementType = (parameter.type as ts.GenericType).typeArguments[0];
                argumentTypes.push(this.typeConverter.convert(elementType, TypePlace.PARAMETER).getPointerTo(), llvm.Type.getInt32Ty(context.llvmContext));
                break;
            } else if (parameter.optional && !parameter.initializer && numberOfArguments <= i) {
                // optional argument that is not set, skip
                break;
            } else {
                argumentTypes.push(this.typeConverter.convert(parameter.type, TypePlace.PARAMETER));
            }
        }

        return argumentTypes;
    }
}