MichaReiser/speedy.js

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

Summary

Maintainability
A
35 mins
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 {ObjectReference} from "./object-reference";
import {Pointer} from "./pointer";

import {AssignableValue, Value} from "./value";

/**
 * Wrapper for an allocation with an alignment.
 *
 * Is capable to work with Values and ensures correct alignment
 */
export class Allocation implements AssignableValue {
    static create(type: ts.Type, context: CodeGenerationContext, name?: string): Allocation {
        const allocaInst = Allocation.createAllocaInstInEntryBlock(context.toLLVMType(type), context, name);
        const alignment = Allocation.getPreferredValueAlignment(type, context);
        return new Allocation(allocaInst, type, alignment, name);
    }

    static createAllocaInstInEntryBlock(type: llvm.Type, context: CodeGenerationContext, name?: string) {
        const fn = context.scope.enclosingFunction;
        const entryBlock = fn.getEntryBlock();
        assert(entryBlock, "The function requires an entry block");

        const entryBlockBuilder = new llvm.IRBuilder(fn.getEntryBlock()!);
        const allocation = entryBlockBuilder.createAlloca(type, undefined, name);
        allocation.alignment = this.getPreferredValueAlignment(type, context);
        return allocation;
    }

    static createGlobal(pointer: llvm.GlobalVariable, type: ts.Type, context: CodeGenerationContext, name?: string) {
        assert(pointer.type.isPointerTy(), "Address needs to be a pointer");
        const alignment = Allocation.getPreferredValueAlignment(type, context);
        return new Allocation(pointer, type, alignment, name);
    }

    static getPreferredValueAlignment(type: llvm.Type | ts.Type, context: CodeGenerationContext) {
        type = type instanceof llvm.Type ? type : context.toLLVMType(type);
        return context.module.dataLayout.getPrefTypeAlignment(type);
    }

    private constructor(public store: llvm.Value, public type: ts.Type, public alignment: number, public name?: string) {
    }

    isAssignable(): boolean {
        return !(this.store instanceof llvm.GlobalVariable && this.store.constant);
    }

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

    dereference(context: CodeGenerationContext): Value {
        if (this.type.flags & ts.TypeFlags.Object) {
            const classReference = context.resolveClass(this.type);
            if (classReference) {
                return classReference.objectFor(new AllocationPointerWrapper(this), this.type as ts.ObjectType, context);
            }
        }

        return context.value(this.generateIR(context), this.type);
    }

    generateIR(context: CodeGenerationContext): llvm.Value {
        return context.builder.createAlignedLoad(this.store, this.alignment, this.name);
    }

    generateAssignmentIR(value: Value | llvm.Value, context: CodeGenerationContext) {
        assert(this.isAssignable, "Cannot assign to constant global variable");

        const llvmValue = value instanceof llvm.Value ? value : value.generateIR(context);
        context.builder.createAlignedStore(llvmValue, this.store, this.alignment, false);
    }

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

/**
 * Wrapper for an allocation that stores an address to use it as pointer
 */
class AllocationPointerWrapper implements Pointer {
    constructor(private allocation: Allocation) {
    }

    get type() {
        return this.allocation.type;
    }

    isPointer(): true {
        return true;
    }

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

    set(ptr: llvm.Value, context: CodeGenerationContext): void {
        this.allocation.generateAssignmentIR(ptr, context);
    }
}