Commit 5234016561

Raul Leal <raulgrell@gmail.com>
2017-04-20 13:36:59
Add @offsetOf builtin function
1 parent 037a9d9
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,
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;
 
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) {
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<typename T>
 static T *ir_create_instruction(IrBuilder *irb, Scope *scope, AstNode *source_node) {
     T *special_instruction = allocate<T>(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<IrInstructionOffsetOf>(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:
             {
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");
 }
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();
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