Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions base/runtime/core.odin
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,12 @@ when !ODIN_NO_RTTI {
#assert(size_of(Raw_Any) == size_of(any))
}

// A view of `T` at alignment 1. `^Unaligned(T)` may point at unaligned
// memory, e.g. the address of a field of a `#packed` struct, or of a struct
// whose `#max_field_align` is lower than `T`'s alignment.
// Accesses through `.value` are misalignment safe.
Unaligned :: struct($T: typeid) #packed { value: T }

Raw_Cstring :: struct {
data: [^]byte,
}
Expand Down
3 changes: 3 additions & 0 deletions src/build_settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,7 @@ enum VetFlags : u64 {
VetFlag_Tabs = 1u<<9,
VetFlag_UnusedProcedures = 1u<<10,
VetFlag_ExplicitAllocators = 1u<<11,
VetFlag_PackedFieldAddr = 1u<<12,

VetFlag_Unused = VetFlag_UnusedVariables|VetFlag_UnusedImports,

Expand Down Expand Up @@ -352,6 +353,8 @@ u64 get_vet_flag_from_name(String const &name) {
return VetFlag_UnusedProcedures;
} else if (name == "explicit-allocators") {
return VetFlag_ExplicitAllocators;
} else if (name == "packed-field-addr") {
return VetFlag_PackedFieldAddr;
}
return VetFlag_NONE;
}
Expand Down
101 changes: 101 additions & 0 deletions src/check_expr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2995,6 +2995,102 @@ gb_internal ExactValue exact_bit_set_all_set_mask(Type *type) {
return res;
}

gb_internal i64 check_field_align_cap_of_addr(Ast *expr, bool *from_packed) {
// traverse the selector/index chain of the addressed expression,
// and determine the smallest field alignment guarantee of the
// containing structs;
// 1 for #packed, N for #max_field_align(N)
i64 cap = I64_MAX;
for (Ast *node = unparen_expr(expr); node != nullptr; ) {
Ast *base = nullptr;
switch (node->kind) {
case Ast_SelectorExpr: base = node->SelectorExpr.expr; break;
case Ast_IndexExpr: base = node->IndexExpr.expr; break;
case Ast_MatrixIndexExpr: base = node->MatrixIndexExpr.expr; break;
default:
return cap;
}
base = unparen_expr(base);
if (base == nullptr) {
return cap;
}
Type *bt = type_of_expr(base);
if (bt == nullptr) {
return cap;
}
bool through_ptr = is_type_pointer(bt) || is_type_multi_pointer(bt);
Type *t = base_type(type_deref(bt));
if (t->kind == Type_Struct) {
if (t->Struct.is_packed) {
if (from_packed) *from_packed = true;
return 1;
}
if (t->Struct.custom_max_field_align != 0 && t->Struct.custom_max_field_align < cap) {
cap = t->Struct.custom_max_field_align;
}
}
// stop if we are derefing a ptr field of the struct;
// it doesn't matter if the ptr itself is misaligned,
// e.g. &p.ptr.x stops the check at ptr,
// the address of x is not contained in the struct;
// also stop on &p.slice[i] or &p.dynarray[i], the slice/dynarray header
// is in the struct, but not the accessed element
if (through_ptr || is_type_slice(t) || is_type_dynamic_array(t)) {
return cap;
}
node = base;
}
return cap;
}

