diff --git a/src/all_types.hpp b/src/all_types.hpp index 086ad7db49..c86b99d35c 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -1270,6 +1270,7 @@ enum BuiltinFnId { BuiltinFnIdFieldParentPtr, BuiltinFnIdOffsetOf, BuiltinFnIdInlineCall, + BuiltinFnIdNoInlineCall, BuiltinFnIdTypeId, BuiltinFnIdShlExact, BuiltinFnIdShrExact, @@ -1439,7 +1440,7 @@ struct CodeGen { struct { TypeTableEntry *entry_bool; - TypeTableEntry *entry_int[2][11]; // [signed,unsigned][2,3,4,5,6,7,8,16,32,64,128] + TypeTableEntry *entry_int[2][12]; // [signed,unsigned][2,3,4,5,6,7,8,16,29,32,64,128] TypeTableEntry *entry_c_int[CIntTypeCount]; TypeTableEntry *entry_c_longdouble; TypeTableEntry *entry_c_void; @@ -2102,7 +2103,7 @@ struct IrInstructionCall { IrInstruction **args; bool is_comptime; LLVMValueRef tmp_ptr; - bool is_inline; + FnInline fn_inline; }; struct IrInstructionConst { diff --git a/src/analyze.cpp b/src/analyze.cpp index 61f7bedcbb..431b64f984 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -3818,12 +3818,14 @@ TypeTableEntry **get_int_type_ptr(CodeGen *g, bool is_signed, uint32_t size_in_b index = 6; } else if (size_in_bits == 16) { index = 7; - } else if (size_in_bits == 32) { + } else if (size_in_bits == 29) { index = 8; - } else if (size_in_bits == 64) { + } else if (size_in_bits == 32) { index = 9; - } else if (size_in_bits == 128) { + } else if (size_in_bits == 64) { index = 10; + } else if (size_in_bits == 128) { + index = 11; } else { return nullptr; } diff --git a/src/codegen.cpp b/src/codegen.cpp index 9941238f8f..d052ef159a 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -839,7 +839,7 @@ static void gen_panic(CodeGen *g, LLVMValueRef msg_arg) { assert(g->panic_fn != nullptr); LLVMValueRef fn_val = fn_llvm_value(g, g->panic_fn); LLVMCallConv llvm_cc = get_llvm_cc(g, g->panic_fn->type_entry->data.fn.fn_type_id.cc); - ZigLLVMBuildCall(g->builder, fn_val, &msg_arg, 1, llvm_cc, false, ""); + ZigLLVMBuildCall(g->builder, fn_val, &msg_arg, 1, llvm_cc, ZigLLVM_FnInlineAuto, ""); LLVMBuildUnreachable(g->builder); } @@ -988,7 +988,7 @@ static LLVMValueRef get_safety_crash_err_fn(CodeGen *g) { static void gen_debug_safety_crash_for_err(CodeGen *g, LLVMValueRef err_val) { LLVMValueRef safety_crash_err_fn = get_safety_crash_err_fn(g); ZigLLVMBuildCall(g->builder, safety_crash_err_fn, &err_val, 1, get_llvm_cc(g, CallingConventionUnspecified), - false, ""); + ZigLLVM_FnInlineAuto, ""); LLVMBuildUnreachable(g->builder); } @@ -2316,12 +2316,22 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutable *executable, IrInstr } } - bool want_always_inline = (instruction->fn_entry != nullptr && - instruction->fn_entry->fn_inline == FnInlineAlways) || instruction->is_inline; + ZigLLVM_FnInline fn_inline; + switch (instruction->fn_inline) { + case FnInlineAuto: + fn_inline = ZigLLVM_FnInlineAuto; + break; + case FnInlineAlways: + fn_inline = (instruction->fn_entry == nullptr) ? ZigLLVM_FnInlineAuto : ZigLLVM_FnInlineAlways; + break; + case FnInlineNever: + fn_inline = ZigLLVM_FnInlineNever; + break; + } LLVMCallConv llvm_cc = get_llvm_cc(g, fn_type->data.fn.fn_type_id.cc); LLVMValueRef result = ZigLLVMBuildCall(g->builder, fn_val, - gen_param_values, (unsigned)gen_param_index, llvm_cc, want_always_inline, ""); + gen_param_values, (unsigned)gen_param_index, llvm_cc, fn_inline, ""); for (size_t param_i = 0; param_i < fn_type_id->param_count; param_i += 1) { FnGenParamInfo *gen_info = &fn_type->data.fn.gen_param_info[param_i]; @@ -4634,6 +4644,7 @@ static const uint8_t int_sizes_in_bits[] = { 7, 8, 16, + 29, 32, 64, 128, @@ -4971,6 +4982,7 @@ static void define_builtin_fns(CodeGen *g) { create_builtin_fn(g, BuiltinFnIdRem, "rem", 2); create_builtin_fn(g, BuiltinFnIdMod, "mod", 2); create_builtin_fn(g, BuiltinFnIdInlineCall, "inlineCall", SIZE_MAX); + create_builtin_fn(g, BuiltinFnIdNoInlineCall, "noInlineCall", SIZE_MAX); create_builtin_fn(g, BuiltinFnIdTypeId, "typeId", 1); create_builtin_fn(g, BuiltinFnIdShlExact, "shlExact", 2); create_builtin_fn(g, BuiltinFnIdShrExact, "shrExact", 2); diff --git a/src/ir.cpp b/src/ir.cpp index 43ea16c976..0512ab5e36 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -928,13 +928,13 @@ static IrInstruction *ir_build_union_field_ptr_from(IrBuilder *irb, IrInstructio static IrInstruction *ir_build_call(IrBuilder *irb, Scope *scope, AstNode *source_node, FnTableEntry *fn_entry, IrInstruction *fn_ref, size_t arg_count, IrInstruction **args, - bool is_comptime, bool is_inline) + bool is_comptime, FnInline fn_inline) { IrInstructionCall *call_instruction = ir_build_instruction(irb, scope, source_node); call_instruction->fn_entry = fn_entry; call_instruction->fn_ref = fn_ref; call_instruction->is_comptime = is_comptime; - call_instruction->is_inline = is_inline; + call_instruction->fn_inline = fn_inline; call_instruction->args = args; call_instruction->arg_count = arg_count; @@ -948,10 +948,10 @@ static IrInstruction *ir_build_call(IrBuilder *irb, Scope *scope, AstNode *sourc static IrInstruction *ir_build_call_from(IrBuilder *irb, IrInstruction *old_instruction, FnTableEntry *fn_entry, IrInstruction *fn_ref, size_t arg_count, IrInstruction **args, - bool is_comptime, bool is_inline) + bool is_comptime, FnInline fn_inline) { IrInstruction *new_instruction = ir_build_call(irb, old_instruction->scope, - old_instruction->source_node, fn_entry, fn_ref, arg_count, args, is_comptime, is_inline); + old_instruction->source_node, fn_entry, fn_ref, arg_count, args, is_comptime, fn_inline); ir_link_new_instruction(new_instruction, old_instruction); return new_instruction; } @@ -4672,6 +4672,7 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo return ir_build_offset_of(irb, scope, node, arg0_value, arg1_value); } case BuiltinFnIdInlineCall: + case BuiltinFnIdNoInlineCall: { if (node->data.fn_call_expr.params.length == 0) { add_node_error(irb->codegen, node, buf_sprintf("expected at least 1 argument, found 0")); @@ -4692,8 +4693,9 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo if (args[i] == irb->codegen->invalid_instruction) return args[i]; } + FnInline fn_inline = (builtin_fn->id == BuiltinFnIdInlineCall) ? FnInlineAlways : FnInlineNever; - return ir_build_call(irb, scope, node, nullptr, fn_ref, arg_count, args, false, true); + return ir_build_call(irb, scope, node, nullptr, fn_ref, arg_count, args, false, fn_inline); } case BuiltinFnIdTypeId: { @@ -4804,7 +4806,7 @@ static IrInstruction *ir_gen_fn_call(IrBuilder *irb, Scope *scope, AstNode *node return args[i]; } - return ir_build_call(irb, scope, node, nullptr, fn_ref, arg_count, args, false, false); + return ir_build_call(irb, scope, node, nullptr, fn_ref, arg_count, args, false, FnInlineAuto); } static IrInstruction *ir_gen_if_bool_expr(IrBuilder *irb, Scope *scope, AstNode *node) { @@ -10617,7 +10619,7 @@ no_mem_slot: static TypeTableEntry *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCall *call_instruction, FnTableEntry *fn_entry, TypeTableEntry *fn_type, IrInstruction *fn_ref, - IrInstruction *first_arg_ptr, bool comptime_fn_call, bool inline_fn_call) + IrInstruction *first_arg_ptr, bool comptime_fn_call, FnInline fn_inline) { FnTypeId *fn_type_id = &fn_type->data.fn.fn_type_id; size_t first_arg_1_or_0 = first_arg_ptr ? 1 : 0; @@ -10876,7 +10878,7 @@ static TypeTableEntry *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCall *cal if (type_requires_comptime(return_type)) { // Throw out our work and call the function as if it were comptime. - return ir_analyze_fn_call(ira, call_instruction, fn_entry, fn_type, fn_ref, first_arg_ptr, true, false); + return ir_analyze_fn_call(ira, call_instruction, fn_entry, fn_type, fn_ref, first_arg_ptr, true, FnInlineAuto); } } @@ -10900,7 +10902,7 @@ static TypeTableEntry *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCall *cal size_t impl_param_count = impl_fn->type_entry->data.fn.fn_type_id.param_count; IrInstruction *new_call_instruction = ir_build_call_from(&ira->new_irb, &call_instruction->base, - impl_fn, nullptr, impl_param_count, casted_args, false, inline_fn_call); + impl_fn, nullptr, impl_param_count, casted_args, false, fn_inline); TypeTableEntry *return_type = impl_fn->type_entry->data.fn.fn_type_id.return_type; ir_add_alloca(ira, new_call_instruction, return_type); @@ -10959,7 +10961,7 @@ static TypeTableEntry *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCall *cal return ira->codegen->builtin_types.entry_invalid; IrInstruction *new_call_instruction = ir_build_call_from(&ira->new_irb, &call_instruction->base, - fn_entry, fn_ref, call_param_count, casted_args, false, inline_fn_call); + fn_entry, fn_ref, call_param_count, casted_args, false, fn_inline); ir_add_alloca(ira, new_call_instruction, return_type); return ir_finish_anal(ira, return_type); @@ -10998,13 +11000,13 @@ static TypeTableEntry *ir_analyze_instruction_call(IrAnalyze *ira, IrInstruction } else if (fn_ref->value.type->id == TypeTableEntryIdFn) { FnTableEntry *fn_table_entry = ir_resolve_fn(ira, fn_ref); return ir_analyze_fn_call(ira, call_instruction, fn_table_entry, fn_table_entry->type_entry, - fn_ref, nullptr, is_comptime, call_instruction->is_inline); + fn_ref, nullptr, is_comptime, call_instruction->fn_inline); } else if (fn_ref->value.type->id == TypeTableEntryIdBoundFn) { assert(fn_ref->value.special == ConstValSpecialStatic); FnTableEntry *fn_table_entry = fn_ref->value.data.x_bound_fn.fn; IrInstruction *first_arg_ptr = fn_ref->value.data.x_bound_fn.first_arg; return ir_analyze_fn_call(ira, call_instruction, fn_table_entry, fn_table_entry->type_entry, - nullptr, first_arg_ptr, is_comptime, call_instruction->is_inline); + nullptr, first_arg_ptr, is_comptime, call_instruction->fn_inline); } else { ir_add_error_node(ira, fn_ref->source_node, buf_sprintf("type '%s' not a function", buf_ptr(&fn_ref->value.type->name))); @@ -11014,7 +11016,7 @@ static TypeTableEntry *ir_analyze_instruction_call(IrAnalyze *ira, IrInstruction if (fn_ref->value.type->id == TypeTableEntryIdFn) { return ir_analyze_fn_call(ira, call_instruction, nullptr, fn_ref->value.type, - fn_ref, nullptr, false, false); + fn_ref, nullptr, false, FnInlineAuto); } else { ir_add_error_node(ira, fn_ref->source_node, buf_sprintf("type '%s' not a function", buf_ptr(&fn_ref->value.type->name))); diff --git a/src/zig_llvm.cpp b/src/zig_llvm.cpp index fa352147cc..0c3e711dd5 100644 --- a/src/zig_llvm.cpp +++ b/src/zig_llvm.cpp @@ -175,12 +175,19 @@ bool ZigLLVMTargetMachineEmitToFile(LLVMTargetMachineRef targ_machine_ref, LLVMM LLVMValueRef ZigLLVMBuildCall(LLVMBuilderRef B, LLVMValueRef Fn, LLVMValueRef *Args, - unsigned NumArgs, unsigned CC, bool always_inline, const char *Name) + unsigned NumArgs, unsigned CC, ZigLLVM_FnInline fn_inline, const char *Name) { CallInst *call_inst = CallInst::Create(unwrap(Fn), makeArrayRef(unwrap(Args), NumArgs), Name); call_inst->setCallingConv(CC); - if (always_inline) { - call_inst->addAttribute(AttributeList::FunctionIndex, Attribute::AlwaysInline); + switch (fn_inline) { + case ZigLLVM_FnInlineAuto: + break; + case ZigLLVM_FnInlineAlways: + call_inst->addAttribute(AttributeList::FunctionIndex, Attribute::AlwaysInline); + break; + case ZigLLVM_FnInlineNever: + call_inst->addAttribute(AttributeList::FunctionIndex, Attribute::NoInline); + break; } return wrap(unwrap(B)->Insert(call_inst)); } diff --git a/src/zig_llvm.hpp b/src/zig_llvm.hpp index b72b6a889f..404154835e 100644 --- a/src/zig_llvm.hpp +++ b/src/zig_llvm.hpp @@ -45,8 +45,13 @@ enum ZigLLVM_EmitOutputType { bool ZigLLVMTargetMachineEmitToFile(LLVMTargetMachineRef targ_machine_ref, LLVMModuleRef module_ref, const char *filename, ZigLLVM_EmitOutputType output_type, char **error_message, bool is_debug); +enum ZigLLVM_FnInline { + ZigLLVM_FnInlineAuto, + ZigLLVM_FnInlineAlways, + ZigLLVM_FnInlineNever, +}; LLVMValueRef ZigLLVMBuildCall(LLVMBuilderRef B, LLVMValueRef Fn, LLVMValueRef *Args, - unsigned NumArgs, unsigned CC, bool always_inline, const char *Name); + unsigned NumArgs, unsigned CC, ZigLLVM_FnInline fn_inline, const char *Name); LLVMValueRef ZigLLVMBuildCmpXchg(LLVMBuilderRef builder, LLVMValueRef ptr, LLVMValueRef cmp, LLVMValueRef new_val, LLVMAtomicOrdering success_ordering, diff --git a/std/debug.zig b/std/debug.zig index a2bea9eddd..c520e98b15 100644 --- a/std/debug.zig +++ b/std/debug.zig @@ -977,7 +977,7 @@ var some_mem_index: usize = 0; error OutOfMemory; -fn globalAlloc(self: &mem.Allocator, n: usize, alignment: usize) -> %[]u8 { +fn globalAlloc(self: &mem.Allocator, n: usize, alignment: u29) -> %[]u8 { const addr = @ptrToInt(&some_mem[some_mem_index]); const rem = @rem(addr, alignment); const march_forward_bytes = if (rem == 0) 0 else (alignment - rem); @@ -991,7 +991,7 @@ fn globalAlloc(self: &mem.Allocator, n: usize, alignment: usize) -> %[]u8 { return result; } -fn globalRealloc(self: &mem.Allocator, old_mem: []u8, new_size: usize, alignment: usize) -> %[]u8 { +fn globalRealloc(self: &mem.Allocator, old_mem: []u8, new_size: usize, alignment: u29) -> %[]u8 { if (new_size <= old_mem.len) { return old_mem[0..new_size]; } else { diff --git a/std/heap.zig b/std/heap.zig index d0bf8ab871..d54d921856 100644 --- a/std/heap.zig +++ b/std/heap.zig @@ -16,7 +16,7 @@ pub var c_allocator = Allocator { .freeFn = cFree, }; -fn cAlloc(self: &Allocator, n: usize, alignment: usize) -> %[]u8 { +fn cAlloc(self: &Allocator, n: usize, alignment: u29) -> %[]u8 { if (c.malloc(usize(n))) |buf| { @ptrCast(&u8, buf)[0..n] } else { @@ -24,7 +24,7 @@ fn cAlloc(self: &Allocator, n: usize, alignment: usize) -> %[]u8 { } } -fn cRealloc(self: &Allocator, old_mem: []u8, new_size: usize, alignment: usize) -> %[]u8 { +fn cRealloc(self: &Allocator, old_mem: []u8, new_size: usize, alignment: u29) -> %[]u8 { if (new_size <= old_mem.len) { old_mem[0..new_size] } else { @@ -106,7 +106,7 @@ pub const IncrementingAllocator = struct { return self.bytes.len - self.end_index; } - fn alloc(allocator: &Allocator, n: usize, alignment: usize) -> %[]u8 { + fn alloc(allocator: &Allocator, n: usize, alignment: u29) -> %[]u8 { const self = @fieldParentPtr(IncrementingAllocator, "allocator", allocator); const addr = @ptrToInt(&self.bytes[self.end_index]); const rem = @rem(addr, alignment); @@ -121,7 +121,7 @@ pub const IncrementingAllocator = struct { return result; } - fn realloc(allocator: &Allocator, old_mem: []u8, new_size: usize, alignment: usize) -> %[]u8 { + fn realloc(allocator: &Allocator, old_mem: []u8, new_size: usize, alignment: u29) -> %[]u8 { if (new_size <= old_mem.len) { return old_mem[0..new_size]; } else { diff --git a/std/mem.zig b/std/mem.zig index 815e122812..e99675d248 100644 --- a/std/mem.zig +++ b/std/mem.zig @@ -7,22 +7,24 @@ pub const Cmp = math.Cmp; pub const Allocator = struct { /// Allocate byte_count bytes and return them in a slice, with the - /// slicer's pointer aligned at least to alignment bytes. - allocFn: fn (self: &Allocator, byte_count: usize, alignment: usize) -> %[]u8, + /// slice's pointer aligned at least to alignment bytes. + allocFn: fn (self: &Allocator, byte_count: usize, alignment: u29) -> %[]u8, - /// Guaranteed: `old_mem.len` is the same as what was returned from allocFn or reallocFn. - /// Guaranteed: alignment >= alignment of old_mem.ptr + /// If `new_byte_count > old_mem.len`: + /// * `old_mem.len` is the same as what was returned from allocFn or reallocFn. + /// * alignment >= alignment of old_mem.ptr /// - /// If `new_byte_count` is less than or equal to `old_mem.len` this function must - /// return successfully. - reallocFn: fn (self: &Allocator, old_mem: []u8, new_byte_count: usize, alignment: usize) -> %[]u8, + /// If `new_byte_count <= old_mem.len`: + /// * this function must return successfully. + /// * alignment <= alignment of old_mem.ptr + reallocFn: fn (self: &Allocator, old_mem: []u8, new_byte_count: usize, alignment: u29) -> %[]u8, /// Guaranteed: `old_mem.len` is the same as what was returned from `allocFn` or `reallocFn` freeFn: fn (self: &Allocator, old_mem: []u8), fn create(self: &Allocator, comptime T: type) -> %&T { const slice = %return self.alloc(T, 1); - &slice[0] + return &slice[0]; } fn destroy(self: &Allocator, ptr: var) { @@ -30,28 +32,43 @@ pub const Allocator = struct { } fn alloc(self: &Allocator, comptime T: type, n: usize) -> %[]T { + return self.alignedAlloc(T, @alignOf(T), n); + } + + fn alignedAlloc(self: &Allocator, comptime T: type, comptime alignment: u29, + n: usize) -> %[]align(alignment) T + { const byte_count = %return math.mul(usize, @sizeOf(T), n); - const byte_slice = %return self.allocFn(self, byte_count, @alignOf(T)); - ([]T)(@alignCast(@alignOf(T), byte_slice)) + const byte_slice = %return self.allocFn(self, byte_count, alignment); + return ([]align(alignment) T)(@alignCast(alignment, byte_slice)); } fn realloc(self: &Allocator, comptime T: type, old_mem: []T, n: usize) -> %[]T { + return self.alignedRealloc(T, @alignOf(T), @alignCast(@alignOf(T), old_mem), n); + } + + fn alignedRealloc(self: &Allocator, comptime T: type, comptime alignment: u29, + old_mem: []align(alignment) T, n: usize) -> %[]align(alignment) T + { if (old_mem.len == 0) { return self.alloc(T, n); } - // Assert that old_mem.ptr is properly aligned. - const aligned_old_mem = @alignCast(@alignOf(T), old_mem); - const byte_count = %return math.mul(usize, @sizeOf(T), n); - const byte_slice = %return self.reallocFn(self, ([]u8)(aligned_old_mem), byte_count, @alignOf(T)); - return ([]T)(@alignCast(@alignOf(T), byte_slice)); + const byte_slice = %return self.reallocFn(self, ([]u8)(old_mem), byte_count, alignment); + return ([]T)(@alignCast(alignment, byte_slice)); } /// Reallocate, but `n` must be less than or equal to `old_mem.len`. /// Unlike `realloc`, this function cannot fail. /// Shrinking to 0 is the same as calling `free`. fn shrink(self: &Allocator, comptime T: type, old_mem: []T, n: usize) -> []T { + return self.alignedShrink(T, @alignOf(T), @alignCast(@alignOf(T), old_mem), n); + } + + fn alignedShrink(self: &Allocator, comptime T: type, comptime alignment: u29, + old_mem: []align(alignment) T, n: usize) -> []align(alignment) T + { if (n == 0) { self.free(old_mem); return old_mem[0..0]; @@ -59,15 +76,12 @@ pub const Allocator = struct { assert(n <= old_mem.len); - // Assert that old_mem.ptr is properly aligned. - const aligned_old_mem = @alignCast(@alignOf(T), old_mem); - // Here we skip the overflow checking on the multiplication because // n <= old_mem.len and the multiplication didn't overflow for that operation. const byte_count = @sizeOf(T) * n; - const byte_slice = %%self.reallocFn(self, ([]u8)(aligned_old_mem), byte_count, @alignOf(T)); - return ([]T)(@alignCast(@alignOf(T), byte_slice)); + const byte_slice = %%self.reallocFn(self, ([]u8)(old_mem), byte_count, alignment); + return ([]T)(@alignCast(alignment, byte_slice)); } fn free(self: &Allocator, memory: var) { diff --git a/std/os/index.zig b/std/os/index.zig index 361750aedc..d26daed9fe 100644 --- a/std/os/index.zig +++ b/std/os/index.zig @@ -1422,6 +1422,54 @@ pub fn args() -> ArgIterator { return ArgIterator.init(); } +/// Caller must call freeArgs on result. +pub fn argsAlloc(allocator: &mem.Allocator) -> %[]const []u8 { + // TODO refactor to only make 1 allocation. + var it = args(); + var contents = %return Buffer.initSize(allocator, 0); + defer contents.deinit(); + + var slice_list = ArrayList(usize).init(allocator); + defer slice_list.deinit(); + + while (it.next(allocator)) |arg_or_err| { + const arg = %return arg_or_err; + defer allocator.free(arg); + %return contents.append(arg); + %return slice_list.append(arg.len); + } + + const contents_slice = contents.toSliceConst(); + const slice_sizes = slice_list.toSliceConst(); + const slice_list_bytes = %return math.mul(usize, @sizeOf([]u8), slice_sizes.len); + const total_bytes = %return math.add(usize, slice_list_bytes, contents_slice.len); + const buf = %return allocator.alignedAlloc(u8, @alignOf([]u8), total_bytes); + %defer allocator.free(buf); + + const result_slice_list = ([][]u8)(buf[0..slice_list_bytes]); + const result_contents = buf[slice_list_bytes..]; + mem.copy(u8, result_contents, contents_slice); + + var contents_index: usize = 0; + for (slice_sizes) |len, i| { + const new_index = contents_index + len; + result_slice_list[i] = result_contents[contents_index..new_index]; + contents_index = new_index; + } + + return result_slice_list; +} + +pub fn argsFree(allocator: &mem.Allocator, args_alloc: []const []u8) { + var total_bytes: usize = 0; + for (args_alloc) |arg| { + total_bytes += @sizeOf([]u8) + arg.len; + } + const unaligned_allocated_buf = @ptrCast(&u8, args_alloc.ptr)[0..total_bytes]; + const aligned_allocated_buf = @alignCast(@alignOf([]u8), unaligned_allocated_buf); + return allocator.free(aligned_allocated_buf); +} + test "windows arg parsing" { testWindowsCmdLine(c"a b\tc d", [][]const u8{"a", "b", "c", "d"}); testWindowsCmdLine(c"\"abc\" d e", [][]const u8{"abc", "d", "e"}); diff --git a/std/special/bootstrap.zig b/std/special/bootstrap.zig index 924e537ddd..99bea09726 100644 --- a/std/special/bootstrap.zig +++ b/std/special/bootstrap.zig @@ -28,7 +28,9 @@ export nakedcc fn _start() -> noreturn { }, else => @compileError("unsupported arch"), } - posixCallMainAndExit() + // If LLVM inlines stack variables into _start, they will overwrite + // the command line argument data. + @noInlineCall(posixCallMainAndExit); } export fn WinMainCRTStartup() -> noreturn { diff --git a/test/compare_output.zig b/test/compare_output.zig index 1a7b8b51dd..ad9c91ff20 100644 --- a/test/compare_output.zig +++ b/test/compare_output.zig @@ -444,4 +444,86 @@ pub fn addCases(cases: &tests.CompareOutputContext) { tc }); + + cases.addCase({ + var tc = cases.create("parsing args", + \\const std = @import("std"); + \\const io = std.io; + \\const os = std.os; + \\const allocator = std.debug.global_allocator; + \\ + \\pub fn main() -> %void { + \\ var args_it = os.args(); + \\ var stdout_file = %return io.getStdOut(); + \\ var stdout_adapter = io.FileOutStream.init(&stdout_file); + \\ const stdout = &stdout_adapter.stream; + \\ var index: usize = 0; + \\ _ = args_it.skip(); + \\ while (args_it.next(allocator)) |arg_or_err| : (index += 1) { + \\ const arg = %return arg_or_err; + \\ %return stdout.print("{}: {}\n", index, arg); + \\ } + \\} + , + \\0: first arg + \\1: 'a' 'b' \ + \\2: bare + \\3: ba""re + \\4: " + \\5: last arg + \\ + ); + + tc.setCommandLineArgs([][]const u8 { + "first arg", + "'a' 'b' \\", + "bare", + "ba\"\"re", + "\"", + "last arg", + }); + + tc + }); + + cases.addCase({ + var tc = cases.create("parsing args new API", + \\const std = @import("std"); + \\const io = std.io; + \\const os = std.os; + \\const allocator = std.debug.global_allocator; + \\ + \\pub fn main() -> %void { + \\ var args_it = os.args(); + \\ var stdout_file = %return io.getStdOut(); + \\ var stdout_adapter = io.FileOutStream.init(&stdout_file); + \\ const stdout = &stdout_adapter.stream; + \\ var index: usize = 0; + \\ _ = args_it.skip(); + \\ while (args_it.next(allocator)) |arg_or_err| : (index += 1) { + \\ const arg = %return arg_or_err; + \\ %return stdout.print("{}: {}\n", index, arg); + \\ } + \\} + , + \\0: first arg + \\1: 'a' 'b' \ + \\2: bare + \\3: ba""re + \\4: " + \\5: last arg + \\ + ); + + tc.setCommandLineArgs([][]const u8 { + "first arg", + "'a' 'b' \\", + "bare", + "ba\"\"re", + "\"", + "last arg", + }); + + tc + }); } diff --git a/test/tests.zig b/test/tests.zig index e74afa1755..a5eb9d4db9 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -189,6 +189,7 @@ pub const CompareOutputContext = struct { expected_output: []const u8, link_libc: bool, special: Special, + cli_args: []const []const u8, const SourceFile = struct { filename: []const u8, @@ -201,6 +202,10 @@ pub const CompareOutputContext = struct { .source = source, }); } + + pub fn setCommandLineArgs(self: &TestCase, args: []const []const u8) { + self.cli_args = args; + } }; const RunCompareOutputStep = struct { @@ -210,9 +215,11 @@ pub const CompareOutputContext = struct { name: []const u8, expected_output: []const u8, test_index: usize, + cli_args: []const []const u8, pub fn create(context: &CompareOutputContext, exe_path: []const u8, - name: []const u8, expected_output: []const u8) -> &RunCompareOutputStep + name: []const u8, expected_output: []const u8, + cli_args: []const []const u8) -> &RunCompareOutputStep { const allocator = context.b.allocator; const ptr = %%allocator.create(RunCompareOutputStep); @@ -223,6 +230,7 @@ pub const CompareOutputContext = struct { .expected_output = expected_output, .test_index = context.test_index, .step = build.Step.init("RunCompareOutput", allocator, make), + .cli_args = cli_args, }; context.test_index += 1; return ptr; @@ -233,10 +241,17 @@ pub const CompareOutputContext = struct { const b = self.context.b; const full_exe_path = b.pathFromRoot(self.exe_path); + var args = ArrayList([]const u8).init(b.allocator); + defer args.deinit(); + + %%args.append(full_exe_path); + for (self.cli_args) |arg| { + %%args.append(arg); + } warn("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name); - const child = %%os.ChildProcess.init([][]u8{full_exe_path}, b.allocator); + const child = %%os.ChildProcess.init(args.toSliceConst(), b.allocator); defer child.deinit(); child.stdin_behavior = StdIo.Ignore; @@ -364,6 +379,7 @@ pub const CompareOutputContext = struct { .expected_output = expected_output, .link_libc = false, .special = special, + .cli_args = []const []const u8{}, }; const root_src_name = if (special == Special.Asm) "source.s" else "source.zig"; tc.addSourceFile(root_src_name, source); @@ -420,7 +436,7 @@ pub const CompareOutputContext = struct { } const run_and_cmp_output = RunCompareOutputStep.create(self, exe.getOutputPath(), annotated_case_name, - case.expected_output); + case.expected_output, case.cli_args); run_and_cmp_output.step.dependOn(&exe.step); self.step.dependOn(&run_and_cmp_output.step); @@ -447,7 +463,7 @@ pub const CompareOutputContext = struct { } const run_and_cmp_output = RunCompareOutputStep.create(self, exe.getOutputPath(), - annotated_case_name, case.expected_output); + annotated_case_name, case.expected_output, case.cli_args); run_and_cmp_output.step.dependOn(&exe.step); self.step.dependOn(&run_and_cmp_output.step);