authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2019-03-15 17:47:47-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2019-03-15 17:57:21-04:00
log9c13e9b7ed9806d0f9774433d5e24359aff1b238
tree5e595fd2182648e660db56b88b86f162810df8f4
parent4090fe81f600afa290de5bf06a287d5fab2ea9dc
signaturelock-open Commit is signed but in an unrecognized format.

breaking changes to std.mem.Allocator interface API

Before, allocator implementations had to provide `allocFn`, `reallocFn`, and `freeFn`. Now, they must provide only `reallocFn` and `shrinkFn`. Reallocating from a zero length slice is allocation, and shrinking to a zero length slice is freeing. When the new memory size is less than or equal to the previous allocation size, `reallocFn` now has the option to return `error.OutOfMemory` to indicate that the allocator would not be able to take advantage of the new size. For more details see #1306. This commit closes #1306. This commit paves the way to solving #2009. This commit also introduces a memory leak to all coroutines. There is an issue where a coroutine calls the function and it frees its own stack frame, but then the return value of `shrinkFn` is a slice, which is implemented as an sret struct. Writing to the return pointer causes invalid memory write. We could work around it by having a global helper function which has a void return type and calling that instead. But instead this hack will suffice until I rework coroutines to be non-allocating. Basically coroutines are not supported right now until they are reworked as in #1194.

19 files changed, 374 insertions(+), 253 deletions(-)