gb_internal void check_vet_packed_field_addr(CheckerContext *c, Operand *o, Ast *expr, Type *type_hint) {
if (c->curr_proc_sig != nullptr && is_type_polymorphic(c->curr_proc_sig)) {
return;
}
Type *pt = base_type(o->type);
if (pt == nullptr || pt->kind != Type_Pointer) {
return;
}
Type *elem = pt->Pointer.elem;
if (elem == nullptr || is_type_polymorphic(elem)) {
return;
}
i64 align = type_align_of(elem);
if (align <= 1) {
return;
}
bool from_packed = false;
i64 cap = check_field_align_cap_of_addr(expr, &from_packed);
if (align <= cap) {
return;
}
if (type_hint != nullptr) {
// taking the addr is fine if the direct user expects a ptr to data
// with alignment within the guarantee
Type *h = base_type(type_hint);
if (is_type_rawptr(h)) {
return;
}
if (h->kind == Type_Pointer || h->kind == Type_MultiPointer) {
Type *he = h->kind == Type_Pointer ? h->Pointer.elem : h->MultiPointer.elem;
if (type_align_of(he) <= cap) {
return;
}
}
}
ERROR_BLOCK();
gbString es = expr_to_string(expr);
gbString ts = type_to_string(elem);
if (from_packed) {
error(expr, "'&%s' has type '^%s' with data alignment %lld, but the address is of a #packed struct's field and may not be aligned", es, ts, cast(long long)align);
} else {
error(expr, "'&%s' has type '^%s' with data alignment %lld, but the address is of a field of a #max_field_align(%lld) struct and may not be aligned", es, ts, cast(long long)align, cast(long long)cap);
}
error_line("\tSuggestion: Cast the address directly to ^runtime.Unaligned(%s), or access the field by value\n", ts);
gb_string_free(ts);
gb_string_free(es);
}

