Commit fb21570630

Andrew Kelley <superjoe30@gmail.com>
2016-12-12 01:43:06
IR: implement alloca builtin
1 parent a963fba
doc/langref.md
@@ -370,6 +370,19 @@ TODO
 Built-in functions are prefixed with `@`. Remember that the `inline` keyword on
 a parameter means that the parameter must be known at compile time.
 
+### @alloca(inline T: type, count: usize) -> []T
+
+Allocates memory in the stack frame of the caller. This temporary space is
+automatically freed when the function that called alloca returns to its caller,
+just like other stack variables.
+
+When using this function to allocate memory, you should know the upper bound
+of `count`. Consider putting a constant array on the stack with the upper bound
+instead of using alloca. If you do use alloca it is to save a few bytes off
+the memory size given that you didn't actually hit your upper bound.
+
+The allocated memory contents are undefined.
+
 ### @typeof(expression) -> type
 
 This function returns a compile-time constant, which is the type of the
src/all_types.hpp
@@ -1044,6 +1044,7 @@ enum BuiltinFnId {
     BuiltinFnIdSetFnTest,
     BuiltinFnIdSetFnVisible,
     BuiltinFnIdSetDebugSafety,
+    BuiltinFnIdAlloca,
 };
 
 struct BuiltinFnEntry {
@@ -1413,6 +1414,7 @@ enum IrInstructionId {
     IrInstructionIdTruncate,
     IrInstructionIdIntType,
     IrInstructionIdBoolNot,
+    IrInstructionIdAlloca,
 };
 
 struct IrInstruction {
@@ -1914,6 +1916,14 @@ struct IrInstructionBoolNot {
     IrInstruction *value;
 };
 
+struct IrInstructionAlloca {
+    IrInstruction base;
+
+    IrInstruction *type_value;
+    IrInstruction *count;
+    LLVMValueRef tmp_ptr;
+};
+
 enum LValPurpose {
     LValPurposeNone,
     LValPurposeAssign,
src/codegen.cpp
@@ -1881,13 +1881,31 @@ static LLVMValueRef ir_render_div_exact(CodeGen *g, IrExecutable *executable, Ir
 }
 
 static LLVMValueRef ir_render_truncate(CodeGen *g, IrExecutable *executable, IrInstructionTruncate *instruction) {
-    assert(instruction->dest_type->type_entry->id == TypeTableEntryIdMetaType);
-    TypeTableEntry *dest_type = get_underlying_type(instruction->dest_type->static_value.data.x_type);
-    assert(dest_type->id == TypeTableEntryIdInt);
+    TypeTableEntry *dest_type = get_underlying_type(instruction->base.type_entry);
     LLVMValueRef target_val = ir_llvm_value(g, instruction->target);
     return LLVMBuildTrunc(g->builder, target_val, dest_type->type_ref, "");
 }
 
+static LLVMValueRef ir_render_alloca(CodeGen *g, IrExecutable *executable, IrInstructionAlloca *instruction) {
+    TypeTableEntry *slice_type = get_underlying_type(instruction->base.type_entry);
+    TypeTableEntry *ptr_type = slice_type->data.structure.fields[slice_ptr_index].type_entry;
+    TypeTableEntry *child_type = ptr_type->data.pointer.child_type;
+    LLVMValueRef size_val = ir_llvm_value(g, instruction->count);
+    LLVMValueRef ptr_val = LLVMBuildArrayAlloca(g->builder, child_type->type_ref, size_val, "");
+
+    // TODO in debug mode, initialize all the bytes to 0xaa
+
+    // store the freshly allocated pointer in the slice
+    LLVMValueRef ptr_field_ptr = LLVMBuildStructGEP(g->builder, instruction->tmp_ptr, slice_ptr_index, "");
+    LLVMBuildStore(g->builder, ptr_val, ptr_field_ptr);
+
+    // store the size in the len field
+    LLVMValueRef len_field_ptr = LLVMBuildStructGEP(g->builder, instruction->tmp_ptr, slice_len_index, "");
+    LLVMBuildStore(g->builder, size_val, len_field_ptr);
+
+    return instruction->tmp_ptr;
+}
+
 static void set_debug_location(CodeGen *g, IrInstruction *instruction) {
     AstNode *source_node = instruction->source_node;
     Scope *scope = instruction->scope;
@@ -1988,6 +2006,8 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable,
             return ir_render_truncate(g, executable, (IrInstructionTruncate *)instruction);
         case IrInstructionIdBoolNot:
             return ir_render_bool_not(g, executable, (IrInstructionBoolNot *)instruction);
+        case IrInstructionIdAlloca:
+            return ir_render_alloca(g, executable, (IrInstructionAlloca *)instruction);
         case IrInstructionIdSwitchVar:
         case IrInstructionIdContainerInitList:
         case IrInstructionIdStructInit:
@@ -2567,6 +2587,9 @@ static void do_code_gen(CodeGen *g) {
             } else if (instruction->id == IrInstructionIdCall) {
                 IrInstructionCall *call_instruction = (IrInstructionCall *)instruction;
                 slot = &call_instruction->tmp_ptr;
+            } else if (instruction->id == IrInstructionIdAlloca) {
+                IrInstructionAlloca *alloca_instruction = (IrInstructionAlloca *)instruction;
+                slot = &alloca_instruction->tmp_ptr;
             } else {
                 zig_unreachable();
             }
@@ -3191,6 +3214,7 @@ static void define_builtin_fns(CodeGen *g) {
     create_builtin_fn(g, BuiltinFnIdSetFnTest, "setFnTest", 1);
     create_builtin_fn(g, BuiltinFnIdSetFnVisible, "setFnVisible", 2);
     create_builtin_fn(g, BuiltinFnIdSetDebugSafety, "setDebugSafety", 2);
+    create_builtin_fn(g, BuiltinFnIdAlloca, "alloca", 2);
 }
 
 static void init(CodeGen *g, Buf *source_path) {
src/ir.cpp
@@ -371,6 +371,10 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionBoolNot *) {
     return IrInstructionIdBoolNot;
 }
 
+static constexpr IrInstructionId ir_instruction_id(IrInstructionAlloca *) {
+    return IrInstructionIdAlloca;
+}
+
 template<typename T>
 static T *ir_create_instruction(IrExecutable *exec, Scope *scope, AstNode *source_node) {
     T *special_instruction = allocate<T>(1);
@@ -1489,6 +1493,27 @@ static IrInstruction *ir_build_bool_not_from(IrBuilder *irb, IrInstruction *old_
     return new_instruction;
 }
 
+static IrInstruction *ir_build_alloca(IrBuilder *irb, Scope *scope, AstNode *source_node,
+    IrInstruction *type_value, IrInstruction *count)
+{
+    IrInstructionAlloca *instruction = ir_build_instruction<IrInstructionAlloca>(irb, scope, source_node);
+    instruction->type_value = type_value;
+    instruction->count = count;
+
+    ir_ref_instruction(type_value);
+    ir_ref_instruction(count);
+
+    return &instruction->base;
+}
+
+static IrInstruction *ir_build_alloca_from(IrBuilder *irb, IrInstruction *old_instruction,
+    IrInstruction *type_value, IrInstruction *count)
+{
+    IrInstruction *new_instruction = ir_build_alloca(irb, old_instruction->scope, old_instruction->source_node, type_value, count);
+    ir_link_new_instruction(new_instruction, old_instruction);
+    return new_instruction;
+}
+
 static void ir_gen_defers_for_block(IrBuilder *irb, Scope *inner_scope, Scope *outer_scope,
         bool gen_error_defers, bool gen_maybe_defers)
 {
@@ -2310,6 +2335,20 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo
 
                 return ir_build_int_type(irb, scope, node, arg0_value, arg1_value);
             }
+        case BuiltinFnIdAlloca:
+            {
+                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
+                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
+                if (arg0_value == irb->codegen->invalid_instruction)
+                    return arg0_value;
+
+                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
+                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
+                if (arg1_value == irb->codegen->invalid_instruction)
+                    return arg1_value;
+
+                return ir_build_alloca(irb, scope, node, arg0_value, arg1_value);
+            }
         case BuiltinFnIdMemcpy:
         case BuiltinFnIdMemset:
         case BuiltinFnIdAlignof:
@@ -7876,6 +7915,69 @@ static TypeTableEntry *ir_analyze_instruction_bool_not(IrAnalyze *ira, IrInstruc
     return bool_type;
 }
 
+static TypeTableEntry *ir_analyze_instruction_alloca(IrAnalyze *ira, IrInstructionAlloca *instruction) {
+    IrInstruction *type_value = instruction->type_value->other;
+    if (type_value->type_entry->id == TypeTableEntryIdInvalid)
+        return ira->codegen->builtin_types.entry_invalid;
+
+    IrInstruction *count_value = instruction->count->other;
+    if (count_value->type_entry->id == TypeTableEntryIdInvalid)
+        return ira->codegen->builtin_types.entry_invalid;
+
+    TypeTableEntry *child_type = ir_resolve_type(ira, type_value);
+    TypeTableEntry *canon_type = get_underlying_type(child_type);
+
+    if (count_value->static_value.special == ConstValSpecialStatic) {
+        // this should be the same as an array declaration
+
+        uint64_t count;
+        if (!ir_resolve_usize(ira, count_value, &count))
+            return ira->codegen->builtin_types.entry_invalid;
+
+        zig_panic("TODO alloca with compile time known count");
+    }
+
+    switch (canon_type->id) {
+        case TypeTableEntryIdInvalid:
+        case TypeTableEntryIdTypeDecl:
+            zig_unreachable();
+        case TypeTableEntryIdBool:
+        case TypeTableEntryIdVoid:
+        case TypeTableEntryIdInt:
+        case TypeTableEntryIdFloat:
+        case TypeTableEntryIdPointer:
+        case TypeTableEntryIdArray:
+        case TypeTableEntryIdStruct:
+        case TypeTableEntryIdMaybe:
+        case TypeTableEntryIdErrorUnion:
+        case TypeTableEntryIdPureError:
+        case TypeTableEntryIdEnum:
+        case TypeTableEntryIdUnion:
+        case TypeTableEntryIdFn:
+            {
+                TypeTableEntry *slice_type = get_slice_type(ira->codegen, child_type, false);
+                IrInstruction *new_instruction = ir_build_alloca_from(&ira->new_irb, &instruction->base, type_value, count_value);
+                ir_add_alloca(ira, new_instruction, slice_type);
+                return slice_type;
+            }
+        case TypeTableEntryIdVar:
+        case TypeTableEntryIdMetaType:
+        case TypeTableEntryIdUnreachable:
+        case TypeTableEntryIdNumLitFloat:
+        case TypeTableEntryIdNumLitInt:
+        case TypeTableEntryIdUndefLit:
+        case TypeTableEntryIdNullLit:
+        case TypeTableEntryIdNamespace:
+        case TypeTableEntryIdBlock:
+        case TypeTableEntryIdBoundFn:
+            ir_add_error(ira, type_value,
+                buf_sprintf("invalid alloca type '%s'", buf_ptr(&child_type->name)));
+            // TODO if this is a typedecl, add error note showing the declaration of the type decl
+            return ira->codegen->builtin_types.entry_invalid;
+    }
+    zig_unreachable();
+}
+
 static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstruction *instruction) {
     switch (instruction->id) {
         case IrInstructionIdInvalid:
@@ -7990,6 +8092,8 @@ static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructi
             return ir_analyze_instruction_int_type(ira, (IrInstructionIntType *)instruction);
         case IrInstructionIdBoolNot:
             return ir_analyze_instruction_bool_not(ira, (IrInstructionBoolNot *)instruction);
+        case IrInstructionIdAlloca:
+            return ir_analyze_instruction_alloca(ira, (IrInstructionAlloca *)instruction);
         case IrInstructionIdCast:
         case IrInstructionIdStructFieldPtr:
         case IrInstructionIdEnumFieldPtr:
@@ -8131,6 +8235,7 @@ bool ir_has_side_effects(IrInstruction *instruction) {
         case IrInstructionIdTruncate:
         case IrInstructionIdIntType:
         case IrInstructionIdBoolNot:
+        case IrInstructionIdAlloca:
             return false;
         case IrInstructionIdAsm:
             {
@@ -8998,115 +9103,3 @@ bool ir_has_side_effects(IrInstruction *instruction) {
 //    }
 //    zig_unreachable();
 //}
-//
-//static LLVMValueRef gen_var_decl_raw(CodeGen *g, AstNode *source_node, AstNodeVariableDeclaration *var_decl,
-//        bool unwrap_maybe, LLVMValueRef *init_value, TypeTableEntry **expr_type, bool var_is_ptr)
-//{
-//    VariableTableEntry *variable = var_decl->variable;
-//
-//    assert(variable);
-//
-//    if (var_decl->expr) {
-//        *init_value = gen_expr(g, var_decl->expr);
-//        *expr_type = get_expr_type(var_decl->expr);
-//    }
-//    if (!type_has_bits(variable->type)) {
-//        return nullptr;
-//    }
-//
-//    bool have_init_expr = false;
-//    bool want_zeroes = false;
-//    if (var_decl->expr) {
-//        ConstExprValue *const_val = &get_resolved_expr(var_decl->expr)->const_val;
-//        if (!const_val->ok || const_val->special == ConstValSpecialOther) {
-//            have_init_expr = true;
-//        }
-//        if (const_val->ok && const_val->special == ConstValSpecialZeroes) {
-//            want_zeroes = true;
-//        }
-//    }
-//    if (have_init_expr) {
-//        TypeTableEntry *expr_type = get_expr_type(var_decl->expr);
-//        LLVMValueRef value;
-//        if (unwrap_maybe) {
-//            assert(var_decl->expr);
-//            assert(expr_type->id == TypeTableEntryIdMaybe);
-//            value = gen_unwrap_maybe(g, var_decl->expr, *init_value);
-//            expr_type = expr_type->data.maybe.child_type;
-//        } else {
-//            value = *init_value;
-//        }
-//        gen_assign_raw(g, var_decl->expr, BinOpTypeAssign, variable->value_ref,
-//                value, variable->type, expr_type);
-//    } else {
-//        bool ignore_uninit = false;
-//        // handle runtime stack allocation
-//        if (var_decl->type) {
-//            TypeTableEntry *var_type = get_type_for_type_node(var_decl->type);
-//            if (var_type->id == TypeTableEntryIdStruct &&
-//                var_type->data.structure.is_slice)
-//            {
-//                assert(var_decl->type->type == NodeTypeArrayType);
-//                AstNode *size_node = var_decl->type->data.array_type.size;
-//                if (size_node) {
-//                    ConstExprValue *const_val = &get_resolved_expr(size_node)->const_val;
-//                    if (!const_val->ok) {
-//                        TypeTableEntry *ptr_type = var_type->data.structure.fields[0].type_entry;
-//                        assert(ptr_type->id == TypeTableEntryIdPointer);
-//                        TypeTableEntry *child_type = ptr_type->data.pointer.child_type;
-//
-//                        LLVMValueRef size_val = gen_expr(g, size_node);
-//
-//                        set_debug_source_node(g, source_node);
-//                        LLVMValueRef ptr_val = LLVMBuildArrayAlloca(g->builder, child_type->type_ref,
-//                                size_val, "");
-//
-//                        size_t ptr_index = var_type->data.structure.fields[0].gen_index;
-//                        assert(ptr_index != SIZE_MAX);
-//                        size_t len_index = var_type->data.structure.fields[1].gen_index;
-//                        assert(len_index != SIZE_MAX);
-//
-//                        // store the freshly allocated pointer in the unknown size array struct
-//                        LLVMValueRef ptr_field_ptr = LLVMBuildStructGEP(g->builder,
-//                                variable->value_ref, ptr_index, "");
-//                        LLVMBuildStore(g->builder, ptr_val, ptr_field_ptr);
-//
-//                        // store the size in the len field
-//                        LLVMValueRef len_field_ptr = LLVMBuildStructGEP(g->builder,
-//                                variable->value_ref, len_index, "");
-//                        LLVMBuildStore(g->builder, size_val, len_field_ptr);
-//
-//                        // don't clobber what we just did with debug initialization
-//                        ignore_uninit = true;
-//                    }
-//                }
-//            }
-//        }
-//        bool want_safe = want_debug_safety(g, source_node);
-//        if (!ignore_uninit && (want_safe || want_zeroes)) {
-//            TypeTableEntry *usize = g->builtin_types.entry_usize;
-//            uint64_t size_bytes = LLVMStoreSizeOfType(g->target_data_ref, variable->type->type_ref);
-//            uint64_t align_bytes = get_memcpy_align(g, variable->type);
-//
-//            // memset uninitialized memory to 0xa
-//            set_debug_source_node(g, source_node);
-//            LLVMTypeRef ptr_u8 = LLVMPointerType(LLVMInt8Type(), 0);
-//            LLVMValueRef fill_char = LLVMConstInt(LLVMInt8Type(), want_zeroes ? 0x00 : 0xaa, false);
-//            LLVMValueRef dest_ptr = LLVMBuildBitCast(g->builder, variable->value_ref, ptr_u8, "");
-//            LLVMValueRef byte_count = LLVMConstInt(usize->type_ref, size_bytes, false);
-//            LLVMValueRef align_in_bytes = LLVMConstInt(LLVMInt32Type(), align_bytes, false);
-//            LLVMValueRef params[] = {
-//                dest_ptr,
-//                fill_char,
-//                byte_count,
-//                align_in_bytes,
-//                LLVMConstNull(LLVMInt1Type()), // is volatile
-//            };
-//
-//            LLVMBuildCall(g->builder, g->memset_fn_val, params, 5, "");
-//        }
-//    }
-//
-//    gen_var_debug_decl(g, variable);
-//    return nullptr;
-//}
src/ir_print.cpp
@@ -753,6 +753,14 @@ static void ir_print_truncate(IrPrint *irp, IrInstructionTruncate *instruction)
     fprintf(irp->f, ")");
 }
 
+static void ir_print_alloca(IrPrint *irp, IrInstructionAlloca *instruction) {
+    fprintf(irp->f, "@alloca(");
+    ir_print_other_instruction(irp, instruction->type_value);
+    fprintf(irp->f, ", ");
+    ir_print_other_instruction(irp, instruction->count);
+    fprintf(irp->f, ")");
+}
+
 static void ir_print_int_type(IrPrint *irp, IrInstructionIntType *instruction) {
     fprintf(irp->f, "@intType(");
     ir_print_other_instruction(irp, instruction->is_signed);
@@ -942,6 +950,9 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) {
         case IrInstructionIdTruncate:
             ir_print_truncate(irp, (IrInstructionTruncate *)instruction);
             break;
+        case IrInstructionIdAlloca:
+            ir_print_alloca(irp, (IrInstructionAlloca *)instruction);
+            break;
         case IrInstructionIdIntType:
             ir_print_int_type(irp, (IrInstructionIntType *)instruction);
             break;