src-self-hosted/ir.zig+1-1
......@@ -1364,7 +1364,7 @@ pub const Builder = struct {
13641364
13651365 if (str_token[0] == 'c') {
13661366 // first we add a null
1367 buf = try irb.comp.gpa().realloc(u8, buf, buf.len + 1);
1367 buf = try irb.comp.gpa().realloc(buf, buf.len + 1);
13681368 buf[buf.len - 1] = 0;
13691369
13701370 // next make an array value
src/all_types.hpp+3-3
......@@ -3356,7 +3356,7 @@ struct IrInstructionCoroPromise {
33563356struct IrInstructionCoroAllocHelper {
33573357 IrInstruction base;
33583358
3359 IrInstruction *alloc_fn;
3359 IrInstruction *realloc_fn;
33603360 IrInstruction *coro_size;
33613361};
33623362
......@@ -3481,8 +3481,8 @@ static const size_t stack_trace_ptr_count = 32;
34813481#define RETURN_ADDRESSES_FIELD_NAME "return_addresses"
34823482#define ERR_RET_TRACE_FIELD_NAME "err_ret_trace"
34833483#define RESULT_FIELD_NAME "result"
3484#define ASYNC_ALLOC_FIELD_NAME "allocFn"
3485#define ASYNC_FREE_FIELD_NAME "freeFn"
3484#define ASYNC_REALLOC_FIELD_NAME "reallocFn"
3485#define ASYNC_SHRINK_FIELD_NAME "shrinkFn"
34863486#define ATOMIC_STATE_FIELD_NAME "atomic_state"
34873487// these point to data belonging to the awaiter
34883488#define ERR_RET_TRACE_PTR_FIELD_NAME "err_ret_trace_ptr"
src/analyze.cpp+5-3
......@@ -3707,9 +3707,11 @@ ZigVar *add_variable(CodeGen *g, AstNode *source_node, Scope *parent_scope, Buf
37073707
37083708 ZigVar *existing_var = find_variable(g, parent_scope, name, nullptr);
37093709 if (existing_var && !existing_var->shadowable) {
3710 ErrorMsg *msg = add_node_error(g, source_node,
3711 buf_sprintf("redeclaration of variable '%s'", buf_ptr(name)));
3712 add_error_note(g, msg, existing_var->decl_node, buf_sprintf("previous declaration is here"));
3710 if (existing_var->var_type == nullptr || !type_is_invalid(existing_var->var_type)) {
3711 ErrorMsg *msg = add_node_error(g, source_node,
3712 buf_sprintf("redeclaration of variable '%s'", buf_ptr(name)));
3713 add_error_note(g, msg, existing_var->decl_node, buf_sprintf("previous declaration is here"));
3714 }
37133715 variable_entry->var_type = g->builtin_types.entry_invalid;
37143716 } else {
37153717 ZigType *type;
src/codegen.cpp+12-5
......@@ -5177,7 +5177,7 @@ static LLVMValueRef get_coro_alloc_helper_fn_val(CodeGen *g, LLVMTypeRef alloc_f
51775177 LLVMValueRef sret_ptr = LLVMBuildAlloca(g->builder, LLVMGetElementType(alloc_fn_arg_types[0]), "");
51785178
51795179 size_t next_arg = 0;
5180 LLVMValueRef alloc_fn_val = LLVMGetParam(fn_val, next_arg);
5180 LLVMValueRef realloc_fn_val = LLVMGetParam(fn_val, next_arg);
51815181 next_arg += 1;
51825182
51835183 LLVMValueRef stack_trace_val;
......@@ -5195,15 +5195,22 @@ static LLVMValueRef get_coro_alloc_helper_fn_val(CodeGen *g, LLVMTypeRef alloc_f
51955195 LLVMValueRef alignment_val = LLVMConstInt(g->builtin_types.entry_u29->type_ref,
51965196 get_coro_frame_align_bytes(g), false);
51975197
5198 ConstExprValue *zero_array = create_const_str_lit(g, buf_create_from_str(""));
5199 ConstExprValue *undef_slice_zero = create_const_slice(g, zero_array, 0, 0, false);
5200 render_const_val(g, undef_slice_zero, "");
5201 render_const_val_global(g, undef_slice_zero, "");
5202
51985203 ZigList<LLVMValueRef> args = {};
51995204 args.append(sret_ptr);
52005205 if (g->have_err_ret_tracing) {
52015206 args.append(stack_trace_val);
52025207 }
52035208 args.append(allocator_val);
5209 args.append(undef_slice_zero->global_refs->llvm_global);
5210 args.append(LLVMGetUndef(g->builtin_types.entry_u29->type_ref));
52045211 args.append(coro_size);
52055212 args.append(alignment_val);
5206 LLVMValueRef call_instruction = ZigLLVMBuildCall(g->builder, alloc_fn_val, args.items, args.length,
5213 LLVMValueRef call_instruction = ZigLLVMBuildCall(g->builder, realloc_fn_val, args.items, args.length,
52075214 get_llvm_cc(g, CallingConventionUnspecified), ZigLLVM_FnInlineAuto, "");
52085215 set_call_instr_sret(g, call_instruction);
52095216 LLVMValueRef err_val_ptr = LLVMBuildStructGEP(g->builder, sret_ptr, err_union_err_index, "");
......@@ -5239,14 +5246,14 @@ static LLVMValueRef get_coro_alloc_helper_fn_val(CodeGen *g, LLVMTypeRef alloc_f
52395246static LLVMValueRef ir_render_coro_alloc_helper(CodeGen *g, IrExecutable *executable,
52405247 IrInstructionCoroAllocHelper *instruction)
52415248{
5242 LLVMValueRef alloc_fn = ir_llvm_value(g, instruction->alloc_fn);
5249 LLVMValueRef realloc_fn = ir_llvm_value(g, instruction->realloc_fn);
52435250 LLVMValueRef coro_size = ir_llvm_value(g, instruction->coro_size);
5244 LLVMValueRef fn_val = get_coro_alloc_helper_fn_val(g, LLVMTypeOf(alloc_fn), instruction->alloc_fn->value.type);
5251 LLVMValueRef fn_val = get_coro_alloc_helper_fn_val(g, LLVMTypeOf(realloc_fn), instruction->realloc_fn->value.type);
52455252 size_t err_code_ptr_arg_index = get_async_err_code_arg_index(g, &g->cur_fn->type_entry->data.fn.fn_type_id);
52465253 size_t allocator_arg_index = get_async_allocator_arg_index(g, &g->cur_fn->type_entry->data.fn.fn_type_id);
52475254
52485255 ZigList<LLVMValueRef> params = {};
5249 params.append(alloc_fn);
5256 params.append(realloc_fn);
52505257 uint32_t err_ret_trace_arg_index = get_err_ret_trace_arg_index(g, g->cur_fn);
52515258 if (err_ret_trace_arg_index != UINT32_MAX) {
52525259 params.append(LLVMGetParam(g->cur_fn_val, err_ret_trace_arg_index));
src/ir.cpp+40-30
......@@ -2788,13 +2788,13 @@ static IrInstruction *ir_build_coro_promise(IrBuilder *irb, Scope *scope, AstNod
27882788}
27892789
27902790static IrInstruction *ir_build_coro_alloc_helper(IrBuilder *irb, Scope *scope, AstNode *source_node,
2791 IrInstruction *alloc_fn, IrInstruction *coro_size)
2791 IrInstruction *realloc_fn, IrInstruction *coro_size)
27922792{
27932793 IrInstructionCoroAllocHelper *instruction = ir_build_instruction<IrInstructionCoroAllocHelper>(irb, scope, source_node);
2794 instruction->alloc_fn = alloc_fn;
2794 instruction->realloc_fn = realloc_fn;
27952795 instruction->coro_size = coro_size;
27962796
2797 ir_ref_instruction(alloc_fn, irb->current_basic_block);
2797 ir_ref_instruction(realloc_fn, irb->current_basic_block);
27982798 ir_ref_instruction(coro_size, irb->current_basic_block);
27992799
28002800 return &instruction->base;
......@@ -3319,9 +3319,11 @@ static ZigVar *create_local_var(CodeGen *codegen, AstNode *node, Scope *parent_s
33193319 if (!skip_name_check) {
33203320 ZigVar *existing_var = find_variable(codegen, parent_scope, name, nullptr);
33213321 if (existing_var && !existing_var->shadowable) {
3322 ErrorMsg *msg = add_node_error(codegen, node,
3323 buf_sprintf("redeclaration of variable '%s'", buf_ptr(name)));
3324 add_error_note(codegen, msg, existing_var->decl_node, buf_sprintf("previous declaration is here"));
3322 if (existing_var->var_type == nullptr || !type_is_invalid(existing_var->var_type)) {
3323 ErrorMsg *msg = add_node_error(codegen, node,
3324 buf_sprintf("redeclaration of variable '%s'", buf_ptr(name)));
3325 add_error_note(codegen, msg, existing_var->decl_node, buf_sprintf("previous declaration is here"));
3326 }
33253327 variable_entry->var_type = codegen->builtin_types.entry_invalid;
33263328 } else {
33273329 ZigType *type;
......@@ -7506,10 +7508,10 @@ bool ir_gen(CodeGen *codegen, AstNode *node, Scope *scope, IrExecutable *ir_exec
75067508 ImplicitAllocatorIdArg);
75077509 irb->exec->coro_allocator_var = ir_create_var(irb, node, coro_scope, nullptr, true, true, true, const_bool_false);
75087510 ir_build_var_decl_src(irb, coro_scope, node, irb->exec->coro_allocator_var, nullptr, nullptr, implicit_allocator_ptr);
7509 Buf *alloc_field_name = buf_create_from_str(ASYNC_ALLOC_FIELD_NAME);
7510 IrInstruction *alloc_fn_ptr = ir_build_field_ptr(irb, coro_scope, node, implicit_allocator_ptr, alloc_field_name);
7511 IrInstruction *alloc_fn = ir_build_load_ptr(irb, coro_scope, node, alloc_fn_ptr);
7512 IrInstruction *maybe_coro_mem_ptr = ir_build_coro_alloc_helper(irb, coro_scope, node, alloc_fn, coro_size);
7511 Buf *realloc_field_name = buf_create_from_str(ASYNC_REALLOC_FIELD_NAME);
7512 IrInstruction *realloc_fn_ptr = ir_build_field_ptr(irb, coro_scope, node, implicit_allocator_ptr, realloc_field_name);
7513 IrInstruction *realloc_fn = ir_build_load_ptr(irb, coro_scope, node, realloc_fn_ptr);
7514 IrInstruction *maybe_coro_mem_ptr = ir_build_coro_alloc_helper(irb, coro_scope, node, realloc_fn, coro_size);
75137515 IrInstruction *alloc_result_is_ok = ir_build_test_nonnull(irb, coro_scope, node, maybe_coro_mem_ptr);
75147516 IrBasicBlock *alloc_err_block = ir_create_basic_block(irb, coro_scope, "AllocError");
75157517 IrBasicBlock *alloc_ok_block = ir_create_basic_block(irb, coro_scope, "AllocOk");
......@@ -7643,11 +7645,11 @@ bool ir_gen(CodeGen *codegen, AstNode *node, Scope *scope, IrExecutable *ir_exec
76437645 merge_incoming_values[1] = await_handle_in_block;
76447646 IrInstruction *awaiter_handle = ir_build_phi(irb, scope, node, 2, merge_incoming_blocks, merge_incoming_values);
76457647
7646 Buf *free_field_name = buf_create_from_str(ASYNC_FREE_FIELD_NAME);
7648 Buf *shrink_field_name = buf_create_from_str(ASYNC_SHRINK_FIELD_NAME);
76477649 IrInstruction *implicit_allocator_ptr = ir_build_get_implicit_allocator(irb, scope, node,
76487650 ImplicitAllocatorIdLocalVar);
7649 IrInstruction *free_fn_ptr = ir_build_field_ptr(irb, scope, node, implicit_allocator_ptr, free_field_name);
7650 IrInstruction *free_fn = ir_build_load_ptr(irb, scope, node, free_fn_ptr);
7651 IrInstruction *shrink_fn_ptr = ir_build_field_ptr(irb, scope, node, implicit_allocator_ptr, shrink_field_name);
7652 IrInstruction *shrink_fn = ir_build_load_ptr(irb, scope, node, shrink_fn_ptr);
76517653 IrInstruction *zero = ir_build_const_usize(irb, scope, node, 0);
76527654 IrInstruction *coro_mem_ptr_maybe = ir_build_coro_free(irb, scope, node, coro_id, irb->exec->coro_handle);
76537655 IrInstruction *u8_ptr_type_unknown_len = ir_build_const_type(irb, scope, node,
......@@ -7659,11 +7661,20 @@ bool ir_gen(CodeGen *codegen, AstNode *node, Scope *scope, IrExecutable *ir_exec
76597661 IrInstruction *coro_size_ptr = ir_build_var_ptr(irb, scope, node, coro_size_var);
76607662 IrInstruction *coro_size = ir_build_load_ptr(irb, scope, node, coro_size_ptr);
76617663 IrInstruction *mem_slice = ir_build_slice(irb, scope, node, coro_mem_ptr_ref, zero, coro_size, false);
7662 size_t arg_count = 2;
7664 size_t arg_count = 5;
76637665 IrInstruction **args = allocate<IrInstruction *>(arg_count);
76647666 args[0] = implicit_allocator_ptr; // self
76657667 args[1] = mem_slice; // old_mem
7666 ir_build_call(irb, scope, node, nullptr, free_fn, arg_count, args, false, FnInlineAuto, false, nullptr, nullptr);
7668 args[2] = ir_build_const_usize(irb, scope, node, 8); // old_align
7669 // TODO: intentional memory leak here. If this is set to 0 then there is an issue where a coroutine
7670 // calls the function and it frees its own stack frame, but then the return value is a slice, which
7671 // is implemented as an sret struct. writing to the return pointer causes invalid memory write.
7672 // We could work around it by having a global helper function which has a void return type
7673 // and calling that instead. But instead this hack will suffice until I rework coroutines to be
7674 // non-allocating. Basically coroutines are not supported right now until they are reworked.
7675 args[3] = ir_build_const_usize(irb, scope, node, 1); // new_size
7676 args[4] = ir_build_const_usize(irb, scope, node, 1); // new_align
7677 ir_build_call(irb, scope, node, nullptr, shrink_fn, arg_count, args, false, FnInlineAuto, false, nullptr, nullptr);
76677678
76687679 IrBasicBlock *resume_block = ir_create_basic_block(irb, scope, "Resume");
76697680 ir_build_cond_br(irb, scope, node, resume_awaiter, resume_block, irb->exec->coro_suspend_block, const_bool_false);
......@@ -13574,32 +13585,31 @@ IrInstruction *ir_get_implicit_allocator(IrAnalyze *ira, IrInstruction *source_i
1357413585static IrInstruction *ir_analyze_async_call(IrAnalyze *ira, IrInstructionCall *call_instruction, ZigFn *fn_entry, ZigType *fn_type,
1357513586 IrInstruction *fn_ref, IrInstruction **casted_args, size_t arg_count, IrInstruction *async_allocator_inst)
1357613587{
13577 Buf *alloc_field_name = buf_create_from_str(ASYNC_ALLOC_FIELD_NAME);
13578 //Buf *free_field_name = buf_create_from_str("freeFn");
13588 Buf *realloc_field_name = buf_create_from_str(ASYNC_REALLOC_FIELD_NAME);
1357913589 assert(async_allocator_inst->value.type->id == ZigTypeIdPointer);
1358013590 ZigType *container_type = async_allocator_inst->value.type->data.pointer.child_type;
13581 IrInstruction *field_ptr_inst = ir_analyze_container_field_ptr(ira, alloc_field_name, &call_instruction->base,
13591 IrInstruction *field_ptr_inst = ir_analyze_container_field_ptr(ira, realloc_field_name, &call_instruction->base,
1358213592 async_allocator_inst, container_type);
1358313593 if (type_is_invalid(field_ptr_inst->value.type)) {
1358413594 return ira->codegen->invalid_instruction;
1358513595 }
13586 ZigType *ptr_to_alloc_fn_type = field_ptr_inst->value.type;
13587 assert(ptr_to_alloc_fn_type->id == ZigTypeIdPointer);
13596 ZigType *ptr_to_realloc_fn_type = field_ptr_inst->value.type;
13597 assert(ptr_to_realloc_fn_type->id == ZigTypeIdPointer);
1358813598
13589 ZigType *alloc_fn_type = ptr_to_alloc_fn_type->data.pointer.child_type;
13590 if (alloc_fn_type->id != ZigTypeIdFn) {
13599 ZigType *realloc_fn_type = ptr_to_realloc_fn_type->data.pointer.child_type;
13600 if (realloc_fn_type->id != ZigTypeIdFn) {
1359113601 ir_add_error(ira, &call_instruction->base,
13592 buf_sprintf("expected allocation function, found '%s'", buf_ptr(&alloc_fn_type->name)));
13602 buf_sprintf("expected reallocation function, found '%s'", buf_ptr(&realloc_fn_type->name)));
1359313603 return ira->codegen->invalid_instruction;
1359413604 }
1359513605
13596 ZigType *alloc_fn_return_type = alloc_fn_type->data.fn.fn_type_id.return_type;
13597 if (alloc_fn_return_type->id != ZigTypeIdErrorUnion) {
13606 ZigType *realloc_fn_return_type = realloc_fn_type->data.fn.fn_type_id.return_type;
13607 if (realloc_fn_return_type->id != ZigTypeIdErrorUnion) {
1359813608 ir_add_error(ira, fn_ref,
13599 buf_sprintf("expected allocation function to return error union, but it returns '%s'", buf_ptr(&alloc_fn_return_type->name)));
13609 buf_sprintf("expected allocation function to return error union, but it returns '%s'", buf_ptr(&realloc_fn_return_type->name)));
1360013610 return ira->codegen->invalid_instruction;
1360113611 }
13602 ZigType *alloc_fn_error_set_type = alloc_fn_return_type->data.error_union.err_set_type;
13612 ZigType *alloc_fn_error_set_type = realloc_fn_return_type->data.error_union.err_set_type;
1360313613 ZigType *return_type = fn_type->data.fn.fn_type_id.return_type;
1360413614 ZigType *promise_type = get_promise_type(ira->codegen, return_type);
1360513615 ZigType *async_return_type = get_error_union_type(ira->codegen, alloc_fn_error_set_type, promise_type);
......@@ -22033,8 +22043,8 @@ static IrInstruction *ir_analyze_instruction_coro_promise(IrAnalyze *ira, IrInst
2203322043}
2203422044
2203522045static IrInstruction *ir_analyze_instruction_coro_alloc_helper(IrAnalyze *ira, IrInstructionCoroAllocHelper *instruction) {
22036 IrInstruction *alloc_fn = instruction->alloc_fn->child;
22037 if (type_is_invalid(alloc_fn->value.type))
22046 IrInstruction *realloc_fn = instruction->realloc_fn->child;
22047 if (type_is_invalid(realloc_fn->value.type))
2203822048 return ira->codegen->invalid_instruction;
2203922049
2204022050 IrInstruction *coro_size = instruction->coro_size->child;
......@@ -22042,7 +22052,7 @@ static IrInstruction *ir_analyze_instruction_coro_alloc_helper(IrAnalyze *ira, I
2204222052 return ira->codegen->invalid_instruction;
2204322053
2204422054 IrInstruction *result = ir_build_coro_alloc_helper(&ira->new_irb, instruction->base.scope,
22045 instruction->base.source_node, alloc_fn, coro_size);
22055 instruction->base.source_node, realloc_fn, coro_size);
2204622056 ZigType *u8_ptr_type = get_pointer_to_type(ira->codegen, ira->codegen->builtin_types.entry_u8, false);
2204722057 result->value.type = get_optional_type(ira->codegen, u8_ptr_type);
2204822058 return result;
src/ir_print.cpp+1-1
......@@ -1286,7 +1286,7 @@ static void ir_print_promise_result_type(IrPrint *irp, IrInstructionPromiseResul
12861286
12871287static void ir_print_coro_alloc_helper(IrPrint *irp, IrInstructionCoroAllocHelper *instruction) {
12881288 fprintf(irp->f, "@coroAllocHelper(");
1289 ir_print_other_instruction(irp, instruction->alloc_fn);
1289 ir_print_other_instruction(irp, instruction->realloc_fn);
12901290 fprintf(irp->f, ",");
12911291 ir_print_other_instruction(irp, instruction->coro_size);
12921292 fprintf(irp->f, ")");
std/array_list.zig+5-2
......@@ -80,7 +80,7 @@ pub fn AlignedArrayList(comptime T: type, comptime A: u29) type {
8080 /// The caller owns the returned memory. ArrayList becomes empty.
8181 pub fn toOwnedSlice(self: *Self) []align(A) T {
8282 const allocator = self.allocator;
83 const result = allocator.alignedShrink(T, A, self.items, self.len);
83 const result = allocator.shrink(self.items, self.len);
8484 self.* = init(allocator);
8585 return result;
8686 }
......@@ -144,6 +144,9 @@ pub fn AlignedArrayList(comptime T: type, comptime A: u29) type {
144144 pub fn shrink(self: *Self, new_len: usize) void {
145145 assert(new_len <= self.len);
146146 self.len = new_len;
147 self.items = self.allocator.realloc(self.items, new_len) catch |e| switch (e) {
148 error.OutOfMemory => return, // no problem, capacity is still correct then.
149 };
147150 }
148151
149152 pub fn ensureCapacity(self: *Self, new_capacity: usize) !void {
......@@ -153,7 +156,7 @@ pub fn AlignedArrayList(comptime T: type, comptime A: u29) type {
153156 better_capacity += better_capacity / 2 + 8;
154157 if (better_capacity >= new_capacity) break;
155158 }
156 self.items = try self.allocator.alignedRealloc(T, A, self.items, better_capacity);
159 self.items = try self.allocator.realloc(self.items, better_capacity);
157160 }
158161
159162 pub fn addOne(self: *Self) !*T {
std/buffer.zig+1-1
......@@ -50,7 +50,7 @@ pub const Buffer = struct {
5050 /// is safe to `deinit`.
5151 pub fn toOwnedSlice(self: *Buffer) []u8 {
5252 const allocator = self.list.allocator;
53 const result = allocator.shrink(u8, self.list.items, self.len());
53 const result = allocator.shrink(self.list.items, self.len());
5454 self.* = initNull(allocator);
5555 return result;
5656 }
std/c.zig+1-1
......@@ -53,7 +53,7 @@ pub extern "c" fn rmdir(path: [*]const u8) c_int;
5353
5454pub extern "c" fn aligned_alloc(alignment: usize, size: usize) ?*c_void;
5555pub extern "c" fn malloc(usize) ?*c_void;
56pub extern "c" fn realloc(*c_void, usize) ?*c_void;
56pub extern "c" fn realloc(?*c_void, usize) ?*c_void;
5757pub extern "c" fn free(*c_void) void;
5858pub extern "c" fn posix_memalign(memptr: **c_void, alignment: usize, size: usize) c_int;
5959
std/debug.zig+1-1
......@@ -1072,7 +1072,7 @@ fn openSelfDebugInfoMacOs(allocator: *mem.Allocator) !DebugInfo {
10721072 .n_value = symbols_buf[symbol_index - 1].nlist.n_value + last_len,
10731073 };
10741074
1075 const symbols = allocator.shrink(MachoSymbol, symbols_buf, symbol_index);
1075 const symbols = allocator.shrink(symbols_buf, symbol_index);
10761076
10771077 // Even though lld emits symbols in ascending order, this debug code
10781078 // should work for programs linked in any valid way.
std/debug/failing_allocator.zig+14-21
......@@ -21,44 +21,37 @@ pub const FailingAllocator = struct {
2121 .freed_bytes = 0,
2222 .deallocations = 0,
2323 .allocator = mem.Allocator{
24 .allocFn = alloc,
2524 .reallocFn = realloc,
26 .freeFn = free,
25 .shrinkFn = shrink,
2726 },
2827 };
2928 }
3029
31 fn alloc(allocator: *mem.Allocator, n: usize, alignment: u29) ![]u8 {
30 fn realloc(allocator: *mem.Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 {
3231 const self = @fieldParentPtr(FailingAllocator, "allocator", allocator);
3332 if (self.index == self.fail_index) {
3433 return error.OutOfMemory;
3534 }
36 const result = try self.internal_allocator.allocFn(self.internal_allocator, n, alignment);
37 self.allocated_bytes += result.len;
38 self.index += 1;
39 return result;
40 }
41
42 fn realloc(allocator: *mem.Allocator, old_mem: []u8, new_size: usize, alignment: u29) ![]u8 {
43 const self = @fieldParentPtr(FailingAllocator, "allocator", allocator);
35 const result = try self.internal_allocator.reallocFn(
36 self.internal_allocator,
37 old_mem,
38 old_align,
39 new_size,
40 new_align,
41 );
4442 if (new_size <= old_mem.len) {
4543 self.freed_bytes += old_mem.len - new_size;
46 return self.internal_allocator.reallocFn(self.internal_allocator, old_mem, new_size, alignment);
44 } else {
45 self.allocated_bytes += new_size - old_mem.len;
4746 }
48 if (self.index == self.fail_index) {
49 return error.OutOfMemory;
50 }
51 const result = try self.internal_allocator.reallocFn(self.internal_allocator, old_mem, new_size, alignment);
52 self.allocated_bytes += new_size - old_mem.len;
5347 self.deallocations += 1;
5448 self.index += 1;
5549 return result;
5650 }
5751
58 fn free(allocator: *mem.Allocator, bytes: []u8) void {
52 fn shrink(allocator: *mem.Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 {
5953 const self = @fieldParentPtr(FailingAllocator, "allocator", allocator);
60 self.freed_bytes += bytes.len;
61 self.deallocations += 1;
62 return self.internal_allocator.freeFn(self.internal_allocator, bytes);
54 self.freed_bytes += old_mem.len - new_size;
55 return self.internal_allocator.shrinkFn(self.internal_allocator, old_mem, old_align, new_size, new_align);
6356 }
6457};
std/heap.zig+133-119
......@@ -13,30 +13,21 @@ const Allocator = mem.Allocator;
1313
1414pub const c_allocator = &c_allocator_state;
1515var c_allocator_state = Allocator{
16 .allocFn = cAlloc,
1716 .reallocFn = cRealloc,
18 .freeFn = cFree,
17 .shrinkFn = cShrink,
1918};
2019
21fn cAlloc(self: *Allocator, n: usize, alignment: u29) ![]u8 {
22 assert(alignment <= @alignOf(c_longdouble));
23 return if (c.malloc(n)) |buf| @ptrCast([*]u8, buf)[0..n] else error.OutOfMemory;
20fn cRealloc(self: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 {
21 assert(new_align <= @alignOf(c_longdouble));
22 const old_ptr = if (old_mem.len == 0) null else @ptrCast(*c_void, old_mem.ptr);
23 const buf = c.realloc(old_ptr, new_size) orelse return error.OutOfMemory;
24 return @ptrCast([*]u8, buf)[0..new_size];
2425}
2526
26fn cRealloc(self: *Allocator, old_mem: []u8, new_size: usize, alignment: u29) ![]u8 {
27fn cShrink(self: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 {
2728 const old_ptr = @ptrCast(*c_void, old_mem.ptr);
28 if (c.realloc(old_ptr, new_size)) |buf| {
29 return @ptrCast([*]u8, buf)[0..new_size];
30 } else if (new_size <= old_mem.len) {
31 return old_mem[0..new_size];
32 } else {
33 return error.OutOfMemory;
34 }
35}
36
37fn cFree(self: *Allocator, old_mem: []u8) void {
38 const old_ptr = @ptrCast(*c_void, old_mem.ptr);
39 c.free(old_ptr);
29 const buf = c.realloc(old_ptr, new_size) orelse return old_mem[0..new_size];
30 return @ptrCast([*]u8, buf)[0..new_size];
4031}
4132
4233/// This allocator makes a syscall directly for every allocation and free.
......@@ -50,9 +41,8 @@ pub const DirectAllocator = struct {
5041 pub fn init() DirectAllocator {
5142 return DirectAllocator{
5243 .allocator = Allocator{
53 .allocFn = alloc,
5444 .reallocFn = realloc,
55 .freeFn = free,
45 .shrinkFn = shrink,
5646 },
5747 .heap_handle = if (builtin.os == Os.windows) null else {},
5848 };
......@@ -116,42 +106,60 @@ pub const DirectAllocator = struct {
116106 }
117107 }
118108
119 fn realloc(allocator: *Allocator, old_mem: []u8, new_size: usize, alignment: u29) ![]u8 {
120 const self = @fieldParentPtr(DirectAllocator, "allocator", allocator);
121
109 fn shrink(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 {
122110 switch (builtin.os) {
123111 Os.linux, Os.macosx, Os.ios, Os.freebsd, Os.netbsd => {
124 if (new_size <= old_mem.len) {
125 const base_addr = @ptrToInt(old_mem.ptr);
126 const old_addr_end = base_addr + old_mem.len;
127 const new_addr_end = base_addr + new_size;
128 const new_addr_end_rounded = mem.alignForward(new_addr_end, os.page_size);
129 if (old_addr_end > new_addr_end_rounded) {
130 _ = os.posix.munmap(new_addr_end_rounded, old_addr_end - new_addr_end_rounded);
131 }
132 return old_mem[0..new_size];
112 const base_addr = @ptrToInt(old_mem.ptr);
113 const old_addr_end = base_addr + old_mem.len;
114 const new_addr_end = base_addr + new_size;
115 const new_addr_end_rounded = mem.alignForward(new_addr_end, os.page_size);
116 if (old_addr_end > new_addr_end_rounded) {
117 _ = os.posix.munmap(new_addr_end_rounded, old_addr_end - new_addr_end_rounded);
133118 }
119 return old_mem[0..new_size];
120 },
121 Os.windows => return realloc(allocator, old_mem, old_align, new_size, new_align) catch {
122 const old_adjusted_addr = @ptrToInt(old_mem.ptr);
123 const old_record_addr = old_adjusted_addr + old_mem.len;
124 const root_addr = @intToPtr(*align(1) usize, old_record_addr).*;
125 const old_ptr = @intToPtr(*c_void, root_addr);
126 const new_record_addr = old_record_addr - new_size + old_mem.len;
127 @intToPtr(*align(1) usize, new_record_addr).* = root_addr;
128 return old_mem[0..new_size];
129 },
130 else => @compileError("Unsupported OS"),
131 }
132 }
134133
135 const result = try alloc(allocator, new_size, alignment);
134 fn realloc(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 {
135 switch (builtin.os) {
136 Os.linux, Os.macosx, Os.ios, Os.freebsd, Os.netbsd => {
137 if (new_size <= old_mem.len and new_align <= old_align) {
138 return shrink(allocator, old_mem, old_align, new_size, new_align);
139 }
140 const result = try alloc(allocator, new_size, new_align);
136141 mem.copy(u8, result, old_mem);
142 _ = os.posix.munmap(@ptrToInt(old_mem.ptr), old_mem.len);
137143 return result;
138144 },
139145 Os.windows => {
146 const self = @fieldParentPtr(DirectAllocator, "allocator", allocator);
147
140148 const old_adjusted_addr = @ptrToInt(old_mem.ptr);
141149 const old_record_addr = old_adjusted_addr + old_mem.len;
142150 const root_addr = @intToPtr(*align(1) usize, old_record_addr).*;
143151 const old_ptr = @intToPtr(*c_void, root_addr);
144 const amt = new_size + alignment + @sizeOf(usize);
145 const new_ptr = os.windows.HeapReAlloc(self.heap_handle.?, 0, old_ptr, amt) orelse blk: {
146 if (new_size > old_mem.len) return error.OutOfMemory;
147 const new_record_addr = old_record_addr - new_size + old_mem.len;
148 @intToPtr(*align(1) usize, new_record_addr).* = root_addr;
149 return old_mem[0..new_size];
150 };
152 const amt = new_size + new_align + @sizeOf(usize);
153 const new_ptr = os.windows.HeapReAlloc(
154 self.heap_handle.?,
155 0,
156 old_ptr,
157 amt,
158 ) orelse return error.OutOfMemory;
151159 const offset = old_adjusted_addr - root_addr;
152160 const new_root_addr = @ptrToInt(new_ptr);
153161 const new_adjusted_addr = new_root_addr + offset;
154 assert(new_adjusted_addr % alignment == 0);
162 assert(new_adjusted_addr % new_align == 0);
155163 const new_record_addr = new_adjusted_addr + new_size;
156164 @intToPtr(*align(1) usize, new_record_addr).* = new_root_addr;
157165 return @intToPtr([*]u8, new_adjusted_addr)[0..new_size];
......@@ -159,23 +167,6 @@ pub const DirectAllocator = struct {
159167 else => @compileError("Unsupported OS"),
160168 }
161169 }
162
163 fn free(allocator: *Allocator, bytes: []u8) void {
164 const self = @fieldParentPtr(DirectAllocator, "allocator", allocator);
165
166 switch (builtin.os) {
167 Os.linux, Os.macosx, Os.ios, Os.freebsd, Os.netbsd => {
168 _ = os.posix.munmap(@ptrToInt(bytes.ptr), bytes.len);
169 },
170 Os.windows => {
171 const record_addr = @ptrToInt(bytes.ptr) + bytes.len;
172 const root_addr = @intToPtr(*align(1) usize, record_addr).*;
173 const ptr = @intToPtr(*c_void, root_addr);
174 _ = os.windows.HeapFree(self.heap_handle.?, 0, ptr);
175 },
176 else => @compileError("Unsupported OS"),
177 }
178 }
179170};
180171
181172/// This allocator takes an existing allocator, wraps it, and provides an interface
......@@ -192,9 +183,8 @@ pub const ArenaAllocator = struct {
192183 pub fn init(child_allocator: *Allocator) ArenaAllocator {
193184 return ArenaAllocator{
194185 .allocator = Allocator{
195 .allocFn = alloc,
196186 .reallocFn = realloc,
197 .freeFn = free,
187 .shrinkFn = shrink,
198188 },
199189 .child_allocator = child_allocator,
200190 .buffer_list = std.LinkedList([]u8).init(),
......@@ -253,17 +243,20 @@ pub const ArenaAllocator = struct {
253243 }
254244 }
255245
256 fn realloc(allocator: *Allocator, old_mem: []u8, new_size: usize, alignment: u29) ![]u8 {
257 if (new_size <= old_mem.len) {
258 return old_mem[0..new_size];
246 fn realloc(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 {
247 if (new_size <= old_mem.len and new_align <= new_size) {
248 // We can't do anything with the memory, so tell the client to keep it.
249 return error.OutOfMemory;
259250 } else {
260 const result = try alloc(allocator, new_size, alignment);
251 const result = try alloc(allocator, new_size, new_align);
261252 mem.copy(u8, result, old_mem);
262253 return result;
263254 }
264255 }
265256
266 fn free(allocator: *Allocator, bytes: []u8) void {}
257 fn shrink(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 {
258 return old_mem[0..new_size];
259 }
267260};
268261
269262pub const FixedBufferAllocator = struct {
......@@ -274,9 +267,8 @@ pub const FixedBufferAllocator = struct {
274267 pub fn init(buffer: []u8) FixedBufferAllocator {
275268 return FixedBufferAllocator{
276269 .allocator = Allocator{
277 .allocFn = alloc,
278270 .reallocFn = realloc,
279 .freeFn = free,
271 .shrinkFn = shrink,
280272 },
281273 .buffer = buffer,
282274 .end_index = 0,
......@@ -298,26 +290,31 @@ pub const FixedBufferAllocator = struct {
298290 return result;
299291 }
300292
301 fn realloc(allocator: *Allocator, old_mem: []u8, new_size: usize, alignment: u29) ![]u8 {
293 fn realloc(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 {
302294 const self = @fieldParentPtr(FixedBufferAllocator, "allocator", allocator);
303295 assert(old_mem.len <= self.end_index);
304 if (new_size <= old_mem.len) {
305 return old_mem[0..new_size];
306 } else if (old_mem.ptr == self.buffer.ptr + self.end_index - old_mem.len) {
296 if (old_mem.ptr == self.buffer.ptr + self.end_index - old_mem.len and
297 mem.alignForward(@ptrToInt(old_mem.ptr), new_align) == @ptrToInt(old_mem.ptr))
298 {
307299 const start_index = self.end_index - old_mem.len;
308300 const new_end_index = start_index + new_size;
309301 if (new_end_index > self.buffer.len) return error.OutOfMemory;
310302 const result = self.buffer[start_index..new_end_index];
311303 self.end_index = new_end_index;
312304 return result;
305 } else if (new_size <= old_mem.len and new_align <= old_align) {
306 // We can't do anything with the memory, so tell the client to keep it.
307 return error.OutOfMemory;
313308 } else {
314 const result = try alloc(allocator, new_size, alignment);
309 const result = try alloc(allocator, new_size, new_align);
315310 mem.copy(u8, result, old_mem);
316311 return result;
317312 }
318313 }
319314
320 fn free(allocator: *Allocator, bytes: []u8) void {}
315 fn shrink(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 {
316 return old_mem[0..new_size];
317 }
321318};
322319
323320pub const ThreadSafeFixedBufferAllocator = blk: {
......@@ -333,9 +330,8 @@ pub const ThreadSafeFixedBufferAllocator = blk: {
333330 pub fn init(buffer: []u8) ThreadSafeFixedBufferAllocator {
334331 return ThreadSafeFixedBufferAllocator{
335332 .allocator = Allocator{
336 .allocFn = alloc,
337333 .reallocFn = realloc,
338 .freeFn = free,
334 .shrinkFn = shrink,
339335 },
340336 .buffer = buffer,
341337 .end_index = 0,
......@@ -357,17 +353,20 @@ pub const ThreadSafeFixedBufferAllocator = blk: {
357353 }
358354 }
359355
360 fn realloc(allocator: *Allocator, old_mem: []u8, new_size: usize, alignment: u29) ![]u8 {
361 if (new_size <= old_mem.len) {
362 return old_mem[0..new_size];
356 fn realloc(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 {
357 if (new_size <= old_mem.len and new_align <= old_align) {
358 // We can't do anything useful with the memory, tell the client to keep it.
359 return error.OutOfMemory;
363360 } else {
364 const result = try alloc(allocator, new_size, alignment);
361 const result = try alloc(allocator, new_size, new_align);
365362 mem.copy(u8, result, old_mem);
366363 return result;
367364 }
368365 }
369366
370 fn free(allocator: *Allocator, bytes: []u8) void {}
367 fn shrink(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 {
368 return old_mem[0..new_size];
369 }
371370 };
372371 }
373372};
......@@ -378,9 +377,8 @@ pub fn stackFallback(comptime size: usize, fallback_allocator: *Allocator) Stack
378377 .fallback_allocator = fallback_allocator,
379378 .fixed_buffer_allocator = undefined,
380379 .allocator = Allocator{
381 .allocFn = StackFallbackAllocator(size).alloc,
382380 .reallocFn = StackFallbackAllocator(size).realloc,
383 .freeFn = StackFallbackAllocator(size).free,
381 .shrinkFn = StackFallbackAllocator(size).shrink,
384382 },
385383 };
386384}
......@@ -399,13 +397,7 @@ pub fn StackFallbackAllocator(comptime size: usize) type {
399397 return &self.allocator;
400398 }
401399
402 fn alloc(allocator: *Allocator, n: usize, alignment: u29) ![]u8 {
403 const self = @fieldParentPtr(Self, "allocator", allocator);
404 return FixedBufferAllocator.alloc(&self.fixed_buffer_allocator.allocator, n, alignment) catch
405 self.fallback_allocator.allocFn(self.fallback_allocator, n, alignment);
406 }
407
408 fn realloc(allocator: *Allocator, old_mem: []u8, new_size: usize, alignment: u29) ![]u8 {
400 fn realloc(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 {
409401 const self = @fieldParentPtr(Self, "allocator", allocator);
410402 const in_buffer = @ptrToInt(old_mem.ptr) >= @ptrToInt(&self.buffer) and
411403 @ptrToInt(old_mem.ptr) < @ptrToInt(&self.buffer) + self.buffer.len;
......@@ -413,37 +405,59 @@ pub fn StackFallbackAllocator(comptime size: usize) type {
413405 return FixedBufferAllocator.realloc(
414406 &self.fixed_buffer_allocator.allocator,
415407 old_mem,
408 old_align,
416409 new_size,
417 alignment,
410 new_align,
418411 ) catch {
419 const result = try self.fallback_allocator.allocFn(
412 const result = try self.fallback_allocator.reallocFn(
420413 self.fallback_allocator,
414 ([*]u8)(undefined)[0..0],
415 undefined,
421416 new_size,
422 alignment,
417 new_align,
423418 );
424419 mem.copy(u8, result, old_mem);
425420 return result;
426421 };
427422 }
428 return self.fallback_allocator.reallocFn(self.fallback_allocator, old_mem, new_size, alignment);
423 return self.fallback_allocator.reallocFn(
424 self.fallback_allocator,
425 old_mem,
426 old_align,
427 new_size,
428 new_align,
429 );
429430 }
430431
431 fn free(allocator: *Allocator, bytes: []u8) void {
432 fn shrink(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 {
432433 const self = @fieldParentPtr(Self, "allocator", allocator);
433 const in_buffer = @ptrToInt(bytes.ptr) >= @ptrToInt(&self.buffer) and
434 @ptrToInt(bytes.ptr) < @ptrToInt(&self.buffer) + self.buffer.len;
435 if (!in_buffer) {
436 return self.fallback_allocator.freeFn(self.fallback_allocator, bytes);
434 const in_buffer = @ptrToInt(old_mem.ptr) >= @ptrToInt(&self.buffer) and
435 @ptrToInt(old_mem.ptr) < @ptrToInt(&self.buffer) + self.buffer.len;
436 if (in_buffer) {
437 return FixedBufferAllocator.shrink(
438 &self.fixed_buffer_allocator.allocator,
439 old_mem,
440 old_align,
441 new_size,
442 new_align,
443 );
437444 }
445 return self.fallback_allocator.shrinkFn(
446 self.fallback_allocator,
447 old_mem,
448 old_align,
449 new_size,
450 new_align,
451 );
438452 }
439453 };
440454}
441455
442456test "c_allocator" {
443457 if (builtin.link_libc) {
444 var slice = c_allocator.alloc(u8, 50) catch return;
458 var slice = try c_allocator.alloc(u8, 50);
445459 defer c_allocator.free(slice);
446 slice = c_allocator.realloc(u8, slice, 100) catch return;
460 slice = try c_allocator.realloc(slice, 100);
447461 }
448462}
449463
......@@ -486,10 +500,10 @@ test "FixedBufferAllocator Reuse memory on realloc" {
486500
487501 var slice0 = try fixed_buffer_allocator.allocator.alloc(u8, 5);
488502 testing.expect(slice0.len == 5);
489 var slice1 = try fixed_buffer_allocator.allocator.realloc(u8, slice0, 10);
503 var slice1 = try fixed_buffer_allocator.allocator.realloc(slice0, 10);
490504 testing.expect(slice1.ptr == slice0.ptr);
491505 testing.expect(slice1.len == 10);
492 testing.expectError(error.OutOfMemory, fixed_buffer_allocator.allocator.realloc(u8, slice1, 11));
506 testing.expectError(error.OutOfMemory, fixed_buffer_allocator.allocator.realloc(slice1, 11));
493507 }
494508 // check that we don't re-use the memory if it's not the most recent block
495509 {
......@@ -499,7 +513,7 @@ test "FixedBufferAllocator Reuse memory on realloc" {
499513 slice0[0] = 1;
500514 slice0[1] = 2;
501515 var slice1 = try fixed_buffer_allocator.allocator.alloc(u8, 2);
502 var slice2 = try fixed_buffer_allocator.allocator.realloc(u8, slice0, 4);
516 var slice2 = try fixed_buffer_allocator.allocator.realloc(slice0, 4);
503517 testing.expect(slice0.ptr != slice2.ptr);
504518 testing.expect(slice1.ptr != slice2.ptr);
505519 testing.expect(slice2[0] == 1);
......@@ -523,7 +537,7 @@ fn testAllocator(allocator: *mem.Allocator) !void {
523537 item.*.* = @intCast(i32, i);
524538 }
525539
526 slice = try allocator.realloc(*i32, slice, 20000);
540 slice = try allocator.realloc(slice, 20000);
527541 testing.expect(slice.len == 20000);
528542
529543 for (slice[0..100]) |item, i| {
......@@ -531,13 +545,13 @@ fn testAllocator(allocator: *mem.Allocator) !void {
531545 allocator.destroy(item);
532546 }
533547
534 slice = try allocator.realloc(*i32, slice, 50);
548 slice = allocator.shrink(slice, 50);
535549 testing.expect(slice.len == 50);
536 slice = try allocator.realloc(*i32, slice, 25);
550 slice = allocator.shrink(slice, 25);
537551 testing.expect(slice.len == 25);
538 slice = try allocator.realloc(*i32, slice, 0);
552 slice = allocator.shrink(slice, 0);
539553 testing.expect(slice.len == 0);
540 slice = try allocator.realloc(*i32, slice, 10);
554 slice = try allocator.realloc(slice, 10);
541555 testing.expect(slice.len == 10);
542556
543557 allocator.free(slice);
......@@ -548,22 +562,22 @@ fn testAllocatorAligned(allocator: *mem.Allocator, comptime alignment: u29) !voi
548562 var slice = try allocator.alignedAlloc(u8, alignment, 10);
549563 testing.expect(slice.len == 10);
550564 // grow
551 slice = try allocator.alignedRealloc(u8, alignment, slice, 100);
565 slice = try allocator.realloc(slice, 100);
552566 testing.expect(slice.len == 100);
553567 // shrink
554 slice = try allocator.alignedRealloc(u8, alignment, slice, 10);
568 slice = allocator.shrink(slice, 10);
555569 testing.expect(slice.len == 10);
556570 // go to zero
557 slice = try allocator.alignedRealloc(u8, alignment, slice, 0);
571 slice = allocator.shrink(slice, 0);
558572 testing.expect(slice.len == 0);
559573 // realloc from zero
560 slice = try allocator.alignedRealloc(u8, alignment, slice, 100);
574 slice = try allocator.realloc(slice, 100);
561575 testing.expect(slice.len == 100);
562576 // shrink with shrink
563 slice = allocator.alignedShrink(u8, alignment, slice, 10);
577 slice = allocator.shrink(slice, 10);
564578 testing.expect(slice.len == 10);
565579 // shrink to zero
566 slice = allocator.alignedShrink(u8, alignment, slice, 0);
580 slice = allocator.shrink(slice, 0);
567581 testing.expect(slice.len == 0);
568582}
569583
......@@ -578,19 +592,19 @@ fn testAllocatorLargeAlignment(allocator: *mem.Allocator) mem.Allocator.Error!vo
578592 var align_mask: usize = undefined;
579593 _ = @shlWithOverflow(usize, ~usize(0), USizeShift(@ctz(large_align)), &align_mask);
580594
581 var slice = try allocator.allocFn(allocator, 500, large_align);
595 var slice = try allocator.alignedAlloc(u8, large_align, 500);
582596 testing.expect(@ptrToInt(slice.ptr) & align_mask == @ptrToInt(slice.ptr));
583597
584 slice = try allocator.reallocFn(allocator, slice, 100, large_align);
598 slice = allocator.shrink(slice, 100);
585599 testing.expect(@ptrToInt(slice.ptr) & align_mask == @ptrToInt(slice.ptr));
586600
587 slice = try allocator.reallocFn(allocator, slice, 5000, large_align);
601 slice = try allocator.realloc(slice, 5000);
588602 testing.expect(@ptrToInt(slice.ptr) & align_mask == @ptrToInt(slice.ptr));
589603
590 slice = try allocator.reallocFn(allocator, slice, 10, large_align);
604 slice = allocator.shrink(slice, 10);
591605 testing.expect(@ptrToInt(slice.ptr) & align_mask == @ptrToInt(slice.ptr));
592606
593 slice = try allocator.reallocFn(allocator, slice, 20000, large_align);
607 slice = try allocator.realloc(slice, 20000);
594608 testing.expect(@ptrToInt(slice.ptr) & align_mask == @ptrToInt(slice.ptr));
595609
596610 allocator.free(slice);
std/math/big/int.zig+1-1
......@@ -60,7 +60,7 @@ pub const Int = struct {
6060 return;
6161 }
6262
63 self.limbs = try self.allocator.realloc(Limb, self.limbs, capacity);
63 self.limbs = try self.allocator.realloc(self.limbs, capacity);
6464 }
6565
6666 pub fn deinit(self: *Int) void {
std/mem.zig+132-46
......@@ -11,31 +11,64 @@ const testing = std.testing;
1111pub const Allocator = struct {
1212 pub const Error = error{OutOfMemory};
1313
14 /// Allocate byte_count bytes and return them in a slice, with the
15 /// slice's pointer aligned at least to alignment bytes.
16 /// The returned newly allocated memory is undefined.
17 /// `alignment` is guaranteed to be >= 1
18 /// `alignment` is guaranteed to be a power of 2
19 allocFn: fn (self: *Allocator, byte_count: usize, alignment: u29) Error![]u8,
20
21 /// If `new_byte_count > old_mem.len`:
22 /// * `old_mem.len` is the same as what was returned from allocFn or reallocFn.
23 /// * alignment >= alignment of old_mem.ptr
24 ///
25 /// If `new_byte_count <= old_mem.len`:
26 /// * this function must return successfully.
27 /// * alignment <= alignment of old_mem.ptr
28 ///
14 /// Realloc is used to modify the size or alignment of an existing allocation,
15 /// as well as to provide the allocator with an opportunity to move an allocation
16 /// to a better location.
17 /// When the size/alignment is greater than the previous allocation, this function
18 /// returns `error.OutOfMemory` when the requested new allocation could not be granted.
19 /// When the size/alignment is less than or equal to the previous allocation,
20 /// this function returns `error.OutOfMemory` when the allocator decides the client
21 /// would be better off keeping the extra alignment/size. Clients will call
22 /// `shrinkFn` when they require the allocator to track a new alignment/size,
23 /// and so this function should only return success when the allocator considers
24 /// the reallocation desirable from the allocator's perspective.
25 /// As an example, `std.ArrayList` tracks a "capacity", and therefore can handle
26 /// reallocation failure, even when `new_n` <= `old_mem.len`. A `FixedBufferAllocator`
27 /// would always return `error.OutOfMemory` for `reallocFn` when the size/alignment
28 /// is less than or equal to the old allocation, because it cannot reclaim the memory,
29 /// and thus the `std.ArrayList` would be better off retaining its capacity.
2930 /// When `reallocFn` returns,
3031 /// `return_value[0..min(old_mem.len, new_byte_count)]` must be the same
3132 /// as `old_mem` was when `reallocFn` is called. The bytes of
3233 /// `return_value[old_mem.len..]` have undefined values.
33 /// `alignment` is guaranteed to be >= 1
34 /// `alignment` is guaranteed to be a power of 2
35 reallocFn: fn (self: *Allocator, old_mem: []u8, new_byte_count: usize, alignment: u29) Error![]u8,
36
37 /// Guaranteed: `old_mem.len` is the same as what was returned from `allocFn` or `reallocFn`
38 freeFn: fn (self: *Allocator, old_mem: []u8) void,
34 /// The returned slice must have its pointer aligned at least to `new_alignment` bytes.
35 reallocFn: fn (
36 self: *Allocator,
37 // Guaranteed to be the same as what was returned from most recent call to
38 // `reallocFn` or `shrinkFn`.
39 // If `old_mem.len == 0` then this is a new allocation and `new_byte_count`
40 // is guaranteed to be >= 1.
41 old_mem: []u8,
42 // If `old_mem.len == 0` then this is `undefined`, otherwise:
43 // Guaranteed to be the same as what was returned from most recent call to
44 // `reallocFn` or `shrinkFn`.
45 // Guaranteed to be >= 1.
46 // Guaranteed to be a power of 2.
47 old_alignment: u29,
48 // If `new_byte_count` is 0 then this is a free and it is guaranteed that
49 // `old_mem.len != 0`.
50 new_byte_count: usize,
51 // Guaranteed to be >= 1.
52 // Guaranteed to be a power of 2.
53 // Returned slice's pointer must have this alignment.
54 new_alignment: u29,
55 ) Error![]u8,
56
57 /// This function deallocates memory. It must succeed.
58 shrinkFn: fn (
59 self: *Allocator,
60 // Guaranteed to be the same as what was returned from most recent call to
61 // `reallocFn` or `shrinkFn`.
62 old_mem: []u8,
63 // Guaranteed to be the same as what was returned from most recent call to
64 // `reallocFn` or `shrinkFn`.
65 old_alignment: u29,
66 // Guaranteed to be less than or equal to `old_mem.len`.
67 new_byte_count: usize,
68 // If `new_byte_count == 0` then this is `undefined`, otherwise:
69 // Guaranteed to be less than or equal to `old_alignment`.
70 new_alignment: u29,
71 ) []u8,
3972
4073 /// Call `destroy` with the result.
4174 /// Returns undefined memory.
......@@ -47,20 +80,29 @@ pub const Allocator = struct {
4780
4881 /// `ptr` should be the return value of `create`
4982 pub fn destroy(self: *Allocator, ptr: var) void {
83 const T = @typeOf(ptr).Child;
84 if (@sizeOf(T) == 0) return;
5085 const non_const_ptr = @intToPtr([*]u8, @ptrToInt(ptr));
51 self.freeFn(self, non_const_ptr[0..@sizeOf(@typeOf(ptr).Child)]);
86 const shrink_result = self.shrinkFn(self, non_const_ptr[0..@sizeOf(T)], @alignOf(T), 0, 1);
87 assert(shrink_result.len == 0);
5288 }
5389
5490 pub fn alloc(self: *Allocator, comptime T: type, n: usize) ![]T {
5591 return self.alignedAlloc(T, @alignOf(T), n);
5692 }
5793
58 pub fn alignedAlloc(self: *Allocator, comptime T: type, comptime alignment: u29, n: usize) ![]align(alignment) T {
94 pub fn alignedAlloc(
95 self: *Allocator,
96 comptime T: type,
97 comptime alignment: u29,
98 n: usize,
99 ) ![]align(alignment) T {
59100 if (n == 0) {
60101 return ([*]align(alignment) T)(undefined)[0..0];
61102 }
103
62104 const byte_count = math.mul(usize, @sizeOf(T), n) catch return Error.OutOfMemory;
63 const byte_slice = try self.allocFn(self, byte_count, alignment);
105 const byte_slice = try self.reallocFn(self, ([*]u8)(undefined)[0..0], undefined, byte_count, alignment);
64106 assert(byte_slice.len == byte_count);
65107 // This loop gets optimized out in ReleaseFast mode
66108 for (byte_slice) |*byte| {
......@@ -69,62 +111,106 @@ pub const Allocator = struct {
69111 return @bytesToSlice(T, @alignCast(alignment, byte_slice));
70112 }
71113
72 pub fn realloc(self: *Allocator, comptime T: type, old_mem: []T, n: usize) ![]T {
73 return self.alignedRealloc(T, @alignOf(T), @alignCast(@alignOf(T), old_mem), n);
114 /// This function requests a new byte size for an existing allocation,
115 /// which can be larger, smaller, or the same size as the old memory
116 /// allocation.
117 /// This function is preferred over `shrink`, because it can fail, even
118 /// when shrinking. This gives the allocator a chance to perform a
119 /// cheap shrink operation if possible, or otherwise return OutOfMemory,
120 /// indicating that the caller should keep their capacity, for example
121 /// in `std.ArrayList.shrink`.
122 /// If you need guaranteed success, call `shrink`.
123 /// If `new_n` is 0, this is the same as `free` and it always succeeds.
124 pub fn realloc(self: *Allocator, old_mem: var, new_n: usize) t: {
125 const Slice = @typeInfo(@typeOf(old_mem)).Pointer;
126 break :t Error![]align(Slice.alignment) Slice.child;
127 } {
128 const old_alignment = @typeInfo(@typeOf(old_mem)).Pointer.alignment;
129 return self.alignedRealloc(old_mem, old_alignment, new_n);
74130 }
75131
76 pub fn alignedRealloc(self: *Allocator, comptime T: type, comptime alignment: u29, old_mem: []align(alignment) T, n: usize) ![]align(alignment) T {
132 /// This is the same as `realloc`, except caller may additionally request
133 /// a new alignment, which can be larger, smaller, or the same as the old
134 /// allocation.
135 pub fn alignedRealloc(
136 self: *Allocator,
137 old_mem: var,
138 comptime new_alignment: u29,
139 new_n: usize,
140 ) Error![]align(new_alignment) @typeInfo(@typeOf(old_mem)).Pointer.child {
141 const Slice = @typeInfo(@typeOf(old_mem)).Pointer;
142 const T = Slice.child;
77143 if (old_mem.len == 0) {
78 return self.alignedAlloc(T, alignment, n);
144 return self.alignedAlloc(T, new_alignment, new_n);
79145 }
80 if (n == 0) {
146 if (new_n == 0) {
81147 self.free(old_mem);
82 return ([*]align(alignment) T)(undefined)[0..0];
148 return ([*]align(new_alignment) T)(undefined)[0..0];
83149 }
84150
85151 const old_byte_slice = @sliceToBytes(old_mem);
86 const byte_count = math.mul(usize, @sizeOf(T), n) catch return Error.OutOfMemory;
87 const byte_slice = try self.reallocFn(self, old_byte_slice, byte_count, alignment);
152 const byte_count = math.mul(usize, @sizeOf(T), new_n) catch return Error.OutOfMemory;
153 const byte_slice = try self.reallocFn(self, old_byte_slice, Slice.alignment, byte_count, new_alignment);
88154 assert(byte_slice.len == byte_count);
89 if (n > old_mem.len) {
155 if (new_n > old_mem.len) {
90156 // This loop gets optimized out in ReleaseFast mode
91157 for (byte_slice[old_byte_slice.len..]) |*byte| {
92158 byte.* = undefined;
93159 }
94160 }
95 return @bytesToSlice(T, @alignCast(alignment, byte_slice));
161 return @bytesToSlice(T, @alignCast(new_alignment, byte_slice));
96162 }
97163
98 /// Reallocate, but `n` must be less than or equal to `old_mem.len`.
99 /// Unlike `realloc`, this function cannot fail.
164 /// Prefer calling realloc to shrink if you can tolerate failure, such as
165 /// in an ArrayList data structure with a storage capacity.
166 /// Shrink always succeeds, and `new_n` must be <= `old_mem.len`.
167 /// Returned slice has same alignment as old_mem.
100168 /// Shrinking to 0 is the same as calling `free`.
101 pub fn shrink(self: *Allocator, comptime T: type, old_mem: []T, n: usize) []T {
102 return self.alignedShrink(T, @alignOf(T), @alignCast(@alignOf(T), old_mem), n);
169 pub fn shrink(self: *Allocator, old_mem: var, new_n: usize) t: {
170 const Slice = @typeInfo(@typeOf(old_mem)).Pointer;
171 break :t []align(Slice.alignment) Slice.child;
172 } {
173 const old_alignment = @typeInfo(@typeOf(old_mem)).Pointer.alignment;
174 return self.alignedShrink(old_mem, old_alignment, new_n);
103175 }
104176
105 pub fn alignedShrink(self: *Allocator, comptime T: type, comptime alignment: u29, old_mem: []align(alignment) T, n: usize) []align(alignment) T {
106 if (n == 0) {
177 /// This is the same as `shrink`, except caller may additionally request
178 /// a new alignment, which must be smaller or the same as the old
179 /// allocation.
180 pub fn alignedShrink(
181 self: *Allocator,
182 old_mem: var,
183 comptime new_alignment: u29,
184 new_n: usize,
185 ) []align(new_alignment) @typeInfo(@typeOf(old_mem)).Pointer.child {
186 const Slice = @typeInfo(@typeOf(old_mem)).Pointer;
187 const T = Slice.child;
188
189 if (new_n == 0) {
107190 self.free(old_mem);
108191 return old_mem[0..0];
109192 }
110193
111 assert(n <= old_mem.len);
194 assert(new_n <= old_mem.len);
195 assert(new_alignment <= Slice.alignment);
112196
113197 // Here we skip the overflow checking on the multiplication because
114 // n <= old_mem.len and the multiplication didn't overflow for that operation.
115 const byte_count = @sizeOf(T) * n;
198 // new_n <= old_mem.len and the multiplication didn't overflow for that operation.
199 const byte_count = @sizeOf(T) * new_n;
116200
117201 const old_byte_slice = @sliceToBytes(old_mem);
118 const byte_slice = self.reallocFn(self, old_byte_slice, byte_count, alignment) catch unreachable;
202 const byte_slice = self.shrinkFn(self, old_byte_slice, Slice.alignment, byte_count, new_alignment);
119203 assert(byte_slice.len == byte_count);
120 return @bytesToSlice(T, @alignCast(alignment, byte_slice));
204 return @bytesToSlice(T, @alignCast(new_alignment, byte_slice));
121205 }
122206
123207 pub fn free(self: *Allocator, memory: var) void {
208 const Slice = @typeInfo(@typeOf(memory)).Pointer;
124209 const bytes = @sliceToBytes(memory);
125210 if (bytes.len == 0) return;
126211 const non_const_ptr = @intToPtr([*]u8, @ptrToInt(bytes.ptr));
127 self.freeFn(self, non_const_ptr[0..bytes.len]);
212 const shrink_result = self.shrinkFn(self, non_const_ptr[0..bytes.len], Slice.alignment, 0, 1);
213 assert(shrink_result.len == 0);
128214 }
129215};
130216
std/os.zig+5-5
......@@ -814,7 +814,7 @@ pub fn getEnvVarOwned(allocator: *mem.Allocator, key: []const u8) GetEnvVarOwned
814814 }
815815
816816 if (result > buf.len) {
817 buf = try allocator.realloc(u16, buf, result);
817 buf = try allocator.realloc(buf, result);
818818 continue;
819819 }
820820
......@@ -1648,7 +1648,7 @@ pub const Dir = struct {
16481648 switch (err) {
16491649 posix.EBADF, posix.EFAULT, posix.ENOTDIR => unreachable,
16501650 posix.EINVAL => {
1651 self.handle.buf = try self.allocator.realloc(u8, self.handle.buf, self.handle.buf.len * 2);
1651 self.handle.buf = try self.allocator.realloc(self.handle.buf, self.handle.buf.len * 2);
16521652 continue;
16531653 },
16541654 else => return unexpectedErrorPosix(err),
......@@ -1730,7 +1730,7 @@ pub const Dir = struct {
17301730 switch (err) {
17311731 posix.EBADF, posix.EFAULT, posix.ENOTDIR => unreachable,
17321732 posix.EINVAL => {
1733 self.handle.buf = try self.allocator.realloc(u8, self.handle.buf, self.handle.buf.len * 2);
1733 self.handle.buf = try self.allocator.realloc(self.handle.buf, self.handle.buf.len * 2);
17341734 continue;
17351735 },
17361736 else => return unexpectedErrorPosix(err),
......@@ -1784,7 +1784,7 @@ pub const Dir = struct {
17841784 switch (err) {
17851785 posix.EBADF, posix.EFAULT, posix.ENOTDIR => unreachable,
17861786 posix.EINVAL => {
1787 self.handle.buf = try self.allocator.realloc(u8, self.handle.buf, self.handle.buf.len * 2);
1787 self.handle.buf = try self.allocator.realloc(self.handle.buf, self.handle.buf.len * 2);
17881788 continue;
17891789 },
17901790 else => return unexpectedErrorPosix(err),
......@@ -3279,7 +3279,7 @@ pub fn cpuCount(fallback_allocator: *mem.Allocator) CpuCountError!usize {
32793279 }
32803280 return sum;
32813281 } else {
3282 set = try allocator.realloc(usize, set, set.len * 2);
3282 set = try allocator.realloc(set, set.len * 2);
32833283 continue;
32843284 }
32853285 },
std/os/path.zig+2-2
......@@ -565,7 +565,7 @@ pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 {
565565 result_index += 1;
566566 }
567567
568 return allocator.shrink(u8, result, result_index);
568 return allocator.shrink(result, result_index);
569569}
570570
571571/// This function is like a series of `cd` statements executed one after another.
......@@ -634,7 +634,7 @@ pub fn resolvePosix(allocator: *Allocator, paths: []const []const u8) ![]u8 {
634634 result_index += 1;
635635 }
636636
637 return allocator.shrink(u8, result, result_index);
637 return allocator.shrink(result, result_index);
638638}
639639
640640test "os.path.resolve" {
std/priority_queue.zig+2-1
......@@ -141,7 +141,7 @@ pub fn PriorityQueue(comptime T: type) type {
141141 better_capacity += better_capacity / 2 + 8;
142142 if (better_capacity >= new_capacity) break;
143143 }
144 self.items = try self.allocator.realloc(T, self.items, better_capacity);
144 self.items = try self.allocator.realloc(self.items, better_capacity);
145145 }
146146
147147 pub fn resize(self: *Self, new_len: usize) !void {
......@@ -150,6 +150,7 @@ pub fn PriorityQueue(comptime T: type) type {
150150 }
151151
152152 pub fn shrink(self: *Self, new_len: usize) void {
153 // TODO take advantage of the new realloc semantics
153154 assert(new_len <= self.len);
154155 self.len = new_len;
155156 }
std/segmented_list.zig+4-3
......@@ -169,11 +169,11 @@ pub fn SegmentedList(comptime T: type, comptime prealloc_item_count: usize) type
169169 const new_cap_shelf_count = shelfCount(new_capacity);
170170 const old_shelf_count = @intCast(ShelfIndex, self.dynamic_segments.len);
171171 if (new_cap_shelf_count > old_shelf_count) {
172 self.dynamic_segments = try self.allocator.realloc([*]T, self.dynamic_segments, new_cap_shelf_count);
172 self.dynamic_segments = try self.allocator.realloc(self.dynamic_segments, new_cap_shelf_count);
173173 var i = old_shelf_count;
174174 errdefer {
175175 self.freeShelves(i, old_shelf_count);
176 self.dynamic_segments = self.allocator.shrink([*]T, self.dynamic_segments, old_shelf_count);
176 self.dynamic_segments = self.allocator.shrink(self.dynamic_segments, old_shelf_count);
177177 }
178178 while (i < new_cap_shelf_count) : (i += 1) {
179179 self.dynamic_segments[i] = (try self.allocator.alloc(T, shelfSize(i))).ptr;
......@@ -199,11 +199,12 @@ pub fn SegmentedList(comptime T: type, comptime prealloc_item_count: usize) type
199199 }
200200
201201 self.freeShelves(old_shelf_count, new_cap_shelf_count);
202 self.dynamic_segments = self.allocator.shrink([*]T, self.dynamic_segments, new_cap_shelf_count);
202 self.dynamic_segments = self.allocator.shrink(self.dynamic_segments, new_cap_shelf_count);
203203 }
204204
205205 pub fn shrink(self: *Self, new_len: usize) void {
206206 assert(new_len <= self.len);
207 // TODO take advantage of the new realloc semantics
207208 self.len = new_len;
208209 }
209210
test/tests.zig+11-7
......@@ -316,11 +316,13 @@ pub const CompareOutputContext = struct {
316316 Term.Exited => |code| {
317317 if (code != 0) {
318318 warn("Process {} exited with error code {}\n", full_exe_path, code);
319 printInvocation(args.toSliceConst());
319320 return error.TestFailed;
320321 }
321322 },
322323 else => {
323324 warn("Process {} terminated unexpectedly\n", full_exe_path);
325 printInvocation(args.toSliceConst());
324326 return error.TestFailed;
325327 },
326328 }
......@@ -681,11 +683,13 @@ pub const CompileErrorContext = struct {
681683 switch (term) {
682684 Term.Exited => |code| {
683685 if (code == 0) {
686 printInvocation(zig_args.toSliceConst());
684687 return error.CompilationIncorrectlySucceeded;
685688 }
686689 },
687690 else => {
688691 warn("Process {} terminated unexpectedly\n", b.zig_exe);
692 printInvocation(zig_args.toSliceConst());
689693 return error.TestFailed;
690694 },
691695 }
......@@ -752,13 +756,6 @@ pub const CompileErrorContext = struct {
752756 }
753757 };
754758
755 fn printInvocation(args: []const []const u8) void {
756 for (args) |arg| {
757 warn("{} ", arg);
758 }
759 warn("\n");
760 }
761
762759 pub fn create(self: *CompileErrorContext, name: []const u8, source: []const u8, expected_lines: ...) *TestCase {
763760 const tc = self.b.allocator.create(TestCase) catch unreachable;
764761 tc.* = TestCase{
......@@ -1240,3 +1237,10 @@ pub const GenHContext = struct {
12401237 self.step.dependOn(&cmp_h.step);
12411238 }
12421239};
1240
1241fn printInvocation(args: []const []const u8) void {
1242 for (args) |arg| {
1243 warn("{} ", arg);
1244 }
1245 warn("\n");
1246}