gb_internal void check_unary_expr(CheckerContext *c, Operand *o, Token op, Ast *node) {
switch (op.kind) {
case Token_And: { // Pointer address
Expand Down Expand Up @@ -12765,6 +12861,11 @@ gb_internal ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast

if (o->mode != Addressing_Invalid) {
check_unary_expr(c, o, ue->op, node);
if (ue->op.kind == Token_And &&
o->mode != Addressing_Invalid &&
(check_vet_flags(c) & VetFlag_PackedFieldAddr) != 0) {
check_vet_packed_field_addr(c, o, ue->expr, type_hint);
}
} else {
ERROR_BLOCK();
gbString s = expr_to_string(ue->expr);
Expand Down
74 changes: 74 additions & 0 deletions src/llvm_abi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,9 @@ gb_internal void lb_add_function_type_attributes(LLVMValueRef fn, lbFunctionType
if (offset != 0 && ft->ret.kind == lbArg_Indirect && ft->ret.attribute != nullptr) {
LLVMAddAttributeAtIndex(fn, offset, ft->ret.attribute);
LLVMAddAttributeAtIndex(fn, offset, noalias_attr);
if (ft->ret.align_attribute != nullptr) {
LLVMAddAttributeAtIndex(fn, offset, ft->ret.align_attribute);
}
}

lbCallingConventionKind cc_kind = lbCallingConvention_C;
Expand Down Expand Up @@ -483,6 +486,73 @@ gb_internal Type *lb_abi_single_result_type(Type *proc_type) {
return nullptr;
}

// add the alignment of Odin controlled ptr args
// and of sret slots explicitly as align attributes
// 1) for an indirect odin/contextless arg pointing to an lvalue of the source type,
// the call emitter enforces alignment with a copy to a
// type aligned temp when the lvalue can't be proven properly aligned
// (see lb_emit_call's indirect arg handling);
// and when pointing to constants, the constant is materialized
// into a private global with proper alignment
// 2) the sret slot is allocated by the caller at the return type alignment
// for all calling conventions;
// for Odin callers an sret ptr is constructed as type aligned alloca,
// or from forwarding a ptr that already satisfies the req;
// and platform/C ABIs require caller provided aligned storage
// byval args already carry their align (set from the source type),
// so only indirect args with no attrib are handled here
gb_internal void lb_abi_add_indirect_source_type_alignments(lbModule *m, lbFunctionType *ft, unsigned arg_count, Type *original_type, ProcCallingConvention calling_convention) {
LLVMContextRef c = m->ctx;
// ignore anything that's not Type_Proc
Type *bt = original_type != nullptr ? base_type(original_type) : nullptr;
if (bt != nullptr && bt->kind != Type_Proc) {
bt = nullptr;
}
// only add align attrib to indirect args in odin cc / contextless,
// if non-null, no attrib already present and align > 1
if (bt != nullptr && is_calling_convention_odin(calling_convention)) {
auto srcs = lb_abi_param_source_types(bt, arg_count);
for (unsigned i = 0; i < arg_count && cast(isize)i < ft->args.count; i++) {
lbArgType *arg = &ft->args[i];
if (arg->kind != lbArg_Indirect || arg->align_attribute != nullptr) {
continue;
}
if (srcs[i] == nullptr) {
continue;
}
i64 align = type_align_of(srcs[i]);
if (align > 1) {
arg->align_attribute = lb_create_enum_attribute(c, "align", align);
}
}
}
// sret 3 cases
// 1 return value (the sret only)
// split multiple return (sret is the only)
// unsplit multiple return (take alignment of the whole tuple)
if (bt != nullptr && ft->ret.kind == lbArg_Indirect && ft->ret.align_attribute == nullptr) {
Type *ret_src = lb_abi_single_result_type(bt);
if (ret_src == nullptr && bt->Proc.results != nullptr &&
bt->Proc.results->Tuple.variables.count > 1) {
if (ft->multiple_return_original_type != nullptr) {
// split multi-return, the sret holds the last result only
auto const &vars = bt->Proc.results->Tuple.variables;
ret_src = vars[vars.count-1]->type;
} else {
// the sret holds the whole tuple
ret_src = bt->Proc.results;
}
}
// ret_src = nullptr -> skip anything unkwnown
if (ret_src != nullptr) {
i64 align = type_align_of(ret_src);
if (align > 1) {
ft->ret.align_attribute = lb_create_enum_attribute(c, "align", align);
}
}
}
}

namespace lbAbi386 {
gb_internal Array<lbArgType> compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count, Type *original_type);
gb_internal LB_ABI_COMPUTE_RETURN_TYPE(compute_return_type);
Expand Down Expand Up @@ -2760,12 +2830,16 @@ gb_internal LB_ABI_INFO(lb_get_abi_info) {
base_type(original_type)
);

lb_abi_add_indirect_source_type_alignments(m, ft, arg_count, original_type, calling_convention);

// NOTE(bill): this is handled here rather than when developing the type in `lb_type_internal_for_procedures_raw`
// This is to make it consistent when and how it is handled
if (calling_convention == ProcCC_Odin) {
// append the `context` pointer
lbArgType context_param = lb_arg_type_direct(LLVMPointerType(LLVMInt8TypeInContext(m->ctx), 0));
if (t_context != nullptr) {
context_param.align_attribute = lb_create_enum_attribute(m->ctx, "align", type_align_of(t_context));
}
array_add(&ft->args, context_param);
}

Expand Down
4 changes: 2 additions & 2 deletions src/llvm_backend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2100,7 +2100,7 @@ gb_internal bool lb_init_global_var(lbModule *m, lbProcedure *p, Entity *e, Ast
lbValue src0 = lb_emit_conv(p, var.init, t);
LLVMValueRef src = OdinLLVMBuildTransmute(p, src0.value, vt);
LLVMValueRef dst = var.var.value;
LLVMBuildStore(p->builder, src, dst);
OdinLLVMBuildStore(p, src, dst);
}

var.is_initialized = true;
Expand Down Expand Up @@ -2977,7 +2977,7 @@ gb_internal lbProcedure *lb_create_main_procedure(lbModule *m, lbProcedure *star
LLVMValueRef dst = LLVMConstInBoundsGEP2(llvm_addr_type(m, all_tests_array), all_tests_array.value, indices, gb_count_of(indices));
LLVMValueRef src = llvm_const_named_struct(m, t_Internal_Test, vals, gb_count_of(vals));

LLVMBuildStore(p->builder, src, dst);
OdinLLVMBuildStore(p, src, dst);
}

lbAddr all_tests_slice = lb_add_local_generated(p, slice_type, true);
Expand Down
3 changes: 3 additions & 0 deletions src/llvm_backend.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,7 @@ gb_internal void lb_addr_store(lbProcedure *p, lbAddr addr, lbValue value);
gb_internal lbValue lb_addr_load(lbProcedure *p, lbAddr const &addr);
gb_internal lbValue lb_emit_load(lbProcedure *p, lbValue v);
gb_internal void lb_emit_store(lbProcedure *p, lbValue ptr, lbValue value);
gb_internal void lb_emit_store_with_max_align(lbProcedure *p, lbValue ptr, lbValue value, i64 max_align);


gb_internal void lb_build_stmt(lbProcedure *p, Ast *stmt);
Expand Down Expand Up @@ -493,6 +494,8 @@ gb_internal lbValue lb_typeid(lbModule *m, Type *type);

gb_internal lbValue lb_address_from_load_or_generate_local(lbProcedure *p, lbValue value);
gb_internal lbValue lb_address_from_load(lbProcedure *p, lbValue value);
#define LB_TRY_GET_ALIGNMENT_MAX_DEPTH 8
gb_internal unsigned lb_try_get_alignment(lbModule *m, LLVMValueRef addr_ptr, unsigned default_alignment, isize depth = LB_TRY_GET_ALIGNMENT_MAX_DEPTH);
gb_internal void lb_add_defer_node(lbProcedure *p, isize scope_index, Ast *stmt);
gb_internal lbAddr lb_add_local_generated(lbProcedure *p, Type *type, bool zero_init);

Expand Down
14 changes: 7 additions & 7 deletions src/llvm_backend_const.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,7 @@ gb_internal LLVMValueRef lb_build_constant_array_values(lbModule *m, Type *type,
if (is_type_proc(elem_type)) {
values[i] = LLVMConstPointerCast(values[i], llvm_elem_type);
}
LLVMBuildStore(p->builder, values[i], elem.value);
OdinLLVMBuildStore(p, values[i], elem.value);
}
return lb_addr_load(p, v).value;
}
Expand Down Expand Up @@ -1024,7 +1024,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb
array_data = llvm_alloca(p, llvm_type, alignment);

LLVMValueRef local_copy = llvm_alloca(p, LLVMTypeOf(backing_array.value), alignment);
LLVMBuildStore(p->builder, backing_array.value, local_copy);
OdinLLVMBuildStore(p, backing_array.value, local_copy);

LLVMBuildMemCpy(p->builder,
array_data, alignment,
Expand All @@ -1033,7 +1033,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb
);
} else {
array_data = llvm_alloca(p, LLVMTypeOf(backing_array.value), alignment);
LLVMBuildStore(p->builder, backing_array.value, array_data);
OdinLLVMBuildStore(p, backing_array.value, array_data);

array_data = LLVMBuildPointerCast(p->builder, array_data, LLVMPointerType(llvm_type, 0), "");
}
Expand Down Expand Up @@ -2053,10 +2053,10 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb
LLVMValueRef src = LLVMGetOperand(elem_value, 0);
lb_mem_copy_non_overlapping(p, {dst, t_rawptr}, {src, t_rawptr}, lb_const_int(m, t_int, sz), false);
} else {
LLVMBuildStore(p->builder, elem_value, dst);
OdinLLVMBuildStore(p, elem_value, dst);
}

values[index] = LLVMBuildLoad2(p->builder, field_llvm_type, ptr, "");
values[index] = OdinLLVMBuildLoad(p, field_llvm_type, ptr);

is_constant = false;
} else {
Expand Down Expand Up @@ -2135,7 +2135,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb
lbAddr v = lb_add_local_generated(p, res.type, true);
map_set(&m->exact_value_compound_literal_addr_map, value.value_compound, v);

LLVMBuildStore(p->builder, constant_value, v.addr.value);
OdinLLVMBuildStore(p, constant_value, v.addr.value);
for (isize i = 0; i < value_count; i++) {
LLVMValueRef val = old_values[i];
if (!LLVMIsConstant(val)) {
Expand All @@ -2147,7 +2147,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb
// LLVMValueRef src = LLVMGetOperand(val, 0);
// lb_mem_copy_non_overlapping(p, {dst, ptr_type}, {src, ptr_type}, lb_const_int(m, t_int, sz), false);
// } else {
LLVMBuildStore(p->builder, val, dst);
OdinLLVMBuildStore(p, val, dst);
// }
}
}
Expand Down
Loading
Loading