diff --git a/src/all_types.hpp b/src/all_types.hpp index 461270357b..39e6033988 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -1252,6 +1252,7 @@ enum BuiltinFnId { BuiltinFnIdShlExact, BuiltinFnIdShrExact, BuiltinFnIdSetEvalBranchQuota, + BuiltinFnIdAlignCast, }; struct BuiltinFnEntry { @@ -1274,6 +1275,7 @@ enum PanicMsgId { PanicMsgIdSliceWidenRemainder, PanicMsgIdUnwrapMaybeFail, PanicMsgIdInvalidErrorCode, + PanicMsgIdIncorrectAlignment, PanicMsgIdCount, }; @@ -1856,6 +1858,7 @@ enum IrInstructionId { IrInstructionIdTypeId, IrInstructionIdSetEvalBranchQuota, IrInstructionIdPtrTypeOf, + IrInstructionIdAlignCast, }; struct IrInstruction { @@ -2462,6 +2465,7 @@ struct IrInstructionFnProto { IrInstruction base; IrInstruction **param_types; + IrInstruction *align_value; IrInstruction *return_type; bool is_var_args; }; @@ -2638,6 +2642,13 @@ struct IrInstructionPtrTypeOf { bool is_volatile; }; +struct IrInstructionAlignCast { + IrInstruction base; + + IrInstruction *align_bytes; + IrInstruction *target; +}; + static const size_t slice_ptr_index = 0; static const size_t slice_len_index = 1; diff --git a/src/analyze.cpp b/src/analyze.cpp index 35ccd3b4d3..6adf4b3d51 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -884,6 +884,9 @@ TypeTableEntry *get_fn_type(CodeGen *g, FnTypeId *fn_type_id) { buf_appendf(&fn_type->name, "%s...", comma); } buf_appendf(&fn_type->name, ")"); + if (fn_type_id->alignment != 0) { + buf_appendf(&fn_type->name, " align %" PRIu32, fn_type_id->alignment); + } if (fn_type_id->return_type->id != TypeTableEntryIdVoid) { buf_appendf(&fn_type->name, " -> %s", buf_ptr(&fn_type_id->return_type->name)); } @@ -1058,12 +1061,13 @@ void init_fn_type_id(FnTypeId *fn_type_id, AstNode *proto_node, size_t param_cou fn_type_id->is_var_args = fn_proto->is_var_args; } -static TypeTableEntry *analyze_fn_type(CodeGen *g, AstNode *proto_node, Scope *child_scope) { +static TypeTableEntry *analyze_fn_type(CodeGen *g, AstNode *proto_node, Scope *child_scope, uint32_t alignment) { assert(proto_node->type == NodeTypeFnProto); AstNodeFnProto *fn_proto = &proto_node->data.fn_proto; FnTypeId fn_type_id = {0}; init_fn_type_id(&fn_type_id, proto_node, proto_node->data.fn_proto.params.length); + fn_type_id.alignment = alignment; for (; fn_type_id.next_param_index < fn_type_id.param_count; fn_type_id.next_param_index += 1) { AstNode *param_node = fn_proto->params.at(fn_type_id.next_param_index); @@ -2056,23 +2060,23 @@ static void resolve_decl_fn(CodeGen *g, TldFn *tld_fn) { } Scope *child_scope = fn_table_entry->fndef_scope ? &fn_table_entry->fndef_scope->base : tld_fn->base.parent_scope; - fn_table_entry->type_entry = analyze_fn_type(g, source_node, child_scope); - - if (fn_table_entry->type_entry->id == TypeTableEntryIdInvalid) { - tld_fn->base.resolution = TldResolutionInvalid; - return; - } + uint32_t alignment = 0; if (fn_proto->align_expr != nullptr) { - if (!analyze_const_align(g, tld_fn->base.parent_scope, fn_proto->align_expr, - &fn_table_entry->align_bytes)) - { + if (!analyze_const_align(g, child_scope, fn_proto->align_expr, &alignment)) { fn_table_entry->type_entry = g->builtin_types.entry_invalid; tld_fn->base.resolution = TldResolutionInvalid; return; } } + fn_table_entry->type_entry = analyze_fn_type(g, source_node, child_scope, alignment); + + if (fn_table_entry->type_entry->id == TypeTableEntryIdInvalid) { + tld_fn->base.resolution = TldResolutionInvalid; + return; + } + if (!fn_table_entry->type_entry->data.fn.is_generic) { g->fn_protos.append(fn_table_entry); @@ -2663,6 +2667,9 @@ bool types_match_const_cast_only(TypeTableEntry *expected_type, TypeTableEntry * if (expected_type->id == TypeTableEntryIdFn && actual_type->id == TypeTableEntryIdFn) { + if (expected_type->data.fn.fn_type_id.alignment > actual_type->data.fn.fn_type_id.alignment) { + return false; + } if (expected_type->data.fn.fn_type_id.cc != actual_type->data.fn.fn_type_id.cc) { return false; } @@ -3384,6 +3391,7 @@ uint32_t fn_type_id_hash(FnTypeId *id) { result += ((uint32_t)(id->cc)) * (uint32_t)3349388391; result += id->is_var_args ? (uint32_t)1931444534 : 0; result += hash_ptr(id->return_type); + result += id->alignment * 0xd3b3f3e2; for (size_t i = 0; i < id->param_count; i += 1) { FnTypeParamInfo *info = &id->param_info[i]; result += info->is_noalias ? (uint32_t)892356923 : 0; @@ -3396,7 +3404,8 @@ bool fn_type_id_eql(FnTypeId *a, FnTypeId *b) { if (a->cc != b->cc || a->return_type != b->return_type || a->is_var_args != b->is_var_args || - a->param_count != b->param_count) + a->param_count != b->param_count || + a->alignment != b->alignment) { return false; } diff --git a/src/ast_render.cpp b/src/ast_render.cpp index b053a4e8af..d245863216 100644 --- a/src/ast_render.cpp +++ b/src/ast_render.cpp @@ -953,7 +953,7 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) { render_node_ungrouped(ar, node->data.slice_expr.array_ref_expr); fprintf(ar->f, "["); render_node_grouped(ar, node->data.slice_expr.start); - fprintf(ar->f, "..."); + fprintf(ar->f, ".."); if (node->data.slice_expr.end) render_node_grouped(ar, node->data.slice_expr.end); fprintf(ar->f, "]"); diff --git a/src/codegen.cpp b/src/codegen.cpp index cd0dc60f00..3954f310b6 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -688,6 +688,8 @@ static Buf *panic_msg_buf(PanicMsgId msg_id) { return buf_create_from_str("reached unreachable code"); case PanicMsgIdInvalidErrorCode: return buf_create_from_str("invalid error code"); + case PanicMsgIdIncorrectAlignment: + return buf_create_from_str("incorrect alignment"); } zig_unreachable(); } @@ -2605,6 +2607,72 @@ static LLVMValueRef ir_render_field_parent_ptr(CodeGen *g, IrExecutable *executa } } +static LLVMValueRef get_default_aligned_load(CodeGen *g, LLVMValueRef ptr) { + LLVMValueRef result = LLVMBuildLoad(g->builder, ptr, ""); + LLVMSetAlignment(result, LLVMABIAlignmentOfType(g->target_data_ref, LLVMGetElementType(LLVMTypeOf(ptr)))); + return result; +} + +static LLVMValueRef ir_render_align_cast(CodeGen *g, IrExecutable *executable, IrInstructionAlignCast *instruction) { + LLVMValueRef target_val = ir_llvm_value(g, instruction->target); + assert(target_val); + + bool want_debug_safety = ir_want_debug_safety(g, &instruction->base); + if (!want_debug_safety) { + return target_val; + } + + TypeTableEntry *target_type = instruction->base.value.type; + uint32_t align_bytes; + LLVMValueRef ptr_val; + + if (target_type->id == TypeTableEntryIdPointer) { + align_bytes = target_type->data.pointer.alignment; + ptr_val = target_val; + } else if (target_type->id == TypeTableEntryIdFn) { + align_bytes = target_type->data.fn.fn_type_id.alignment; + ptr_val = target_val; + } else if (target_type->id == TypeTableEntryIdMaybe && + target_type->data.maybe.child_type->id == TypeTableEntryIdPointer) + { + align_bytes = target_type->data.maybe.child_type->data.pointer.alignment; + ptr_val = target_val; + } else if (target_type->id == TypeTableEntryIdMaybe && + target_type->data.maybe.child_type->id == TypeTableEntryIdFn) + { + align_bytes = target_type->data.maybe.child_type->data.fn.fn_type_id.alignment; + ptr_val = target_val; + } else if (target_type->id == TypeTableEntryIdStruct && target_type->data.structure.is_slice) { + TypeTableEntry *slice_ptr_type = target_type->data.structure.fields[slice_ptr_index].type_entry; + align_bytes = slice_ptr_type->data.pointer.alignment; + + size_t ptr_index = target_type->data.structure.fields[slice_ptr_index].gen_index; + LLVMValueRef ptr_val_ptr = LLVMBuildStructGEP(g->builder, target_val, (unsigned)ptr_index, ""); + ptr_val = get_default_aligned_load(g, ptr_val_ptr); + } else { + zig_unreachable(); + } + + assert(align_bytes != 1); + + TypeTableEntry *usize = g->builtin_types.entry_usize; + LLVMValueRef ptr_as_int_val = LLVMBuildPtrToInt(g->builder, ptr_val, usize->type_ref, ""); + LLVMValueRef alignment_minus_1 = LLVMConstInt(usize->type_ref, align_bytes - 1, false); + LLVMValueRef anded_val = LLVMBuildAnd(g->builder, ptr_as_int_val, alignment_minus_1, ""); + LLVMValueRef ok_bit = LLVMBuildICmp(g->builder, LLVMIntEQ, anded_val, LLVMConstNull(usize->type_ref), ""); + + LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "AlignCastOk"); + LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "AlignCastFail"); + + LLVMBuildCondBr(g->builder, ok_bit, ok_block, fail_block); + + LLVMPositionBuilderAtEnd(g->builder, fail_block); + gen_debug_safety_crash(g, PanicMsgIdIncorrectAlignment); + + LLVMPositionBuilderAtEnd(g->builder, ok_block); + + return target_val; +} static LLVMAtomicOrdering to_LLVMAtomicOrdering(AtomicOrder atomic_order) { switch (atomic_order) { @@ -3350,6 +3418,8 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable, return ir_render_enum_tag_name(g, executable, (IrInstructionEnumTagName *)instruction); case IrInstructionIdFieldParentPtr: return ir_render_field_parent_ptr(g, executable, (IrInstructionFieldParentPtr *)instruction); + case IrInstructionIdAlignCast: + return ir_render_align_cast(g, executable, (IrInstructionAlignCast *)instruction); } zig_unreachable(); } @@ -4633,6 +4703,7 @@ static void define_builtin_fns(CodeGen *g) { create_builtin_fn(g, BuiltinFnIdShlExact, "shlExact", 2); create_builtin_fn(g, BuiltinFnIdShrExact, "shrExact", 2); create_builtin_fn(g, BuiltinFnIdSetEvalBranchQuota, "setEvalBranchQuota", 1); + create_builtin_fn(g, BuiltinFnIdAlignCast, "alignCast", 2); } static const char *bool_to_str(bool b) { diff --git a/src/ir.cpp b/src/ir.cpp index 251c597224..243a8a2389 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -555,6 +555,10 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionPtrTypeOf *) { return IrInstructionIdPtrTypeOf; } +static constexpr IrInstructionId ir_instruction_id(IrInstructionAlignCast *) { + return IrInstructionIdAlignCast; +} + template static T *ir_create_instruction(IrBuilder *irb, Scope *scope, AstNode *source_node) { T *special_instruction = allocate(1); @@ -1899,10 +1903,11 @@ static IrInstruction *ir_build_unwrap_err_payload_from(IrBuilder *irb, IrInstruc } static IrInstruction *ir_build_fn_proto(IrBuilder *irb, Scope *scope, AstNode *source_node, - IrInstruction **param_types, IrInstruction *return_type, bool is_var_args) + IrInstruction **param_types, IrInstruction *align_value, IrInstruction *return_type, bool is_var_args) { IrInstructionFnProto *instruction = ir_build_instruction(irb, scope, source_node); instruction->param_types = param_types; + instruction->align_value = align_value; instruction->return_type = return_type; instruction->is_var_args = is_var_args; @@ -1912,6 +1917,7 @@ static IrInstruction *ir_build_fn_proto(IrBuilder *irb, Scope *scope, AstNode *s for (size_t i = 0; i < param_count; i += 1) { ir_ref_instruction(param_types[i], irb->current_basic_block); } + if (align_value != nullptr) ir_ref_instruction(align_value, irb->current_basic_block); ir_ref_instruction(return_type, irb->current_basic_block); return &instruction->base; @@ -2219,6 +2225,19 @@ static IrInstruction *ir_build_set_eval_branch_quota(IrBuilder *irb, Scope *scop return &instruction->base; } +static IrInstruction *ir_build_align_cast(IrBuilder *irb, Scope *scope, AstNode *source_node, + IrInstruction *align_bytes, IrInstruction *target) +{ + IrInstructionAlignCast *instruction = ir_build_instruction(irb, scope, source_node); + instruction->align_bytes = align_bytes; + instruction->target = target; + + ir_ref_instruction(align_bytes, irb->current_basic_block); + ir_ref_instruction(target, irb->current_basic_block); + + return &instruction->base; +} + static IrInstruction *ir_instruction_br_get_dep(IrInstructionBr *instruction, size_t index) { return nullptr; } @@ -2738,6 +2757,10 @@ static IrInstruction *ir_instruction_fnproto_get_dep(IrInstructionFnProto *instr if (param_index < instruction->base.source_node->data.fn_proto.params.length) { return instruction->param_types[param_index]; } + size_t next_index = param_index - instruction->base.source_node->data.fn_proto.params.length; + if (next_index == 0 && instruction->align_value != nullptr) { + return instruction->align_value; + } return nullptr; } @@ -2925,6 +2948,14 @@ static IrInstruction *ir_instruction_ptrtypeof_get_dep(IrInstructionPtrTypeOf *i } } +static IrInstruction *ir_instruction_aligncast_get_dep(IrInstructionAlignCast *instruction, size_t index) { + switch (index) { + case 0: return instruction->align_bytes; + case 1: return instruction->target; + default: return nullptr; + } +} + static IrInstruction *ir_instruction_get_dep(IrInstruction *instruction, size_t index) { switch (instruction->id) { case IrInstructionIdInvalid: @@ -3121,6 +3152,8 @@ static IrInstruction *ir_instruction_get_dep(IrInstruction *instruction, size_t return ir_instruction_setevalbranchquota_get_dep((IrInstructionSetEvalBranchQuota *) instruction, index); case IrInstructionIdPtrTypeOf: return ir_instruction_ptrtypeof_get_dep((IrInstructionPtrTypeOf *) instruction, index); + case IrInstructionIdAlignCast: + return ir_instruction_aligncast_get_dep((IrInstructionAlignCast *) instruction, index); } zig_unreachable(); } @@ -4531,6 +4564,20 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo return ir_build_set_eval_branch_quota(irb, scope, node, arg0_value); } + case BuiltinFnIdAlignCast: + { + 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_align_cast(irb, scope, node, arg0_value, arg1_value); + } } zig_unreachable(); } @@ -6060,11 +6107,18 @@ static IrInstruction *ir_gen_fn_proto(IrBuilder *irb, Scope *parent_scope, AstNo param_types[i] = type_value; } + IrInstruction *align_value = nullptr; + if (node->data.fn_proto.align_expr != nullptr) { + align_value = ir_gen_node(irb, node->data.fn_proto.align_expr, parent_scope); + if (align_value == irb->codegen->invalid_instruction) + return irb->codegen->invalid_instruction; + } + IrInstruction *return_type = ir_gen_node(irb, node->data.fn_proto.return_type, parent_scope); if (return_type == irb->codegen->invalid_instruction) return irb->codegen->invalid_instruction; - return ir_build_fn_proto(irb, parent_scope, node, param_types, return_type, is_var_args); + return ir_build_fn_proto(irb, parent_scope, node, param_types, align_value, return_type, is_var_args); } static IrInstruction *ir_gen_node_raw(IrBuilder *irb, AstNode *node, Scope *scope, @@ -8316,15 +8370,29 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst } // explicit cast from []T to []u8 or []u8 to []T - if (is_slice(wanted_type) && is_slice(actual_type) && - (is_u8(wanted_type->data.structure.fields[slice_ptr_index].type_entry->data.pointer.child_type) || - is_u8(actual_type->data.structure.fields[slice_ptr_index].type_entry->data.pointer.child_type)) && - (wanted_type->data.structure.fields[slice_ptr_index].type_entry->data.pointer.is_const || - !actual_type->data.structure.fields[slice_ptr_index].type_entry->data.pointer.is_const)) - { - if (!ir_emit_global_runtime_side_effect(ira, source_instr)) - return ira->codegen->invalid_instruction; - return ir_resolve_cast(ira, source_instr, value, wanted_type, CastOpResizeSlice, true); + if (is_slice(wanted_type) && is_slice(actual_type)) { + TypeTableEntry *wanted_ptr_type = wanted_type->data.structure.fields[slice_ptr_index].type_entry; + TypeTableEntry *actual_ptr_type = actual_type->data.structure.fields[slice_ptr_index].type_entry; + if ((is_u8(wanted_ptr_type->data.pointer.child_type) || is_u8(actual_ptr_type->data.pointer.child_type)) && + (wanted_ptr_type->data.pointer.is_const || !actual_ptr_type->data.pointer.is_const)) + { + uint32_t src_align_bytes = get_ptr_align(actual_ptr_type); + uint32_t dest_align_bytes = get_ptr_align(wanted_ptr_type); + + if (dest_align_bytes > src_align_bytes) { + ErrorMsg *msg = ir_add_error(ira, source_instr, + buf_sprintf("cast increases pointer alignment")); + add_error_note(ira->codegen, msg, source_instr->source_node, + buf_sprintf("'%s' has alignment %" PRIu32, buf_ptr(&actual_type->name), src_align_bytes)); + add_error_note(ira->codegen, msg, source_instr->source_node, + buf_sprintf("'%s' has alignment %" PRIu32, buf_ptr(&wanted_type->name), dest_align_bytes)); + return ira->codegen->invalid_instruction; + } + + if (!ir_emit_global_runtime_side_effect(ira, source_instr)) + return ira->codegen->invalid_instruction; + return ir_resolve_cast(ira, source_instr, value, wanted_type, CastOpResizeSlice, true); + } } // explicit cast from [N]u8 to []const T @@ -10226,7 +10294,10 @@ static TypeTableEntry *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCall *cal ira->new_irb.exec->backward_branch_count, ira->new_irb.exec->backward_branch_quota, nullptr, nullptr, fn_proto_node->data.fn_proto.align_expr, nullptr, ira->new_irb.exec); - ir_resolve_align(ira, align_result, &impl_fn->align_bytes); + uint32_t align_bytes = 0; + ir_resolve_align(ira, align_result, &align_bytes); + impl_fn->align_bytes = align_bytes; + inst_fn_type_id.alignment = align_bytes; } { @@ -13728,11 +13799,9 @@ static TypeTableEntry *ir_analyze_instruction_slice(IrAnalyze *ira, IrInstructio TypeTableEntry *return_type; if (array_type->id == TypeTableEntryIdArray) { - uint32_t normal_array_alignment = get_abi_alignment(ira->codegen, array_type); - uint32_t align_bytes = (ptr_type->data.pointer.alignment >= normal_array_alignment) ? - normal_array_alignment : 1; TypeTableEntry *slice_ptr_type = get_pointer_to_type_extra(ira->codegen, array_type->data.array.child_type, - ptr_type->data.pointer.is_const, ptr_type->data.pointer.is_volatile, align_bytes, 0, 0); + ptr_type->data.pointer.is_const, ptr_type->data.pointer.is_volatile, + ptr_type->data.pointer.alignment, 0, 0); return_type = get_slice_type(ira->codegen, slice_ptr_type); } else if (array_type->id == TypeTableEntryIdPointer) { TypeTableEntry *slice_ptr_type = get_pointer_to_type_extra(ira->codegen, array_type->data.pointer.child_type, @@ -14237,6 +14306,11 @@ static TypeTableEntry *ir_analyze_instruction_fn_proto(IrAnalyze *ira, IrInstruc } } + if (instruction->align_value != nullptr) { + if (!ir_resolve_align(ira, instruction->align_value->other, &fn_type_id.alignment)) + return ira->codegen->builtin_types.entry_invalid; + } + IrInstruction *return_type_value = instruction->return_type->other; fn_type_id.return_type = ir_resolve_type(ira, return_type_value); if (type_is_invalid(fn_type_id.return_type)) @@ -14866,6 +14940,90 @@ static TypeTableEntry *ir_analyze_instruction_ptr_type_of(IrAnalyze *ira, IrInst return ira->codegen->builtin_types.entry_type; } +static TypeTableEntry *ir_analyze_instruction_align_cast(IrAnalyze *ira, IrInstructionAlignCast *instruction) { + uint32_t align_bytes; + IrInstruction *align_bytes_inst = instruction->align_bytes->other; + if (!ir_resolve_align(ira, align_bytes_inst, &align_bytes)) + return ira->codegen->builtin_types.entry_invalid; + + IrInstruction *target = instruction->target->other; + TypeTableEntry *target_type = target->value.type; + if (type_is_invalid(target_type)) + return ira->codegen->builtin_types.entry_invalid; + + TypeTableEntry *result_type; + uint32_t old_align_bytes; + + if (target_type->id == TypeTableEntryIdPointer) { + result_type = get_pointer_to_type_extra(ira->codegen, + target_type->data.pointer.child_type, + target_type->data.pointer.is_const, target_type->data.pointer.is_volatile, + align_bytes, + target_type->data.pointer.bit_offset, target_type->data.pointer.unaligned_bit_count); + } else if (target_type->id == TypeTableEntryIdFn) { + FnTypeId fn_type_id = target_type->data.fn.fn_type_id; + old_align_bytes = fn_type_id.alignment; + fn_type_id.alignment = align_bytes; + result_type = get_fn_type(ira->codegen, &fn_type_id); + } else if (target_type->id == TypeTableEntryIdMaybe && + target_type->data.maybe.child_type->id == TypeTableEntryIdPointer) + { + TypeTableEntry *ptr_type = target_type->data.maybe.child_type; + old_align_bytes = ptr_type->data.pointer.alignment; + TypeTableEntry *better_ptr_type = get_pointer_to_type_extra(ira->codegen, + ptr_type->data.pointer.child_type, + ptr_type->data.pointer.is_const, ptr_type->data.pointer.is_volatile, + align_bytes, + ptr_type->data.pointer.bit_offset, ptr_type->data.pointer.unaligned_bit_count); + + result_type = get_maybe_type(ira->codegen, better_ptr_type); + } else if (target_type->id == TypeTableEntryIdMaybe && + target_type->data.maybe.child_type->id == TypeTableEntryIdFn) + { + FnTypeId fn_type_id = target_type->data.maybe.child_type->data.fn.fn_type_id; + old_align_bytes = fn_type_id.alignment; + fn_type_id.alignment = align_bytes; + TypeTableEntry *fn_type = get_fn_type(ira->codegen, &fn_type_id); + result_type = get_maybe_type(ira->codegen, fn_type); + } else if (is_slice(target_type)) { + TypeTableEntry *slice_ptr_type = target_type->data.structure.fields[slice_ptr_index].type_entry; + old_align_bytes = slice_ptr_type->data.pointer.alignment; + TypeTableEntry *result_ptr_type = get_pointer_to_type_extra(ira->codegen, + slice_ptr_type->data.pointer.child_type, + slice_ptr_type->data.pointer.is_const, slice_ptr_type->data.pointer.is_volatile, + align_bytes, + slice_ptr_type->data.pointer.bit_offset, slice_ptr_type->data.pointer.unaligned_bit_count); + result_type = get_slice_type(ira->codegen, result_ptr_type); + } else { + ir_add_error(ira, target, + buf_sprintf("expected pointer or slice, found '%s'", buf_ptr(&target_type->name))); + return ira->codegen->builtin_types.entry_invalid; + } + + if (instr_is_comptime(target)) { + ConstExprValue *val = ir_resolve_const(ira, target, UndefBad); + if (!val) + return ira->codegen->builtin_types.entry_invalid; + + ConstExprValue *out_val = ir_build_const_from(ira, &instruction->base); + copy_const_val(out_val, val, false); + out_val->type = result_type; + return result_type; + } + + IrInstruction *result; + if (align_bytes > old_align_bytes && align_bytes != 1) { + result = ir_build_align_cast(&ira->new_irb, instruction->base.scope, instruction->base.source_node, + align_bytes_inst, target); + } else { + result = ir_build_cast(&ira->new_irb, instruction->base.scope, instruction->base.source_node, + result_type, target, CastOpNoop); + } + ir_link_new_instruction(result, &instruction->base); + result->value.type = result_type; + return result_type; +} + static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstruction *instruction) { switch (instruction->id) { case IrInstructionIdInvalid: @@ -14877,6 +15035,10 @@ static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructi case IrInstructionIdStructFieldPtr: case IrInstructionIdEnumFieldPtr: case IrInstructionIdInitEnum: + case IrInstructionIdMaybeWrap: + case IrInstructionIdErrWrapCode: + case IrInstructionIdErrWrapPayload: + case IrInstructionIdCast: zig_unreachable(); case IrInstructionIdReturn: return ir_analyze_instruction_return(ira, (IrInstructionReturn *)instruction); @@ -15046,11 +15208,8 @@ static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructi return ir_analyze_instruction_set_eval_branch_quota(ira, (IrInstructionSetEvalBranchQuota *)instruction); case IrInstructionIdPtrTypeOf: return ir_analyze_instruction_ptr_type_of(ira, (IrInstructionPtrTypeOf *)instruction); - case IrInstructionIdMaybeWrap: - case IrInstructionIdErrWrapCode: - case IrInstructionIdErrWrapPayload: - case IrInstructionIdCast: - zig_panic("TODO analyze more instructions"); + case IrInstructionIdAlignCast: + return ir_analyze_instruction_align_cast(ira, (IrInstructionAlignCast *)instruction); } zig_unreachable(); } @@ -15228,6 +15387,7 @@ bool ir_has_side_effects(IrInstruction *instruction) { case IrInstructionIdFieldParentPtr: case IrInstructionIdOffsetOf: case IrInstructionIdTypeId: + case IrInstructionIdAlignCast: return false; case IrInstructionIdAsm: { diff --git a/src/ir_print.cpp b/src/ir_print.cpp index bfaf45835b..3c6154b21d 100644 --- a/src/ir_print.cpp +++ b/src/ir_print.cpp @@ -174,10 +174,16 @@ static void ir_print_decl_var(IrPrint *irp, IrInstructionDeclVar *decl_var_instr if (decl_var_instruction->var_type) { fprintf(irp->f, "%s %s: ", var_or_const, name); ir_print_other_instruction(irp, decl_var_instruction->var_type); - fprintf(irp->f, " = "); + fprintf(irp->f, " "); } else { - fprintf(irp->f, "%s %s = ", var_or_const, name); + fprintf(irp->f, "%s %s ", var_or_const, name); } + if (decl_var_instruction->align_value) { + fprintf(irp->f, "align "); + ir_print_other_instruction(irp, decl_var_instruction->align_value); + fprintf(irp->f, " "); + } + fprintf(irp->f, "= "); ir_print_other_instruction(irp, decl_var_instruction->init_value); if (decl_var_instruction->var->is_comptime != nullptr) { fprintf(irp->f, " // comptime = "); @@ -640,7 +646,7 @@ static void ir_print_slice(IrPrint *irp, IrInstructionSlice *instruction) { ir_print_other_instruction(irp, instruction->ptr); fprintf(irp->f, "["); ir_print_other_instruction(irp, instruction->start); - fprintf(irp->f, "..."); + fprintf(irp->f, ".."); if (instruction->end) ir_print_other_instruction(irp, instruction->end); fprintf(irp->f, "]"); @@ -745,7 +751,13 @@ static void ir_print_fn_proto(IrPrint *irp, IrInstructionFnProto *instruction) { ir_print_other_instruction(irp, instruction->param_types[i]); } } - fprintf(irp->f, ")->"); + fprintf(irp->f, ")"); + if (instruction->align_value != nullptr) { + fprintf(irp->f, " align "); + ir_print_other_instruction(irp, instruction->align_value); + fprintf(irp->f, " "); + } + fprintf(irp->f, "->"); ir_print_other_instruction(irp, instruction->return_type); } @@ -920,6 +932,14 @@ static void ir_print_set_eval_branch_quota(IrPrint *irp, IrInstructionSetEvalBra fprintf(irp->f, ")"); } +static void ir_print_align_cast(IrPrint *irp, IrInstructionAlignCast *instruction) { + fprintf(irp->f, "@alignCast("); + ir_print_other_instruction(irp, instruction->align_bytes); + fprintf(irp->f, ","); + ir_print_other_instruction(irp, instruction->target); + fprintf(irp->f, ")"); +} + static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) { ir_print_prefix(irp, instruction); switch (instruction->id) { @@ -1213,6 +1233,9 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) { case IrInstructionIdSetEvalBranchQuota: ir_print_set_eval_branch_quota(irp, (IrInstructionSetEvalBranchQuota *)instruction); break; + case IrInstructionIdAlignCast: + ir_print_align_cast(irp, (IrInstructionAlignCast *)instruction); + break; } fprintf(irp->f, "\n"); } diff --git a/std/debug.zig b/std/debug.zig index 6d7138a551..0b80f4b104 100644 --- a/std/debug.zig +++ b/std/debug.zig @@ -957,16 +957,21 @@ pub var global_allocator = mem.Allocator { var some_mem: [100 * 1024]u8 = undefined; var some_mem_index: usize = 0; -fn globalAlloc(self: &mem.Allocator, n: usize) -> %[]u8 { - const result = some_mem[some_mem_index .. some_mem_index + n]; - some_mem_index += n; +fn globalAlloc(self: &mem.Allocator, n: usize, alignment: usize) -> %[]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); + const adjusted_index = some_mem_index + march_forward_bytes; + const end_index = adjusted_index + n; + const result = some_mem[adjusted_index .. end_index]; + some_mem_index = end_index; return result; } -fn globalRealloc(self: &mem.Allocator, old_mem: []u8, new_size: usize) -> %[]u8 { - const result = %return globalAlloc(self, new_size); +fn globalRealloc(self: &mem.Allocator, old_mem: []u8, new_size: usize, alignment: usize) -> %[]u8 { + const result = %return globalAlloc(self, new_size, alignment); @memcpy(result.ptr, old_mem.ptr, old_mem.len); return result; } -fn globalFree(self: &mem.Allocator, old_mem: []u8) { } +fn globalFree(self: &mem.Allocator, ptr: &u8) { } diff --git a/std/mem.zig b/std/mem.zig index 26fb2a3cd6..671ea8de87 100644 --- a/std/mem.zig +++ b/std/mem.zig @@ -11,21 +11,18 @@ pub const Cmp = math.Cmp; error NoMem; pub const Allocator = struct { - allocFn: fn (self: &Allocator, n: usize) -> %[]u8, - /// Note that old_mem may be a slice of length 0, in which case reallocFn - /// should simply call allocFn. - reallocFn: fn (self: &Allocator, old_mem: []u8, new_size: usize) -> %[]u8, - /// Note that mem may be a slice of length 0, in which case freeFn - /// should do nothing. - freeFn: fn (self: &Allocator, mem: []u8), + /// 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, - /// Aborts the program if an allocation fails. - fn checkedAlloc(self: &Allocator, comptime T: type, n: usize) -> []T { - alloc(self, T, n) %% |err| debug.panic("allocation failure: {}", @errorName(err)) - } + /// Guaranteed: old_mem.len > 0 and alignment >= alignment of old_mem.ptr + reallocFn: fn (self: &Allocator, old_mem: []u8, new_byte_count: usize, alignment: usize) -> %[]u8, + + freeFn: fn (self: &Allocator, ptr: &u8), fn create(self: &Allocator, comptime T: type) -> %&T { - &(%return self.alloc(T, 1))[0] + const slice = %return self.alloc(T, 1); + &slice[0] } fn destroy(self: &Allocator, ptr: var) { @@ -34,16 +31,29 @@ pub const Allocator = struct { fn alloc(self: &Allocator, comptime T: type, n: usize) -> %[]T { const byte_count = %return math.mul(usize, @sizeOf(T), n); - ([]T)(%return self.allocFn(self, byte_count)) + const byte_slice = %return self.allocFn(self, byte_count, @alignOf(T)); + ([]T)(@alignCast(@alignOf(T), byte_slice)) } fn realloc(self: &Allocator, comptime T: type, old_mem: []T, n: usize) -> %[]T { + if (old_mem.len == 0) { + return self.alloc(T, n); + } + + // Assert that old_mem.ptr is properly aligned. + _ = @alignCast(@alignOf(T), old_mem.ptr); + const byte_count = %return math.mul(usize, @sizeOf(T), n); - ([]T)(%return self.reallocFn(self, ([]u8)(old_mem), byte_count)) + const byte_slice = %return self.reallocFn(self, ([]u8)(old_mem), byte_count, @alignOf(T)); + ([]T)(@alignCast(@alignOf(T), byte_slice)) } - fn free(self: &Allocator, mem: var) { - self.freeFn(self, ([]u8)(mem)); + fn free(self: &Allocator, memory: var) { + const const_slice = ([]const u8)(memory); + if (memory.len == 0) + return; + const ptr = @intToPtr(&u8, @ptrToInt(const_slice.ptr)); + self.freeFn(self, ptr); } }; @@ -79,24 +89,28 @@ pub const IncrementingAllocator = struct { _ = os.posix.munmap(self.bytes.ptr, self.bytes.len); } - fn alloc(allocator: &Allocator, n: usize) -> %[]u8 { + fn alloc(allocator: &Allocator, n: usize, alignment: usize) -> %[]u8 { const self = @fieldParentPtr(IncrementingAllocator, "allocator", allocator); - const new_end_index = self.end_index + n; + const addr = @ptrToInt(&self.bytes[self.end_index]); + const rem = @rem(addr, alignment); + const march_forward_bytes = if (rem == 0) 0 else (alignment - rem); + const adjusted_index = self.end_index + march_forward_bytes; + const new_end_index = adjusted_index + n; if (new_end_index > self.bytes.len) { return error.NoMem; } - const result = self.bytes[self.end_index..new_end_index]; + const result = self.bytes[adjusted_index .. new_end_index]; self.end_index = new_end_index; return result; } - fn realloc(allocator: &Allocator, old_mem: []u8, new_size: usize) -> %[]u8 { - const result = %return alloc(allocator, new_size); + fn realloc(allocator: &Allocator, old_mem: []u8, new_size: usize, alignment: usize) -> %[]u8 { + const result = %return alloc(allocator, new_size, alignment); copy(u8, result, old_mem); return result; } - fn free(allocator: &Allocator, bytes: []u8) { + fn free(allocator: &Allocator, bytes: &u8) { // Do nothing. That's the point of an incrementing allocator. } }; diff --git a/test/cases/align.zig b/test/cases/align.zig index ee68408226..33013732de 100644 --- a/test/cases/align.zig +++ b/test/cases/align.zig @@ -62,3 +62,24 @@ fn testBytesAlign(b: u8) { const ptr = @ptrCast(&u32, &bytes[0]); assert(*ptr == 0x33333333); } + +test "specifying alignment allows slice cast" { + testBytesAlignSlice(0x33); +} +fn testBytesAlignSlice(b: u8) { + var bytes align 4 = []u8{b, b, b, b}; + const slice = ([]u32)(bytes[0..]); + assert(slice[0] == 0x33333333); +} + +test "@alignCast" { + var x: u32 align 4 = 1; + expectsOnly1(&x); + assert(x == 2); +} +fn expectsOnly1(x: &align 1 u32) { + expects4(@alignCast(4, x)); +} +fn expects4(x: &align 4 u32) { + *x += 1; +} diff --git a/test/cases/cast.zig b/test/cases/cast.zig index 8c1df39f66..310d5e3d9b 100644 --- a/test/cases/cast.zig +++ b/test/cases/cast.zig @@ -277,7 +277,7 @@ fn cast128Float(x: u128) -> f128 { } test "const slice widen cast" { - const bytes = []u8{0x12, 0x12, 0x12, 0x12}; + const bytes align 4 = []u8{0x12, 0x12, 0x12, 0x12}; const u32_value = ([]const u32)(bytes[0..])[0]; assert(u32_value == 0x12121212); diff --git a/test/cases/misc.zig b/test/cases/misc.zig index 46f86c4bd1..abe9d3871d 100644 --- a/test/cases/misc.zig +++ b/test/cases/misc.zig @@ -404,7 +404,7 @@ test "cast slice to u8 slice" { bytes[6] = 0; bytes[7] = 0; assert(big_thing_slice[1] == 0); - const big_thing_again = ([]i32)(bytes); + const big_thing_again = ([]align 1 i32)(bytes); assert(big_thing_again[2] == 3); big_thing_again[2] = -1; assert(bytes[8] == @maxValue(u8)); diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 21575bfc34..e21e56f4e8 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -2022,4 +2022,21 @@ pub fn addCases(cases: &tests.CompileErrorContext) { ".tmp_source.zig:3:17: error: cast increases pointer alignment", ".tmp_source.zig:3:38: note: '&u8' has alignment 1", ".tmp_source.zig:3:27: note: '&u32' has alignment 4"); + + cases.add("increase pointer alignment in slice resize", + \\export fn entry() -> u32 { + \\ var bytes = []u8{0x01, 0x02, 0x03, 0x04}; + \\ return ([]u32)(bytes[0..])[0]; + \\} + , + ".tmp_source.zig:3:19: error: cast increases pointer alignment", + ".tmp_source.zig:3:19: note: '[]u8' has alignment 1", + ".tmp_source.zig:3:19: note: '[]u32' has alignment 4"); + + cases.add("@alignCast expects pointer or slice", + \\export fn entry() { + \\ @alignCast(4, u32(3)) + \\} + , + ".tmp_source.zig:2:22: error: expected pointer or slice, found 'u32'"); } diff --git a/test/debug_safety.zig b/test/debug_safety.zig index 7767477b6e..4a60a881ed 100644 --- a/test/debug_safety.zig +++ b/test/debug_safety.zig @@ -200,8 +200,8 @@ pub fn addCases(cases: &tests.CompareOutputContext) { \\ const x = widenSlice([]u8{1, 2, 3, 4, 5}); \\ if (x.len == 0) return error.Whatever; \\} - \\fn widenSlice(slice: []const u8) -> []const i32 { - \\ ([]const i32)(slice) + \\fn widenSlice(slice: []align 1 const u8) -> []align 1 const i32 { + \\ ([]align 1 const i32)(slice) \\} ); @@ -261,4 +261,22 @@ pub fn addCases(cases: &tests.CompareOutputContext) { \\ return error(x); \\} ); + + cases.addDebugSafety("@alignCast misaligned", + \\pub fn panic(message: []const u8) -> noreturn { + \\ @breakpoint(); + \\ while (true) {} + \\} + \\error Wrong; + \\pub fn main() -> %void { + \\ var array align 4 = []u32{0x11111111, 0x11111111}; + \\ const bytes = ([]u8)(array[0..]); + \\ if (foo(bytes) != 0x11111111) return error.Wrong; + \\} + \\fn foo(bytes: []u8) -> u32 { + \\ const slice4 = bytes[1..5]; + \\ const int_slice = ([]u32)(@alignCast(4, slice4)); + \\ return int_slice[0]; + \\} + ); }