diff --git a/src/all_types.hpp b/src/all_types.hpp index c1b1542467..5a11c12bba 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -1037,6 +1037,7 @@ struct TypeTableEntry { ZigLLVMDIType *di_type; bool zero_bits; + bool is_copyable; union { TypeTableEntryPointer pointer; diff --git a/src/analyze.cpp b/src/analyze.cpp index d3617ad9c8..bfec62e5de 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -300,6 +300,18 @@ uint64_t type_size_bits(CodeGen *g, TypeTableEntry *type_entry) { return LLVMSizeOfTypeInBits(g->target_data_ref, canon_type->type_ref); } +static bool type_is_copyable(CodeGen *g, TypeTableEntry *type_entry) { + type_ensure_zero_bits_known(g, type_entry); + if (!type_has_bits(type_entry)) + return true; + + if (!handle_is_ptr(type_entry)) + return true; + + ensure_complete_type(g, type_entry); + return type_entry->is_copyable; +} + static bool is_slice(TypeTableEntry *type) { return type->id == TypeTableEntryIdStruct && type->data.structure.is_slice; } @@ -336,6 +348,7 @@ TypeTableEntry *get_pointer_to_type_extra(CodeGen *g, TypeTableEntry *child_type type_ensure_zero_bits_known(g, child_type); TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdPointer); + entry->is_copyable = true; const char *const_str = is_const ? "const " : ""; const char *volatile_str = is_volatile ? "volatile " : ""; @@ -392,6 +405,7 @@ TypeTableEntry *get_maybe_type(CodeGen *g, TypeTableEntry *child_type) { TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdMaybe); assert(child_type->type_ref); assert(child_type->di_type); + entry->is_copyable = type_is_copyable(g, child_type); buf_resize(&entry->name, 0); buf_appendf(&entry->name, "?%s", buf_ptr(&child_type->name)); @@ -471,6 +485,7 @@ TypeTableEntry *get_error_type(CodeGen *g, TypeTableEntry *child_type) { return child_type->error_parent; TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdErrorUnion); + entry->is_copyable = true; assert(child_type->type_ref); assert(child_type->di_type); ensure_complete_type(g, child_type); @@ -557,6 +572,7 @@ TypeTableEntry *get_array_type(CodeGen *g, TypeTableEntry *child_type, uint64_t TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdArray); entry->zero_bits = (array_size == 0) || child_type->zero_bits; + entry->is_copyable = false; buf_resize(&entry->name, 0); buf_appendf(&entry->name, "[%" PRIu64 "]%s", array_size, buf_ptr(&child_type->name)); @@ -614,6 +630,7 @@ TypeTableEntry *get_slice_type(CodeGen *g, TypeTableEntry *child_type, bool is_c } else if (is_const) { TypeTableEntry *var_peer = get_slice_type(g, child_type, false); TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdStruct); + entry->is_copyable = true; buf_resize(&entry->name, 0); buf_appendf(&entry->name, "[]const %s", buf_ptr(&child_type->name)); @@ -629,6 +646,7 @@ TypeTableEntry *get_slice_type(CodeGen *g, TypeTableEntry *child_type, bool is_c return entry; } else { TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdStruct); + entry->is_copyable = true; // If the child type is []const T then we need to make sure the type ref // and debug info is the same as if the child type were []T. @@ -752,6 +770,7 @@ TypeTableEntry *get_typedecl_type(CodeGen *g, const char *name, TypeTableEntry * buf_init_from_str(&entry->name, name); + entry->is_copyable = type_is_copyable(g, child_type); entry->type_ref = child_type->type_ref; entry->di_type = child_type->di_type; entry->zero_bits = child_type->zero_bits; @@ -768,6 +787,7 @@ TypeTableEntry *get_bound_fn_type(CodeGen *g, FnTableEntry *fn_entry) { return fn_type->data.fn.bound_fn_parent; TypeTableEntry *bound_fn_type = new_type_table_entry(TypeTableEntryIdBoundFn); + bound_fn_type->is_copyable = false; bound_fn_type->data.bound_fn.fn_type = fn_type; bound_fn_type->zero_bits = true; @@ -786,6 +806,7 @@ TypeTableEntry *get_fn_type(CodeGen *g, FnTypeId *fn_type_id) { ensure_complete_type(g, fn_type_id->return_type); TypeTableEntry *fn_type = new_type_table_entry(TypeTableEntryIdFn); + fn_type->is_copyable = true; fn_type->data.fn.fn_type_id = *fn_type_id; if (fn_type_id->is_cold) { @@ -972,6 +993,7 @@ TypeTableEntry *analyze_type_expr(CodeGen *g, Scope *scope, AstNode *node) { static TypeTableEntry *get_generic_fn_type(CodeGen *g, FnTypeId *fn_type_id) { TypeTableEntry *fn_type = new_type_table_entry(TypeTableEntryIdFn); + fn_type->is_copyable = false; buf_init_from_str(&fn_type->name, "fn("); size_t i = 0; for (; i < fn_type_id->next_param_index; i += 1) { @@ -1078,6 +1100,12 @@ static TypeTableEntry *analyze_fn_type(CodeGen *g, AstNode *proto_node, Scope *c case TypeTableEntryIdFn: case TypeTableEntryIdTypeDecl: case TypeTableEntryIdEnumTag: + ensure_complete_type(g, type_entry); + if (!fn_type_id.is_extern && !type_is_copyable(g, type_entry)) { + add_node_error(g, param_node->data.param_decl.type, + buf_sprintf("type '%s' is not copyable; cannot pass by value", buf_ptr(&type_entry->name))); + return g->builtin_types.entry_invalid; + } break; } FnTypeParamInfo *param_info = &fn_type_id.param_info[fn_type_id.next_param_index]; @@ -1159,6 +1187,7 @@ static TypeTableEntry *create_enum_tag_type(CodeGen *g, TypeTableEntry *enum_typ buf_resize(&entry->name, 0); buf_appendf(&entry->name, "@enumTagType(%s)", buf_ptr(&enum_type->name)); + entry->is_copyable = true; entry->data.enum_tag.enum_type = enum_type; entry->data.enum_tag.int_type = int_type; entry->type_ref = int_type->type_ref; @@ -4007,6 +4036,7 @@ void render_const_value(Buf *buf, ConstExprValue *const_val) { TypeTableEntry *make_int_type(CodeGen *g, bool is_signed, size_t size_in_bits) { TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdInt); + entry->is_copyable = true; entry->type_ref = LLVMIntType(size_in_bits); const char u_or_i = is_signed ? 'i' : 'u'; diff --git a/src/codegen.cpp b/src/codegen.cpp index 66d3e35246..94aec5664d 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -261,6 +261,13 @@ static void addLLVMArgAttr(LLVMValueRef arg_val, unsigned param_index, const cha return addLLVMAttr(arg_val, param_index + 1, attr_name); } +static void addLLVMCallsiteAttr(LLVMValueRef call_instr, unsigned param_index, const char *attr_name) { + unsigned kind_id = LLVMGetEnumAttributeKindForName(attr_name, strlen(attr_name)); + assert(kind_id != 0); + LLVMAttributeRef llvm_attr = LLVMCreateEnumAttribute(LLVMGetGlobalContext(), kind_id, 0); + LLVMAddCallSiteAttribute(call_instr, param_index + 1, llvm_attr); +} + static Buf *get_mangled_name(CodeGen *g, Buf *original_name, bool external_linkage) { if (external_linkage || g->external_symbol_names.maybe_get(original_name) == nullptr) { return original_name; @@ -1578,11 +1585,12 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutable *executable, IrInstr fn_type = instruction->fn_ref->value.type; } - TypeTableEntry *src_return_type = fn_type->data.fn.fn_type_id.return_type; + FnTypeId *fn_type_id = &fn_type->data.fn.fn_type_id; + TypeTableEntry *src_return_type = fn_type_id->return_type; bool ret_has_bits = type_has_bits(src_return_type); bool first_arg_ret = ret_has_bits && handle_is_ptr(src_return_type); size_t actual_param_count = instruction->arg_count + (first_arg_ret ? 1 : 0); - bool is_var_args = fn_type->data.fn.fn_type_id.is_var_args; + bool is_var_args = fn_type_id->is_var_args; LLVMValueRef *gen_param_values = allocate(actual_param_count); size_t gen_param_index = 0; if (first_arg_ret) { @@ -1603,6 +1611,13 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutable *executable, IrInstr LLVMValueRef result = ZigLLVMBuildCall(g->builder, fn_val, gen_param_values, gen_param_index, fn_type->data.fn.calling_convention, ""); + for (size_t param_i = 0; param_i < fn_type_id->param_count; param_i += 1) { + FnGenParamInfo *gen_info = &fn_type->data.fn.gen_param_info[param_i]; + if (gen_info->is_byval) { + addLLVMCallsiteAttr(result, gen_info->gen_index, "byval"); + } + } + if (src_return_type->id == TypeTableEntryIdUnreachable) { return LLVMBuildUnreachable(g->builder); } else if (!ret_has_bits) { @@ -3376,7 +3391,7 @@ static void do_code_gen(CodeGen *g) { addLLVMArgAttr(fn_val, gen_index, "nonnull"); } if (is_byval) { - // TODO add byval attr? + addLLVMArgAttr(fn_val, gen_index, "byval"); } } diff --git a/src/ir.cpp b/src/ir.cpp index ca80f58677..b6a7c08a0e 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -9905,7 +9905,6 @@ static TypeTableEntry *ir_analyze_instruction_size_of(IrAnalyze *ira, case TypeTableEntryIdNumLitInt: case TypeTableEntryIdBoundFn: case TypeTableEntryIdMetaType: - case TypeTableEntryIdFn: case TypeTableEntryIdNamespace: case TypeTableEntryIdArgTuple: ir_add_error_node(ira, size_of_instruction->base.source_node, @@ -9925,6 +9924,7 @@ static TypeTableEntry *ir_analyze_instruction_size_of(IrAnalyze *ira, case TypeTableEntryIdEnum: case TypeTableEntryIdUnion: case TypeTableEntryIdEnumTag: + case TypeTableEntryIdFn: { uint64_t size_in_bytes = type_size(ira->codegen, type_entry); ConstExprValue *out_val = ir_build_const_from(ira, &size_of_instruction->base); diff --git a/std/list.zig b/std/list.zig index c54332d9ef..d01e3a88a4 100644 --- a/std/list.zig +++ b/std/list.zig @@ -34,9 +34,9 @@ pub fn List(comptime T: type) -> type{ return l.items[0...l.len]; } - pub fn append(l: &Self, item: T) -> %void { + pub fn append(l: &Self, item: &const T) -> %void { const new_item_ptr = %return l.addOne(); - *new_item_ptr = item; + *new_item_ptr = *item; } pub fn resize(l: &Self, new_len: usize) -> %void { diff --git a/test/cases/enum.zig b/test/cases/enum.zig index d3534115b5..395a37caf1 100644 --- a/test/cases/enum.zig +++ b/test/cases/enum.zig @@ -48,15 +48,15 @@ test "constantEnumWithPayload" { shouldBeNotEmpty(full); } -fn shouldBeEmpty(x: AnEnumWithPayload) { - switch (x) { +fn shouldBeEmpty(x: &const AnEnumWithPayload) { + switch (*x) { AnEnumWithPayload.Empty => {}, else => @unreachable(), } } -fn shouldBeNotEmpty(x: AnEnumWithPayload) { - switch (x) { +fn shouldBeNotEmpty(x: &const AnEnumWithPayload) { + switch (*x) { AnEnumWithPayload.Empty => @unreachable(), else => {}, } diff --git a/test/cases/incomplete_struct_param_tld.zig b/test/cases/incomplete_struct_param_tld.zig new file mode 100644 index 0000000000..50c078737b --- /dev/null +++ b/test/cases/incomplete_struct_param_tld.zig @@ -0,0 +1,32 @@ +const assert = @import("std").debug.assert; + +const A = struct { + b: B, +}; + +const B = struct { + c: C, +}; + +const C = struct { + x: i32, + + fn d(c: &const C) -> i32 { + return c.x; + } +}; + +fn foo(a: &const A) -> i32 { + return a.b.c.d(); +} + +test "incomplete struct param top level declaration" { + const a = A { + .b = B { + .c = C { + .x = 13, + }, + }, + }; + assert(foo(a) == 13); +} diff --git a/test/cases/misc.zig b/test/cases/misc.zig index 28a5fcd9c7..d78cd12d60 100644 --- a/test/cases/misc.zig +++ b/test/cases/misc.zig @@ -338,8 +338,8 @@ const Test3Point = struct { }; const test3_foo = Test3Foo.Three{Test3Point {.x = 3, .y = 4}}; const test3_bar = Test3Foo.Two{13}; -fn test3_1(f: Test3Foo) { - switch (f) { +fn test3_1(f: &const Test3Foo) { + switch (*f) { Test3Foo.Three => |pt| { assert(pt.x == 3); assert(pt.y == 4); @@ -347,8 +347,8 @@ fn test3_1(f: Test3Foo) { else => @unreachable(), } } -fn test3_2(f: Test3Foo) { - switch (f) { +fn test3_2(f: &const Test3Foo) { + switch (*f) { Test3Foo.Two => |x| { assert(x == 13); }, diff --git a/test/cases/null.zig b/test/cases/null.zig index c502afba69..2194be28eb 100644 --- a/test/cases/null.zig +++ b/test/cases/null.zig @@ -62,8 +62,8 @@ fn foo(x: ?i32) -> ?bool { test "ifVarMaybePointer" { assert(shouldBeAPlus1(Particle {.a = 14, .b = 1, .c = 1, .d = 1}) == 15); } -fn shouldBeAPlus1(p: Particle) -> u64 { - var maybe_particle: ?Particle = p; +fn shouldBeAPlus1(p: &const Particle) -> u64 { + var maybe_particle: ?Particle = *p; if (const *particle ?= maybe_particle) { particle.a += 1; } diff --git a/test/cases/struct.zig b/test/cases/struct.zig index 5d2c2ba608..b927c01941 100644 --- a/test/cases/struct.zig +++ b/test/cases/struct.zig @@ -53,10 +53,10 @@ const StructFoo = struct { b : bool, c : f32, }; -fn testFoo(foo : StructFoo) { +fn testFoo(foo: &const StructFoo) { assert(foo.b); } -fn testMutation(foo : &StructFoo) { +fn testMutation(foo: &StructFoo) { foo.c = 100; } @@ -110,7 +110,7 @@ const Foo = struct { fn aFunc() -> i32 { 13 } -fn callStructField(foo: Foo) -> i32 { +fn callStructField(foo: &const Foo) -> i32 { return foo.ptr(); } @@ -123,7 +123,7 @@ test "storeMemberFunctionInVariable" { } const MemberFnTestFoo = struct { x: i32, - fn member(foo: MemberFnTestFoo) -> i32 { foo.x } + fn member(foo: &const MemberFnTestFoo) -> i32 { foo.x } }; diff --git a/test/cases/switch.zig b/test/cases/switch.zig index 00046e38ce..9f57f45fc8 100644 --- a/test/cases/switch.zig +++ b/test/cases/switch.zig @@ -92,8 +92,8 @@ const SwitchProngWithVarEnum = enum { Two: f32, Meh, }; -fn switchProngWithVarFn(a: SwitchProngWithVarEnum) { - switch(a) { +fn switchProngWithVarFn(a: &const SwitchProngWithVarEnum) { + switch(*a) { SwitchProngWithVarEnum.One => |x| { if (x != 13) @unreachable(); }, diff --git a/test/run_tests.cpp b/test/run_tests.cpp index 187ace42c7..b866d5682f 100644 --- a/test/run_tests.cpp +++ b/test/run_tests.cpp @@ -512,42 +512,6 @@ export fn main(argc: c_int, argv: &&u8) -> c_int { )SOURCE", "3.25\n3\n3.00\n-0.40\n"); - add_simple_case("incomplete struct parameter top level decl", R"SOURCE( -const io = @import("std").io; -const A = struct { - b: B, -}; - -const B = struct { - c: C, -}; - -const C = struct { - x: i32, - - fn d(c: &const C) { - %%io.stdout.printf("OK\n"); - } -}; - -fn foo(a: A) { - a.b.c.d(); -} - -pub fn main(args: [][]u8) -> %void { - const a = A { - .b = B { - .c = C { - .x = 13, - }, - }, - }; - foo(a); -} - - )SOURCE", "OK\n"); - - add_simple_case("same named methods in incomplete struct", R"SOURCE( const io = @import("std").io; @@ -1037,9 +1001,10 @@ export fn entry() -> usize { @sizeOf(@typeOf(a)) } )SOURCE", 1, ".tmp_source.zig:4:11: error: expected array or C string literal, found 'usize'"); add_compile_fail_case("non compile time array concatenation", R"SOURCE( -fn f(s: [10]u8) -> []u8 { +fn f() -> []u8 { s ++ "foo" } +var s: [10]u8 = undefined; export fn entry() -> usize { @sizeOf(@typeOf(f)) } )SOURCE", 1, ".tmp_source.zig:3:5: error: unable to evaluate constant expression"); @@ -1071,10 +1036,10 @@ const Foo = struct { a: i32, b: i32, - fn member_a(foo: Foo) -> i32 { + fn member_a(foo: &const Foo) -> i32 { return foo.a; } - fn member_b(foo: Foo) -> i32 { + fn member_b(foo: &const Foo) -> i32 { return foo.b; } }; @@ -1085,7 +1050,7 @@ const members = []member_fn_type { Foo.member_b, }; -fn f(foo: Foo, index: usize) { +fn f(foo: &const Foo, index: usize) { const result = members[index](); } @@ -1335,15 +1300,15 @@ const EnumWithData = enum { One, Two: i32, }; -fn bad_eql_2(a: EnumWithData, b: EnumWithData) -> bool { - a == b +fn bad_eql_2(a: &const EnumWithData, b: &const EnumWithData) -> bool { + *a == *b } export fn entry1() -> usize { @sizeOf(@typeOf(bad_eql_1)) } export fn entry2() -> usize { @sizeOf(@typeOf(bad_eql_2)) } )SOURCE", 2, ".tmp_source.zig:3:7: error: operator not allowed for type '[]u8'", - ".tmp_source.zig:10:7: error: operator not allowed for type 'EnumWithData'"); + ".tmp_source.zig:10:8: error: operator not allowed for type 'EnumWithData'"); add_compile_fail_case("non-const switch number literal", R"SOURCE( export fn foo() { @@ -1845,6 +1810,12 @@ export fn bar() {} pub const baz = 1234; )SOURCE"); } + + add_compile_fail_case("pass non-copyable type by value to function", R"SOURCE( +const Point = struct { x: i32, y: i32, }; +fn foo(p: Point) { } +export fn entry() -> usize { @sizeOf(@typeOf(foo)) } + )SOURCE", 1, ".tmp_source.zig:3:11: error: type 'Point' is not copyable; cannot pass by value"); } ////////////////////////////////////////////////////////////////////////////// diff --git a/test/self_hosted.zig b/test/self_hosted.zig index 7830e67ea4..a6888e2ea0 100644 --- a/test/self_hosted.zig +++ b/test/self_hosted.zig @@ -15,6 +15,7 @@ const test_generics = @import("cases/generics.zig"); const test_goto = @import("cases/goto.zig"); const test_if = @import("cases/if.zig"); const test_import = @import("cases/import.zig"); +const test_incomplete_struct_param_tld = @import("cases/incomplete_struct_param_tld.zig"); const test_ir_block_deps = @import("cases/ir_block_deps.zig"); const test_math = @import("cases/math.zig"); const test_misc = @import("cases/misc.zig");