ApsarasX/llvm-bindings

[Bug] CreateAlloca causes segmentation fault

jso8910 opened this issue · 7 comments

Your environment

  • OS version: MacOS monterey
  • Node.js version: 17.6.0
  • LLVM version: 13

Describe the bug

Essentially, if I pass an IRBuilder around a few functions as an argument, (I haven't tested without doing this but I strongly assume that nothing would change if I used it in its original function) CreateAlloca causes a segmentation fault. It used to work and I haven't changed anything about how I use it but it still doesn't work. I have tried manually declaring all of the values so I am sure there is nothing wrong with them. This is how I am using it:

builder.CreateAlloca(builder.getInt64Ty(), null, name)

All I get is a segmentation fault. I have run SetInsertPoint if you are wondering.

Expected behavior

No segmentation fault, a variable to be created

The information you gave is too little. I can't find out the cause of the problem. Please give the complete code and the screenshot of error reporting.

The error:
Zsh: segmentation fault

That's not exact but there is no information.

The code? That's it. Everything else works and doesn't cause errors and is the code I used that used to work before I randomly got segmentation faults. If you want to know I can tell you the basics of the code (I can't show you because I am on my phone).

I initialize context, builder, and module, do processing of an object, run a function with builder and context as an argument, that function creates an llvm function and then runs another function with builder and context as a function which creates a block called init and I run builder.SetInsertPoint(init). This isn't exactly where the variable assignment normally is but I put the variable assignment here to eliminate the possibility of bugs elsewhere. At this point there is a segmentation fault.

Here is the relevant code now that I am on my computer:

Initializing the builder

    const context = new llvm.LLVMContext();
    const module = new llvm.Module("demo", context);
    const builder = new llvm.IRBuilder(context);

Where I run the CreateAlloca

    const init = llvm.BasicBlock.Create(context, "init", parent);
    const cond = llvm.BasicBlock.Create(context, "condition", parent);
    const body = llvm.BasicBlock.Create(context, "body", parent);
    const after_body = llvm.BasicBlock.Create(context, "after", parent);
    const end = llvm.BasicBlock.Create(context, "end", parent);
    parent.addBasicBlock(init)
    builder.SetInsertPoint(init)
    builder.CreateAlloca(builder.getInt64Ty(), null, name)

The code you gave is not enough, please give all the code you wrote.

In addition, I used your sample to write the following code, it works fine.

import path from 'path';
import llvm from 'llvm-bindings';

const filename = path.basename(__filename);
const context = new llvm.LLVMContext();
const module = new llvm.Module("demo", context);
const builder = new llvm.IRBuilder(context);

const returnType = builder.getVoidTy();
const functionType = llvm.FunctionType.get(returnType, false);
const parent = llvm.Function.Create(functionType, llvm.Function.LinkageTypes.ExternalLinkage, 'issue13', module);

const init = llvm.BasicBlock.Create(context, "init", parent);
builder.SetInsertPoint(init)
builder.CreateAlloca(builder.getInt64Ty(), null, 'alloca')
builder.CreateRetVoid();

if (llvm.verifyFunction(parent)) {
    console.error(`${filename}: verifying the 'add' function failed`);
    process.exit(1);
}
if (llvm.verifyModule(module)) {
    console.error(`${filename}: verifying the module failed`);
    process.exit(1);
}

console.log(module.print());

Here is all of my code used to generate the llvm. The code I am testing will run the create_function function and, within it, call create_for. One variable (i=0) should be assigned inside the init section.

interface Var_List {
    [keys: string]: llvm.AllocaInst
}

interface LooseType {
    [keys: string]: llvm.Type
}

interface LooseValue {
    [keys: string]: Function
}

const VARS: Var_List = {}

const TypeByName: LooseType = {}

const TypeValueByName: LooseValue = {}

const gen_llvm = (ast: ts.NodeArray<ts.Node>, typeChecker: ts.TypeChecker) => {
    const context = new llvm.LLVMContext();
    const module = new llvm.Module("demo", context);
    const builder = new llvm.IRBuilder(context);

    // Get type objects
    TypeByName.i16 = builder.getInt16Ty();
    TypeByName.i32 = builder.getInt32Ty();
    TypeByName.i64 = builder.getInt64Ty();
    // Get function to create value
    TypeValueByName.i16 = builder.getInt16;
    TypeValueByName.i32 = builder.getInt32;
    TypeValueByName.i64 = builder.getInt64;
    // TODO: add bool, char, float, etc

    ast.forEach(node => {
        switch (node.kind) {
            // TODO: add more to switch and eventually make this into a function which contains every possible child and how to handle it
            case SyntaxKind.FunctionDeclaration:
                create_function((node as FunctionDeclaration), builder, context, typeChecker)
    }
    })
    return module
}

// TODO add a return type and parameters
const create_function = (node: FunctionDeclaration, builder: llvm.IRBuilder, context: llvm.LLVMContext, typeChecker: ts.TypeChecker) => {
    let funcName = node.name?.kind == SyntaxKind.Identifier && node.name?.escapedText;
    if (!funcName) {
        return; // Add something empty of a type idk
    }
    const returnType = builder.getInt32Ty();
    const funcType = llvm.FunctionType.get(returnType, false);
    const func = llvm.Function.Create(
        funcType,
        llvm.Function.LinkageTypes.ExternalLinkage,
        funcName);
    const fnStart = llvm.BasicBlock.Create(context, "start", func);
    builder.SetInsertPoint(fnStart);

    const body = node.body;
    if (!body || body?.kind !== SyntaxKind.Block) {
        return;
    }

    body.forEachChild(node => {
        switch (node.kind) {
            case SyntaxKind.ForStatement:
                create_for((node as ForStatement), builder, context, typeChecker);
        }
    })
    // TODO have a proper return
    builder.CreateRet(builder.getInt32(0))
}

const var_dec_list = (node: VariableDeclarationList, builder: llvm.IRBuilder, context: llvm.LLVMContext, typeChecker: ts.TypeChecker) => {
    node.declarations.forEach(var_dec => {
        if (var_dec.name.kind === SyntaxKind.Identifier) {
            console.log("hi")
            const name = var_dec.name.escapedText;
            let value = null;
            switch (var_dec.initializer?.kind) {
                // TODO: Add more, make function work for stuff that isnt a number
                case SyntaxKind.NumericLiteral:
                    value = parseInt((var_dec.initializer as ts.NumericLiteral).text);
                // ? consider how to distinguish between char and string.
            }

            // ? Do we need to check for valid variable names? How should we lint?
            if (typeof value !== "undefined" && name && typeof name !== "undefined" && var_dec.initializer) {
                console.log("hi")
                let type = typeChecker.typeToString(typeChecker.getTypeAtLocation(var_dec.initializer));

                if (!(type in TypeByName)) {
                    type = "i64" // TODO: should this be a float? Probably but idk exactly how to do floats rn
                }
                VARS[name] = create_primitive_var(name, TypeByName[type], builder)
                // VARS[name] = builder.CreateAlloca(TypeByName[type], null, name)
                console.log("seg")
                set_var(TypeValueByName[type](value), VARS[name], builder)
            }
        }
    })
}

const create_primitive_var = (name: string, type: llvm.Type, builder: llvm.IRBuilder) => {
    console.log(!!builder, type, name)
    // ! DO NOT PUSH https://github.com/ApsarasX/llvm-bindings/issues/13
    return builder.CreateAlloca(type, undefined, name)
}

const set_var = (value: llvm.Value, var_alloc: llvm.AllocaInst, builder: llvm.IRBuilder) => {

    if (!llvm.Type.isSameType(value.getType(), var_alloc.getType().getPointerElementType())) {
        throw TypeError(`Variable types do not match. Cannot change type of a variable`); // TODO: better message
    }
    builder.CreateStore(value, var_alloc);
}

export const create_for = (node: ForStatement, builder: llvm.IRBuilder, context: llvm.LLVMContext, typeChecker: ts.TypeChecker) => {
    const parent = builder.GetInsertBlock()?.getParent()
    if (!parent) {
        return
    }
    const init = llvm.BasicBlock.Create(context, "init", parent);
    const cond = llvm.BasicBlock.Create(context, "condition", parent);
    const body = llvm.BasicBlock.Create(context, "body", parent);
    const after_body = llvm.BasicBlock.Create(context, "after", parent);
    const end = llvm.BasicBlock.Create(context, "end", parent);
    parent.addBasicBlock(init)
    builder.SetInsertPoint(init)

    if (node.initializer) {
        console.log("here")
        switch (node.initializer.kind) {
            case SyntaxKind.VariableDeclarationList:
                console.log("here")
                var_dec_list((node.initializer as unknown as ts.VariableDeclarationList), builder, context, typeChecker);
                console.log(VARS)
            // TODO more cases, put in function
        }
    }
    // VARS['i'] = create_primitive_var('i', builder.getInt32Ty(), builder)
    // set_var(builder.getInt32(0), VARS['i'], builder)
    builder.CreateBr(cond);

    parent.addBasicBlock(cond)
    builder.SetInsertPoint(cond)
    const condBuilder = new llvm.IRBuilder(cond);
    const condCheck = condBuilder.CreateICmpSLT(builder.CreateLoad(VARS['i'].getType().getPointerElementType(), VARS['i'], 'i'), builder.getInt32(5), 'condition');
    builder.CreateCondBr(condCheck, body, end)

    parent.addBasicBlock(body);
    // TODO the content here
    builder.CreateBr(after_body);

    parent.addBasicBlock(after_body);
    builder.SetInsertPoint(after_body);
    const inc_res = builder.CreateAdd(builder.CreateLoad(builder.getInt32Ty(), VARS['i']), builder.getInt32(1), 'inc_i')
    builder.CreateStore(inc_res, VARS['i'])
}

export default gen_llvm;

There are 4 errors in your sample code.

Error 1

When using llvm.Function.Create, you should pass the 4th argument as module.

The corrent code is shown below.

    const func = llvm.Function.Create(
        funcType,
        llvm.Function.LinkageTypes.ExternalLinkage,
-       funcName);
+       funcName,
+       module);

Error 2

All llvm methods are imported from C++ addon. If you write like above, you will lose this pointer.

The corrent code is shown below.

-    TypeValueByName.i16 = builder.getInt16;
-    TypeValueByName.i32 = builder.getInt32;
-    TypeValueByName.i64 = builder.getInt64;
+    TypeValueByName.i16 = builder.getInt16.bind(builder);
+    TypeValueByName.i32 = builder.getInt32.bind(builder);
+    TypeValueByName.i64 = builder.getInt64.bind(builder);

Error 3

You passed two different types of llvm values to CreateICmpSLT method.

The corrent code is shown below.

-    const condCheck = condBuilder.CreateICmpSLT(builder.CreateLoad(VARS['i'].getType().getPointerElementType(), VARS['i'], 'i'), builder.getInt32(5), 'condition');
+    const condCheck = condBuilder.CreateICmpSLT(builder.CreateLoad(VARS['i'].getType().getPointerElementType(), VARS['i'], 'i'), builder.getInt64(5), 'condition');

Error 4

If you pass the third parameter when creating a basic block using llvm.BasicBlock.Create, you should not call the addBasicBlock method extraly. They are in conflict with each other.

- parent.addBasicBlock(init);
- parent.addBasicBlock(cond);
- parent.addBasicBlock(body);
- parent.addBasicBlock(after_body);

it's solved, please close.