diff --git a/doc/langref.md b/doc/langref.md index c202925ae3..6d6b9b63ce 100644 --- a/doc/langref.md +++ b/doc/langref.md @@ -630,3 +630,7 @@ Converts an integer to a pointer. To convert the other way, use `usize(ptr)`. ### @enumTagName(value: var) -> []const u8 Converts an enum tag name to a slice of bytes. Example: + +### @fieldParentPtr(comptime ParentType: type, comptime field_name: []const u8, field_ptr: &T) -> &ParentType + +Given a pointer to a field, returns the base pointer of a struct. diff --git a/src/all_types.hpp b/src/all_types.hpp index 0c6af587da..80f17afdd4 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -1189,6 +1189,7 @@ enum BuiltinFnId { BuiltinFnIdPtrCast, BuiltinFnIdIntToPtr, BuiltinFnIdEnumTagName, + BuiltinFnIdFieldParentPtr, }; struct BuiltinFnEntry { @@ -1736,6 +1737,7 @@ enum IrInstructionId { IrInstructionIdPanic, IrInstructionIdEnumTagName, IrInstructionIdSetFnRefInline, + IrInstructionIdFieldParentPtr, }; struct IrInstruction { @@ -2488,6 +2490,15 @@ struct IrInstructionSetFnRefInline { IrInstruction *fn_ref; }; +struct IrInstructionFieldParentPtr { + IrInstruction base; + + IrInstruction *type_value; + IrInstruction *field_name; + IrInstruction *field_ptr; + TypeStructField *field; +}; + 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 7222cd3fc8..d5f1cfe4f6 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -2261,6 +2261,34 @@ static LLVMValueRef ir_render_enum_tag_name(CodeGen *g, IrExecutable *executable return LLVMBuildInBoundsGEP(g->builder, enum_tag_type->data.enum_tag.name_table, indices, 2, ""); } +static LLVMValueRef ir_render_field_parent_ptr(CodeGen *g, IrExecutable *executable, + IrInstructionFieldParentPtr *instruction) +{ + TypeTableEntry *container_ptr_type = instruction->base.value.type; + assert(container_ptr_type->id == TypeTableEntryIdPointer); + + TypeTableEntry *container_type = container_ptr_type->data.pointer.child_type; + + size_t byte_offset = LLVMOffsetOfElement(g->target_data_ref, + container_type->type_ref, instruction->field->gen_index); + + LLVMValueRef field_ptr_val = ir_llvm_value(g, instruction->field_ptr); + + if (byte_offset == 0) { + return LLVMBuildBitCast(g->builder, field_ptr_val, container_ptr_type->type_ref, ""); + } else { + TypeTableEntry *usize = g->builtin_types.entry_usize; + + LLVMValueRef field_ptr_int = LLVMBuildPtrToInt(g->builder, field_ptr_val, + usize->type_ref, ""); + + LLVMValueRef base_ptr_int = LLVMBuildNUWSub(g->builder, field_ptr_int, + LLVMConstInt(usize->type_ref, byte_offset, false), ""); + + return LLVMBuildIntToPtr(g->builder, base_ptr_int, container_ptr_type->type_ref, ""); + } +} + static LLVMAtomicOrdering to_LLVMAtomicOrdering(AtomicOrder atomic_order) { switch (atomic_order) { @@ -2963,6 +2991,8 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable, return ir_render_panic(g, executable, (IrInstructionPanic *)instruction); case IrInstructionIdEnumTagName: return ir_render_enum_tag_name(g, executable, (IrInstructionEnumTagName *)instruction); + case IrInstructionIdFieldParentPtr: + return ir_render_field_parent_ptr(g, executable, (IrInstructionFieldParentPtr *)instruction); } zig_unreachable(); } @@ -4509,6 +4539,7 @@ static void define_builtin_fns(CodeGen *g) { create_builtin_fn(g, BuiltinFnIdPtrCast, "ptrcast", 2); create_builtin_fn(g, BuiltinFnIdIntToPtr, "intToPtr", 2); create_builtin_fn(g, BuiltinFnIdEnumTagName, "enumTagName", 1); + create_builtin_fn(g, BuiltinFnIdFieldParentPtr, "fieldParentPtr", 3); } static void add_compile_var(CodeGen *g, const char *name, ConstExprValue *value) { diff --git a/src/ir.cpp b/src/ir.cpp index 9b6cb8ab36..8bdf90057c 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -549,6 +549,10 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionSetFnRefInline * return IrInstructionIdSetFnRefInline; } +static constexpr IrInstructionId ir_instruction_id(IrInstructionFieldParentPtr *) { + return IrInstructionIdFieldParentPtr; +} + template static T *ir_create_instruction(IrBuilder *irb, Scope *scope, AstNode *source_node) { T *special_instruction = allocate(1); @@ -2162,6 +2166,23 @@ static IrInstruction *ir_build_set_fn_ref_inline(IrBuilder *irb, Scope *scope, A return &instruction->base; } +static IrInstruction *ir_build_field_parent_ptr(IrBuilder *irb, Scope *scope, AstNode *source_node, + IrInstruction *type_value, IrInstruction *field_name, IrInstruction *field_ptr, TypeStructField *field) +{ + IrInstructionFieldParentPtr *instruction = ir_build_instruction( + irb, scope, source_node); + instruction->type_value = type_value; + instruction->field_name = field_name; + instruction->field_ptr = field_ptr; + instruction->field = field; + + ir_ref_instruction(type_value, irb->current_basic_block); + ir_ref_instruction(field_name, irb->current_basic_block); + ir_ref_instruction(field_ptr, irb->current_basic_block); + + return &instruction->base; +} + static IrInstruction *ir_instruction_br_get_dep(IrInstructionBr *instruction, size_t index) { return nullptr; } @@ -2833,6 +2854,15 @@ static IrInstruction *ir_instruction_setfnrefinline_get_dep(IrInstructionSetFnRe } } +static IrInstruction *ir_instruction_fieldparentptr_get_dep(IrInstructionFieldParentPtr *instruction, size_t index) { + switch (index) { + case 0: return instruction->type_value; + case 1: return instruction->field_name; + case 2: return instruction->field_ptr; + default: return nullptr; + } +} + static IrInstruction *ir_instruction_get_dep(IrInstruction *instruction, size_t index) { switch (instruction->id) { @@ -3026,6 +3056,8 @@ static IrInstruction *ir_instruction_get_dep(IrInstruction *instruction, size_t return ir_instruction_enumtagname_get_dep((IrInstructionEnumTagName *) instruction, index); case IrInstructionIdSetFnRefInline: return ir_instruction_setfnrefinline_get_dep((IrInstructionSetFnRefInline *) instruction, index); + case IrInstructionIdFieldParentPtr: + return ir_instruction_fieldparentptr_get_dep((IrInstructionFieldParentPtr *) instruction, index); } zig_unreachable(); } @@ -4333,6 +4365,25 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo IrInstruction *actual_tag = ir_build_enum_tag(irb, scope, node, arg0_value); return ir_build_enum_tag_name(irb, scope, node, actual_tag); } + case BuiltinFnIdFieldParentPtr: + { + 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; + + AstNode *arg2_node = node->data.fn_call_expr.params.at(2); + IrInstruction *arg2_value = ir_gen_node(irb, arg2_node, scope); + if (arg2_value == irb->codegen->invalid_instruction) + return arg2_value; + + return ir_build_field_parent_ptr(irb, scope, node, arg0_value, arg1_value, arg2_value, nullptr); + } } zig_unreachable(); } @@ -11263,6 +11314,89 @@ static TypeTableEntry *ir_analyze_instruction_set_fn_ref_inline(IrAnalyze *ira, } } +static TypeTableEntry *ir_analyze_instruction_field_parent_ptr(IrAnalyze *ira, + IrInstructionFieldParentPtr *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; + + 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; + + IrInstruction *field_ptr = instruction->field_ptr->other; + if (type_is_invalid(field_ptr->value.type)) + 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; + } + + ensure_complete_type(ira->codegen, container_type); + + 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; + } + + if (field_ptr->value.type->id != TypeTableEntryIdPointer) { + ir_add_error(ira, field_ptr, + buf_sprintf("expected pointer, found '%s'", buf_ptr(&field_ptr->value.type->name))); + return ira->codegen->builtin_types.entry_invalid; + } + + TypeTableEntry *field_ptr_type = get_pointer_to_type_extra(ira->codegen, field->type_entry, + field_ptr->value.type->data.pointer.is_const, + field_ptr->value.type->data.pointer.is_volatile, 0, 0); + IrInstruction *casted_field_ptr = ir_implicit_cast(ira, field_ptr, field_ptr_type); + if (type_is_invalid(casted_field_ptr->value.type)) + return ira->codegen->builtin_types.entry_invalid; + + TypeTableEntry *result_type = get_pointer_to_type_extra(ira->codegen, container_type, + casted_field_ptr->value.type->data.pointer.is_const, + casted_field_ptr->value.type->data.pointer.is_volatile, 0, 0); + + if (instr_is_comptime(casted_field_ptr)) { + ConstExprValue *field_ptr_val = ir_resolve_const(ira, casted_field_ptr, UndefBad); + if (!field_ptr_val) + return ira->codegen->builtin_types.entry_invalid; + + if (field_ptr_val->data.x_ptr.special != ConstPtrSpecialBaseStruct) { + ir_add_error(ira, field_ptr, buf_sprintf("pointer value not based on parent struct")); + return ira->codegen->builtin_types.entry_invalid; + } + + size_t ptr_field_index = field_ptr_val->data.x_ptr.data.base_struct.field_index; + if (ptr_field_index != field->src_index) { + ir_add_error(ira, &instruction->base, + buf_sprintf("field '%s' has index %zu but pointer value is index %zu of struct '%s'", + buf_ptr(field->name), field->src_index, + ptr_field_index, buf_ptr(&container_type->name))); + return ira->codegen->builtin_types.entry_invalid; + } + + ConstExprValue *out_val = ir_build_const_from(ira, &instruction->base); + out_val->data.x_ptr.special = ConstPtrSpecialRef; + out_val->data.x_ptr.data.ref.pointee = field_ptr_val->data.x_ptr.data.base_struct.struct_val; + out_val->data.x_ptr.mut = field_ptr_val->data.x_ptr.mut; + + return result_type; + } + + IrInstruction *result = ir_build_field_parent_ptr(&ira->new_irb, instruction->base.scope, + instruction->base.source_node, type_value, field_name_value, casted_field_ptr, field); + ir_link_new_instruction(result, &instruction->base); + return result_type; +} + 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); @@ -12781,6 +12915,8 @@ static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructi return ir_analyze_instruction_enum_tag_name(ira, (IrInstructionEnumTagName *)instruction); case IrInstructionIdSetFnRefInline: return ir_analyze_instruction_set_fn_ref_inline(ira, (IrInstructionSetFnRefInline *)instruction); + case IrInstructionIdFieldParentPtr: + return ir_analyze_instruction_field_parent_ptr(ira, (IrInstructionFieldParentPtr *)instruction); case IrInstructionIdMaybeWrap: case IrInstructionIdErrWrapCode: case IrInstructionIdErrWrapPayload: @@ -12964,6 +13100,7 @@ bool ir_has_side_effects(IrInstruction *instruction) { case IrInstructionIdTypeName: case IrInstructionIdEnumTagName: case IrInstructionIdSetFnRefInline: + case IrInstructionIdFieldParentPtr: return false; case IrInstructionIdAsm: { diff --git a/src/ir_print.cpp b/src/ir_print.cpp index 7670dfa09e..569f038895 100644 --- a/src/ir_print.cpp +++ b/src/ir_print.cpp @@ -875,6 +875,16 @@ static void ir_print_set_fn_ref_inline(IrPrint *irp, IrInstructionSetFnRefInline ir_print_other_instruction(irp, instruction->fn_ref); } +static void ir_print_field_parent_ptr(IrPrint *irp, IrInstructionFieldParentPtr *instruction) { + fprintf(irp->f, "@fieldParentPtr("); + ir_print_other_instruction(irp, instruction->type_value); + fprintf(irp->f, ","); + ir_print_other_instruction(irp, instruction->field_name); + fprintf(irp->f, ","); + ir_print_other_instruction(irp, instruction->field_ptr); + fprintf(irp->f, ")"); +} + static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) { ir_print_prefix(irp, instruction); switch (instruction->id) { @@ -1162,6 +1172,9 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) { case IrInstructionIdSetFnRefInline: ir_print_set_fn_ref_inline(irp, (IrInstructionSetFnRefInline *)instruction); break; + case IrInstructionIdFieldParentPtr: + ir_print_field_parent_ptr(irp, (IrInstructionFieldParentPtr *)instruction); + break; } fprintf(irp->f, "\n"); } diff --git a/std/build.zig b/std/build.zig index ad42caa506..ca3cb6554a 100644 --- a/std/build.zig +++ b/std/build.zig @@ -580,9 +580,7 @@ const Exe = struct { } fn make(step: &Step) -> %void { - // TODO issue #320 - //const self = @fieldParentPtr(Exe, "step", step); - const exe = @ptrcast(&Exe, step); + const exe = @fieldParentPtr(Exe, "step", step); const builder = exe.builder; var zig_args = List([]const u8).init(builder.allocator); @@ -754,9 +752,7 @@ const CLibrary = struct { } fn make(step: &Step) -> %void { - // TODO issue #320 - //const self = @fieldParentPtr(CLibrary, "step", step); - const self = @ptrcast(&CLibrary, step); + const self = @fieldParentPtr(CLibrary, "step", step); const cc = os.getEnv("CC") ?? "cc"; const builder = self.builder; @@ -897,9 +893,7 @@ const CExecutable = struct { } fn make(step: &Step) -> %void { - // TODO issue #320 - //const self = @fieldParentPtr(CExecutable, "step", step); - const self = @ptrcast(&CExecutable, step); + const self = @fieldParentPtr(CExecutable, "step", step); const cc = os.getEnv("CC") ?? "cc"; const builder = self.builder; @@ -987,9 +981,7 @@ const CommandStep = struct { } fn make(step: &Step) -> %void { - // TODO issue #320 - //const self = @fieldParentPtr(CExecutable, "step", step); - const self = @ptrcast(&CommandStep, step); + const self = @fieldParentPtr(CommandStep, "step", step); // TODO set cwd self.builder.spawnChildEnvMap(self.env_map, self.exe_path, self.args); @@ -1021,9 +1013,7 @@ const InstallCLibraryStep = struct { } fn make(step: &Step) -> %void { - // TODO issue #320 - //const self = @fieldParentPtr(InstallCLibraryStep, "step", step); - const self = @ptrcast(&InstallCLibraryStep, step); + const self = @fieldParentPtr(InstallCLibraryStep, "step", step); self.builder.copyFile(self.lib.out_filename, self.dest_file); if (!self.lib.static) { @@ -1051,9 +1041,7 @@ const InstallFileStep = struct { } fn make(step: &Step) -> %void { - // TODO issue #320 - //const self = @fieldParentPtr(InstallFileStep, "step", step); - const self = @ptrcast(&InstallFileStep, step); + const self = @fieldParentPtr(InstallFileStep, "step", step); debug.panic("TODO install file"); } diff --git a/std/mem.zig b/std/mem.zig index e5b05071e9..99bfb0adee 100644 --- a/std/mem.zig +++ b/std/mem.zig @@ -76,9 +76,7 @@ pub const IncrementingAllocator = struct { } fn alloc(allocator: &Allocator, n: usize) -> %[]u8 { - // TODO issue #320 - //const self = @fieldParentPtr(IncrementingAllocator, "allocator", allocator); - const self = @ptrcast(&IncrementingAllocator, allocator); + const self = @fieldParentPtr(IncrementingAllocator, "allocator", allocator); const new_end_index = self.end_index + n; if (new_end_index > self.bytes.len) { return error.NoMem; diff --git a/test/cases/field_parent_ptr.zig b/test/cases/field_parent_ptr.zig new file mode 100644 index 0000000000..fcb5c544a3 --- /dev/null +++ b/test/cases/field_parent_ptr.zig @@ -0,0 +1,41 @@ +const assert = @import("std").debug.assert; + +test "@fieldParentPtr non-first field" { + testParentFieldPtr(&foo.c); + comptime testParentFieldPtr(&foo.c); +} + +test "@fieldParentPtr first field" { + testParentFieldPtrFirst(&foo.a); + comptime testParentFieldPtrFirst(&foo.a); +} + +const Foo = struct { + a: bool, + b: f32, + c: i32, + d: i32, +}; + +const foo = Foo { + .a = true, + .b = 0.123, + .c = 1234, + .d = -10, +}; + +fn testParentFieldPtr(c: &const i32) { + assert(c == &foo.c); + + const base = @fieldParentPtr(Foo, "c", c); + assert(base == &foo); + assert(&base.c == c); +} + +fn testParentFieldPtrFirst(a: &const bool) { + assert(a == &foo.a); + + const base = @fieldParentPtr(Foo, "a", a); + assert(base == &foo); + assert(&base.a == a); +} diff --git a/test/run_tests.cpp b/test/run_tests.cpp index f928b05e87..e7b041ee3f 100644 --- a/test/run_tests.cpp +++ b/test/run_tests.cpp @@ -1902,6 +1902,56 @@ export fn foo() { var y: &void = @intToPtr(&void, x); } )SOURCE", 1, ".tmp_source.zig:4:31: error: type '&void' has 0 bits and cannot store information"); + + add_compile_fail_case("@fieldParentPtr - non struct", R"SOURCE( +const Foo = i32; +export fn foo(a: &i32) -> &Foo { + return @fieldParentPtr(Foo, "a", a); +} + )SOURCE", 1, ".tmp_source.zig:4:28: error: expected struct type, found 'i32'"); + + add_compile_fail_case("@fieldParentPtr - bad field name", R"SOURCE( +const Foo = struct { + derp: i32, +}; +export fn foo(a: &i32) -> &Foo { + return @fieldParentPtr(Foo, "a", a); +} + )SOURCE", 1, ".tmp_source.zig:6:33: error: struct 'Foo' has no field 'a'"); + + add_compile_fail_case("@fieldParentPtr - field pointer is not pointer", R"SOURCE( +const Foo = struct { + a: i32, +}; +export fn foo(a: i32) -> &Foo { + return @fieldParentPtr(Foo, "a", a); +} + )SOURCE", 1, ".tmp_source.zig:6:38: error: expected pointer, found 'i32'"); + + add_compile_fail_case("@fieldParentPtr - comptime field ptr not based on struct", R"SOURCE( +const Foo = struct { + a: i32, + b: i32, +}; +const foo = Foo { .a = 1, .b = 2, }; + +comptime { + const field_ptr = @intToPtr(&i32, 0x1234); + const another_foo_ptr = @fieldParentPtr(Foo, "b", field_ptr); +} + )SOURCE", 1, ".tmp_source.zig:10:55: error: pointer value not based on parent struct"); + + add_compile_fail_case("@fieldParentPtr - comptime wrong field index", R"SOURCE( +const Foo = struct { + a: i32, + b: i32, +}; +const foo = Foo { .a = 1, .b = 2, }; + +comptime { + const another_foo_ptr = @fieldParentPtr(Foo, "b", &foo.a); +} + )SOURCE", 1, ".tmp_source.zig:9:29: error: field 'b' has index 1 but pointer value is index 0 of struct 'Foo'"); } ////////////////////////////////////////////////////////////////////////////// diff --git a/test/self_hosted.zig b/test/self_hosted.zig index 1ffdfbc459..98d0277f78 100644 --- a/test/self_hosted.zig +++ b/test/self_hosted.zig @@ -10,6 +10,7 @@ comptime { _ = @import("cases/enum_with_members.zig"); _ = @import("cases/error.zig"); _ = @import("cases/eval.zig"); + _ = @import("cases/field_parent_ptr.zig"); _ = @import("cases/fn.zig"); _ = @import("cases/for.zig"); _ = @import("cases/generics.zig");