diff --git a/doc/langref.md b/doc/langref.md index 6d6b9b63c..e1d2dbccb 100644 --- a/doc/langref.md +++ b/doc/langref.md @@ -314,6 +314,10 @@ for the current target. The result is a target-specific compile time constant. +### @offsetOf(comptime T: type, comptime field_name: [] const u8) -> (number literal) + +This function returns the byte offset of a field relative to its containing struct. + ### Overflow Arithmetic These functions take an integer type, two variables of the specified type, diff --git a/src/all_types.hpp b/src/all_types.hpp index 5dc7b9057..fc5b6efb1 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -1190,6 +1190,7 @@ enum BuiltinFnId { BuiltinFnIdIntToPtr, BuiltinFnIdEnumTagName, BuiltinFnIdFieldParentPtr, + BuiltinFnIdOffsetOf, }; struct BuiltinFnEntry { @@ -1742,6 +1743,7 @@ enum IrInstructionId { IrInstructionIdEnumTagName, IrInstructionIdSetFnRefInline, IrInstructionIdFieldParentPtr, + IrInstructionIdOffsetOf, }; struct IrInstruction { @@ -2503,6 +2505,13 @@ struct IrInstructionFieldParentPtr { TypeStructField *field; }; +struct IrInstructionOffsetOf { + IrInstruction base; + + IrInstruction *type_value; + IrInstruction *field_name; +}; + static const size_t slice_ptr_index = 0; static const size_t slice_len_index = 1; diff --git a/src/codegen.cpp b/src/codegen.cpp index fc57b4977..735c3f19a 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -2888,6 +2888,7 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable, case IrInstructionIdDeclRef: case IrInstructionIdSwitchVar: case IrInstructionIdSetFnRefInline: + case IrInstructionIdOffsetOf: zig_unreachable(); case IrInstructionIdReturn: return ir_render_return(g, executable, (IrInstructionReturn *)instruction); @@ -4548,6 +4549,7 @@ static void define_builtin_fns(CodeGen *g) { create_builtin_fn(g, BuiltinFnIdIntToPtr, "intToPtr", 2); create_builtin_fn(g, BuiltinFnIdEnumTagName, "enumTagName", 1); create_builtin_fn(g, BuiltinFnIdFieldParentPtr, "fieldParentPtr", 3); + create_builtin_fn(g, BuiltinFnIdOffsetOf, "offsetOf", 2); } static void add_compile_var(CodeGen *g, const char *name, ConstExprValue *value) { diff --git a/src/ir.cpp b/src/ir.cpp index e131667e0..b1de81749 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -553,6 +553,10 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionFieldParentPtr * return IrInstructionIdFieldParentPtr; } +static constexpr IrInstructionId ir_instruction_id(IrInstructionOffsetOf *) { + return IrInstructionIdOffsetOf; +} + template static T *ir_create_instruction(IrBuilder *irb, Scope *scope, AstNode *source_node) { T *special_instruction = allocate(1); @@ -2183,6 +2187,19 @@ static IrInstruction *ir_build_field_parent_ptr(IrBuilder *irb, Scope *scope, As return &instruction->base; } +static IrInstruction *ir_build_offset_of(IrBuilder *irb, Scope *scope, AstNode *source_node, + IrInstruction *type_value, IrInstruction *field_name) +{ + IrInstructionOffsetOf *instruction = ir_build_instruction(irb, scope, source_node); + instruction->type_value = type_value; + instruction->field_name = field_name; + + ir_ref_instruction(type_value, irb->current_basic_block); + ir_ref_instruction(field_name, irb->current_basic_block); + + return &instruction->base; +} + static IrInstruction *ir_instruction_br_get_dep(IrInstructionBr *instruction, size_t index) { return nullptr; } @@ -2863,6 +2880,13 @@ static IrInstruction *ir_instruction_fieldparentptr_get_dep(IrInstructionFieldPa } } +static IrInstruction *ir_instruction_offsetof_get_dep(IrInstructionOffsetOf *instruction, size_t index) { + switch (index) { + case 0: return instruction->type_value; + case 1: return instruction->field_name; + default: return nullptr; + } +} static IrInstruction *ir_instruction_get_dep(IrInstruction *instruction, size_t index) { switch (instruction->id) { @@ -3058,6 +3082,8 @@ static IrInstruction *ir_instruction_get_dep(IrInstruction *instruction, size_t return ir_instruction_setfnrefinline_get_dep((IrInstructionSetFnRefInline *) instruction, index); case IrInstructionIdFieldParentPtr: return ir_instruction_fieldparentptr_get_dep((IrInstructionFieldParentPtr *) instruction, index); + case IrInstructionIdOffsetOf: + return ir_instruction_offsetof_get_dep((IrInstructionOffsetOf *) instruction, index); } zig_unreachable(); } @@ -4384,6 +4410,20 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo return ir_build_field_parent_ptr(irb, scope, node, arg0_value, arg1_value, arg2_value, nullptr); } + case BuiltinFnIdOffsetOf: + { + 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_offset_of(irb, scope, node, arg0_value, arg1_value); + } } zig_unreachable(); } @@ -11401,6 +11441,41 @@ static TypeTableEntry *ir_analyze_instruction_field_parent_ptr(IrAnalyze *ira, return result_type; } +static TypeTableEntry *ir_analyze_instruction_offset_of(IrAnalyze *ira, + IrInstructionOffsetOf *instruction) +{ + IrInstruction *type_value = instruction->type_value->other; + TypeTableEntry *container_type = ir_resolve_type(ira, type_value); + if (type_is_invalid(container_type)) + return ira->codegen->builtin_types.entry_invalid; + + ensure_complete_type(ira->codegen, container_type); + + IrInstruction *field_name_value = instruction->field_name->other; + Buf *field_name = ir_resolve_str(ira, field_name_value); + if (!field_name) + return ira->codegen->builtin_types.entry_invalid; + + if (container_type->id != TypeTableEntryIdStruct) { + ir_add_error(ira, type_value, + buf_sprintf("expected struct type, found '%s'", buf_ptr(&container_type->name))); + return ira->codegen->builtin_types.entry_invalid; + } + + TypeStructField *field = find_struct_type_field(container_type, field_name); + if (field == nullptr) { + ir_add_error(ira, field_name_value, + buf_sprintf("struct '%s' has no field '%s'", + buf_ptr(&container_type->name), buf_ptr(field_name))); + return ira->codegen->builtin_types.entry_invalid; + } + + size_t byte_offset = LLVMOffsetOfElement(ira->codegen->target_data_ref, container_type->type_ref, field->gen_index); + ConstExprValue *out_val = ir_build_const_from(ira, &instruction->base); + bignum_init_unsigned(&out_val->data.x_bignum, byte_offset); + return ira->codegen->builtin_types.entry_num_lit_int; +} + static TypeTableEntry *ir_analyze_instruction_type_name(IrAnalyze *ira, IrInstructionTypeName *instruction) { IrInstruction *type_value = instruction->type_value->other; TypeTableEntry *type_entry = ir_resolve_type(ira, type_value); @@ -12921,6 +12996,8 @@ static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructi return ir_analyze_instruction_set_fn_ref_inline(ira, (IrInstructionSetFnRefInline *)instruction); case IrInstructionIdFieldParentPtr: return ir_analyze_instruction_field_parent_ptr(ira, (IrInstructionFieldParentPtr *)instruction); + case IrInstructionIdOffsetOf: + return ir_analyze_instruction_offset_of(ira, (IrInstructionOffsetOf *)instruction); case IrInstructionIdMaybeWrap: case IrInstructionIdErrWrapCode: case IrInstructionIdErrWrapPayload: @@ -13108,6 +13185,7 @@ bool ir_has_side_effects(IrInstruction *instruction) { case IrInstructionIdEnumTagName: case IrInstructionIdSetFnRefInline: case IrInstructionIdFieldParentPtr: + case IrInstructionIdOffsetOf: return false; case IrInstructionIdAsm: { diff --git a/src/ir_print.cpp b/src/ir_print.cpp index 569f03889..fa8a9fe3b 100644 --- a/src/ir_print.cpp +++ b/src/ir_print.cpp @@ -885,6 +885,14 @@ static void ir_print_field_parent_ptr(IrPrint *irp, IrInstructionFieldParentPtr fprintf(irp->f, ")"); } +static void ir_print_offset_of(IrPrint *irp, IrInstructionOffsetOf *instruction) { + fprintf(irp->f, "@offset_of("); + ir_print_other_instruction(irp, instruction->type_value); + fprintf(irp->f, ","); + ir_print_other_instruction(irp, instruction->field_name); + fprintf(irp->f, ")"); +} + static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) { ir_print_prefix(irp, instruction); switch (instruction->id) { @@ -1175,6 +1183,9 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) { case IrInstructionIdFieldParentPtr: ir_print_field_parent_ptr(irp, (IrInstructionFieldParentPtr *)instruction); break; + case IrInstructionIdOffsetOf: + ir_print_offset_of(irp, (IrInstructionOffsetOf *)instruction); + break; } fprintf(irp->f, "\n"); } diff --git a/test/cases/cast.zig b/test/cases/cast.zig index 7a09dd092..6a07b27a2 100644 --- a/test/cases/cast.zig +++ b/test/cases/cast.zig @@ -90,15 +90,20 @@ fn castToMaybeTypeError(z: i32) { const f = z; const g: %?i32 = f; - const a = A{ .a = 1 }; + const a = A{ .a = z }; const b: %?A = a; assert((??%%b).a == 1); } test "implicitly cast from int to %?T" { - const f: %?i32 = 1; - comptime const g: %?i32 = 1; + implicitIntLitToMaybe(); + comptime implicitIntLitToMaybe(); } +fn implicitIntLitToMaybe() { + const f: ?i32 = 1; + const g: %?i32 = 1; +} + test "return null from fn() -> %?&T" { const a = returnNullFromMaybeTypeErrorRef(); diff --git a/test/cases/sizeof_and_typeof.zig b/test/cases/sizeof_and_typeof.zig index 702e0ecc2..ff8bd4cb6 100644 --- a/test/cases/sizeof_and_typeof.zig +++ b/test/cases/sizeof_and_typeof.zig @@ -6,3 +6,29 @@ test "sizeofAndTypeOf" { } const x: u16 = 13; const z: @typeOf(x) = 19; + +const A = struct { + a: u8, + b: u32, + c: u8, +}; + +const P = packed struct { + a: u8, + b: u32, + c: u8, +}; + +test "offsetOf" { + // Packed structs have fixed memory layout + const p: P = undefined; + assert(@offsetOf(P, "a") == 0); + assert(@offsetOf(@typeOf(p), "b") == 1); + assert(@offsetOf(@typeOf(p), "c") == 5); + + // Non-packed struct fields can be moved/padded + const a: A = undefined; + assert(usize(&a.a) - usize(&a) == @offsetOf(A, "a")); + assert(usize(&a.b) - usize(&a) == @offsetOf(@typeOf(a), "b")); + assert(usize(&a.c) - usize(&a) == @offsetOf(@typeOf(a), "c")); +} \ No newline at end of file