Commit fb21570630
Changed files (5)
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;