MichaReiser/speedy.js

View on GitHub
packages/compiler/src/code-generation/value/object-property-reference.ts

Summary

Maintainability
A
35 mins
Test Coverage
import * as llvm from "llvm-node";
import * as ts from "typescript";
import {CodeGenerationContext} from "../code-generation-context";
import {invoke} from "../util/functions";
import {Allocation} from "./allocation";
import {ObjectReference} from "./object-reference";
import {AssignableValue, Value} from "./value";

/**
 * Reference to an object property (x.name)
 */
export abstract class ObjectPropertyReference implements AssignableValue {

    /**
     * Creates a computed property
     * @param propertyType the type of the property
     * @param object the object to which the property belongs
     * @param property the property symbol
     * @param getter the getter to access the property
     * @param setter the setter to change the value of the property
     * @return the property reference
     */
    static createComputedPropertyReference(propertyType: ts.Type,
                                           object: ObjectReference,
                                           property: ts.Symbol,
                                           getter: llvm.Function | undefined,
                                           setter: llvm.Function | undefined) {
        return new ComputedObjectPropertyReference(propertyType, object, property, getter, setter);
    }

    static createFieldProperty(propertyType: ts.Type, object: ObjectReference, property: ts.Symbol) {
        return new ObjectFieldPropertyReference(propertyType, object, property);
    }

    protected constructor(public propertyType: ts.Type, protected object: ObjectReference) {
    }

    generateIR(context: CodeGenerationContext): llvm.Value {
        return this.getValue(context).generateIR(context);
    }

    generateAssignmentIR(value: Value, context: CodeGenerationContext): void {
        this.setValue(value, context);
    }

    /**
     * Returns the value of the property
     * @param context the context
     */
    protected abstract getValue(context: CodeGenerationContext): Value;

    /**
     * Sets the value of the property
     * @param value the value to set
     * @param context the context
     */
    protected abstract setValue(value: Value, context: CodeGenerationContext): void;

    /**
     * Indicator if the property is assignable or not
     */
    abstract isAssignable(): boolean;

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

    dereference(context: CodeGenerationContext) {
        return this.getValue(context);
    }

    castImplicit(type: ts.Type, context: CodeGenerationContext) {
        return this.dereference(context).castImplicit(type, context);
    }
}

/**
 * Backs a computed property, e.g. one that is implemented using get index() or one that resits in the runtime
 */
class ComputedObjectPropertyReference extends ObjectPropertyReference {
    constructor(propertyType: ts.Type,
                object: ObjectReference,
                private property: ts.Symbol,
                private getter: llvm.Function | undefined,
                private setter: llvm.Function | undefined) {
        super(propertyType, object);
    }

    protected getValue(context: CodeGenerationContext): Value {
        const result = invoke(this.getter!, [this.object.generateIR(context)], context.toLLVMType(this.propertyType), context, { name: this.property.name });
        return context.value(result, this.propertyType);
    }

    protected setValue(value: Value, context: CodeGenerationContext): void {
        const args = [
            value.generateIR(context),
            this.object.generateIR(context)
        ].reverse();

        invoke(this.setter!, args, llvm.Type.getVoidTy(context.llvmContext), context, this.property.name);
    }

    isAssignable(): boolean {
        return !!this.setter;
    }
}

/**
 * Backs a property that dispatches directly to an object field
 */
class ObjectFieldPropertyReference extends ObjectPropertyReference {

    constructor(propertyType: ts.Type, object: ObjectReference, private property: ts.Symbol) {
        super(propertyType, object);
    }

    protected getValue(context: CodeGenerationContext): Value {
        const alignment = Allocation.getPreferredValueAlignment(this.propertyType, context);
        const value = context.builder.createAlignedLoad(this.getFieldAddress(context), alignment, this.property.name);
        return context.value(value, this.propertyType);
    }

    protected setValue(value: Value, context: CodeGenerationContext): void {
        const alignment = Allocation.getPreferredValueAlignment(this.propertyType, context);
        context.builder.createAlignedStore(value.generateIR(context), this.getFieldAddress(context), alignment);
    }

    isAssignable(): boolean {
        return true;
    }

    private getFieldAddress(context: CodeGenerationContext) {
        const fieldIndex = [
            llvm.ConstantInt.get(context.llvmContext, 0),
            llvm.ConstantInt.get(context.llvmContext, this.object.clazz.getFieldOffset(this.property))
        ];

        return context.builder.createInBoundsGEP(this.object.generateIR(context), fieldIndex, `&${this.property.name}`);
    }
}