From cf39819478e237255109d0343e642db70e88071b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 22 Jan 2018 22:24:07 -0500 Subject: [PATCH] add new kind of test: generating .h files. and more * docgen supports obj_err code kind for demonstrating errors without explicit test cases * add documentation for `extern enum`. See #367 * remove coldcc keyword and add @setIsCold. See #661 * add compile errors for non-extern struct, enum, unions in function signatures * add .h file generation for extern struct, enum, unions --- build.zig | 1 + doc/docgen.zig | 44 ++++++- doc/langref.html.in | 34 +++++- src-self-hosted/parser.zig | 2 +- src-self-hosted/tokenizer.zig | 2 - src/all_types.hpp | 12 ++ src/analyze.cpp | 154 ++++++++++++++++++------- src/codegen.cpp | 186 ++++++++++++++++++++++++++++-- src/ir.cpp | 55 +++++++++ src/ir_print.cpp | 9 ++ src/parser.cpp | 8 +- src/tokenizer.cpp | 2 - src/tokenizer.hpp | 1 - std/os/index.zig | 6 +- std/special/builtin.zig | 3 +- std/special/compiler_rt/index.zig | 3 +- std/special/panic.zig | 3 +- test/cases/misc.zig | 9 ++ test/compile_errors.zig | 27 ++++- test/gen_h.zig | 53 +++++++++ test/tests.zig | 151 +++++++++++++++++++++++- 21 files changed, 682 insertions(+), 83 deletions(-) create mode 100644 test/gen_h.zig diff --git a/build.zig b/build.zig index 189e0e156f05585c6ed44d9de3fa6e7c056875e8..d48c58bbffc0a4f3824916131ac52341f2214639 100644 --- a/build.zig +++ b/build.zig @@ -118,6 +118,7 @@ pub fn build(b: &Builder) -> %void { test_step.dependOn(tests.addAssembleAndLinkTests(b, test_filter)); test_step.dependOn(tests.addDebugSafetyTests(b, test_filter)); test_step.dependOn(tests.addTranslateCTests(b, test_filter)); + test_step.dependOn(tests.addGenHTests(b, test_filter)); } fn dependOnLib(lib_exe_obj: &std.build.LibExeObjStep, dep: &const LibraryDep) { diff --git a/doc/docgen.zig b/doc/docgen.zig index 76d952420c976a191040d19091d982b7527a434b..9ec827ff3ebed89764f17463f72af04f689d0a19 100644 --- a/doc/docgen.zig +++ b/doc/docgen.zig @@ -286,7 +286,7 @@ const Code = struct { TestError: []const u8, TestSafety: []const u8, Exe: ExpectedOutcome, - Obj, + Obj: ?[]const u8, }; }; @@ -442,9 +442,12 @@ fn genToc(allocator: &mem.Allocator, tokenizer: &Tokenizer) -> %Toc { code_kind_id = Code.Id { .TestSafety = name}; name = "test"; } else if (mem.eql(u8, code_kind_str, "obj")) { - code_kind_id = Code.Id.Obj; + code_kind_id = Code.Id { .Obj = null }; + } else if (mem.eql(u8, code_kind_str, "obj_err")) { + code_kind_id = Code.Id { .Obj = name }; + name = "test"; } else if (mem.eql(u8, code_kind_str, "syntax")) { - code_kind_id = Code.Id.Obj; + code_kind_id = Code.Id { .Obj = null }; is_inline = true; } else { return parseError(tokenizer, code_kind_tok, "unrecognized code kind: {}", code_kind_str); @@ -861,13 +864,14 @@ fn genHtml(allocator: &mem.Allocator, tokenizer: &Tokenizer, toc: &Toc, out: &io const colored_stderr = try termColor(allocator, escaped_stderr); try out.print("
$ zig test {}.zig\n{}
\n", code.name, colored_stderr); }, - Code.Id.Obj => { + Code.Id.Obj => |maybe_error_match| { const name_plus_obj_ext = try std.fmt.allocPrint(allocator, "{}{}", code.name, obj_ext); const tmp_obj_file_name = try os.path.join(allocator, tmp_dir_name, name_plus_obj_ext); var build_args = std.ArrayList([]const u8).init(allocator); defer build_args.deinit(); try build_args.appendSlice([][]const u8 {zig_exe, "build-obj", tmp_source_file_name, + "--color", "on", "--output", tmp_obj_file_name}); if (!code.is_inline) { @@ -890,8 +894,36 @@ fn genHtml(allocator: &mem.Allocator, tokenizer: &Tokenizer, toc: &Toc, out: &io }, } - _ = exec(allocator, build_args.toSliceConst()) catch return parseError( - tokenizer, code.source_token, "example failed to compile"); + if (maybe_error_match) |error_match| { + const result = try os.ChildProcess.exec(allocator, build_args.toSliceConst(), null, null, max_doc_file_size); + switch (result.term) { + os.ChildProcess.Term.Exited => |exit_code| { + if (exit_code == 0) { + warn("{}\nThe following command incorrectly succeeded:\n", result.stderr); + for (build_args.toSliceConst()) |arg| warn("{} ", arg) else warn("\n"); + return parseError(tokenizer, code.source_token, "example build incorrectly succeeded"); + } + }, + else => { + warn("{}\nThe following command crashed:\n", result.stderr); + for (build_args.toSliceConst()) |arg| warn("{} ", arg) else warn("\n"); + return parseError(tokenizer, code.source_token, "example compile crashed"); + }, + } + if (mem.indexOf(u8, result.stderr, error_match) == null) { + warn("{}\nExpected to find '{}' in stderr", result.stderr, error_match); + return parseError(tokenizer, code.source_token, "example did not have expected compile error message"); + } + const escaped_stderr = try escapeHtml(allocator, result.stderr); + const colored_stderr = try termColor(allocator, escaped_stderr); + try out.print("\n{}\n", colored_stderr); + if (!code.is_inline) { + try out.print("\n"); + } + } else { + _ = exec(allocator, build_args.toSliceConst()) catch return parseError( + tokenizer, code.source_token, "example failed to compile"); + } if (!code.is_inline) { try out.print("\n"); } diff --git a/doc/langref.html.in b/doc/langref.html.in index db7d760e9825c28ebd343c99becb901ac7c74ea5..24cd77accd145e33bfc930c48dbbb4134e25fc11 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -1909,7 +1909,22 @@ test "@tagName" { assert(mem.eql(u8, @tagName(Small.Three), "Three")); } {#code_end#} -

TODO extern enum

+ {#header_open|extern enum#} +

+ By default, enums are not guaranteed to be compatible with the C ABI: +

+ {#code_begin|obj_err|parameter of type 'Foo' not allowed in function with calling convention 'ccc'#} +const Foo = enum { A, B, C }; +export fn entry(foo: Foo) { } + {#code_end#} +

+ For a C-ABI-compatible enum, use extern enum: +

+ {#code_begin|obj#} +const Foo = extern enum { A, B, C }; +export fn entry(foo: Foo) { } + {#code_end#} + {#header_close#}

TODO packed enum

{#see_also|@memberName|@memberCount|@tagName#} {#header_close#} @@ -2662,8 +2677,9 @@ export fn sub(a: i8, b: i8) -> i8 { return a - b; } extern "kernel32" stdcallcc fn ExitProcess(exit_code: u32) -> noreturn; extern "c" fn atan2(a: f64, b: f64) -> f64; -// coldcc makes a function use the cold calling convention. -coldcc fn abort() -> noreturn { +// The @setCold builtin tells the optimizer that a function is rarely called. +fn abort() -> noreturn { + @setCold(true); while (true) {} } @@ -4300,6 +4316,12 @@ test "call foo" { This function is only valid within function scope.

{#header_close#} + {#header_open|@setCold#} +
@setCold(is_cold: bool)
+

+ Tells the optimizer that a function is rarely called. +

+ {#header_close#} {#header_open|@setDebugSafety#}
@setDebugSafety(scope, safety_on: bool)

@@ -5533,7 +5555,7 @@ UseDecl = "use" Expression ";" ExternDecl = "extern" option(String) (FnProto | VariableDeclaration) ";" -FnProto = option("coldcc" | "nakedcc" | "stdcallcc" | "extern") "fn" option(Symbol) ParamDeclList option("align" "(" Expression ")") option("section" "(" Expression ")") option("->" TypeExpr) +FnProto = option("nakedcc" | "stdcallcc" | "extern") "fn" option(Symbol) ParamDeclList option("align" "(" Expression ")") option("section" "(" Expression ")") option("->" TypeExpr) FnDef = option("inline" | "export") FnProto Block @@ -5739,8 +5761,8 @@ hljs.registerLanguage("zig", function(t) { }, a = t.IR + "\\s*\\(", c = { - keyword: "const align var extern stdcallcc coldcc nakedcc volatile export pub noalias inline struct packed enum union goto break return try catch test continue unreachable comptime and or asm defer if else switch while for fn use bool f32 f64 void type noreturn error i8 u8 i16 u16 i32 u32 i64 u64 isize usize i8w u8w i16w i32w u32w i64w u64w isizew usizew c_short c_ushort c_int c_uint c_long c_ulong c_longlong c_ulonglong", - built_in: "breakpoint returnAddress frameAddress fieldParentPtr setFloatMode IntType OpaqueType compileError compileLog setDebugSafety setEvalBranchQuota offsetOf memcpy inlineCall setGlobalLinkage setGlobalSection divTrunc divFloor enumTagName intToPtr ptrToInt panic canImplicitCast ptrCast bitCast rem mod memset sizeOf alignOf alignCast maxValue minValue memberCount typeOf addWithOverflow subWithOverflow mulWithOverflow shlWithOverflow shlExact shrExact cInclude cDefine cUndef ctz clz import cImport errorName embedFile cmpxchg fence divExact truncate", + keyword: "const align var extern stdcallcc nakedcc volatile export pub noalias inline struct packed enum union goto break return try catch test continue unreachable comptime and or asm defer if else switch while for fn use bool f32 f64 void type noreturn error i8 u8 i16 u16 i32 u32 i64 u64 isize usize i8w u8w i16w i32w u32w i64w u64w isizew usizew c_short c_ushort c_int c_uint c_long c_ulong c_longlong c_ulonglong", + built_in: "breakpoint returnAddress frameAddress fieldParentPtr setFloatMode IntType OpaqueType compileError compileLog setCold setDebugSafety setEvalBranchQuota offsetOf memcpy inlineCall setGlobalLinkage setGlobalSection divTrunc divFloor enumTagName intToPtr ptrToInt panic canImplicitCast ptrCast bitCast rem mod memset sizeOf alignOf alignCast maxValue minValue memberCount typeOf addWithOverflow subWithOverflow mulWithOverflow shlWithOverflow shlExact shrExact cInclude cDefine cUndef ctz clz import cImport errorName embedFile cmpxchg fence divExact truncate", literal: "true false null undefined" }, n = [e, t.CLCM, t.CBCM, s, r]; diff --git a/src-self-hosted/parser.zig b/src-self-hosted/parser.zig index 88b6dd9bb0a25c57ac2d1fa668fb08d2772d86c5..5e1975339abafff850fff9023d6a4bc65a3bebf6 100644 --- a/src-self-hosted/parser.zig +++ b/src-self-hosted/parser.zig @@ -211,7 +211,7 @@ pub const Parser = struct { Token.Id.StringLiteral => { @panic("TODO extern with string literal"); }, - Token.Id.Keyword_coldcc, Token.Id.Keyword_nakedcc, Token.Id.Keyword_stdcallcc => { + Token.Id.Keyword_nakedcc, Token.Id.Keyword_stdcallcc => { stack.append(State.TopLevel) catch unreachable; const fn_token = try self.eatToken(Token.Id.Keyword_fn); // TODO shouldn't need this cast diff --git a/src-self-hosted/tokenizer.zig b/src-self-hosted/tokenizer.zig index 5825869149e356e07d3f90c1240269ed66704f45..f6ba130b2566eddb6cba028d68fa52e1fde0660d 100644 --- a/src-self-hosted/tokenizer.zig +++ b/src-self-hosted/tokenizer.zig @@ -16,7 +16,6 @@ pub const Token = struct { KeywordId{.bytes="and", .id = Id.Keyword_and}, KeywordId{.bytes="asm", .id = Id.Keyword_asm}, KeywordId{.bytes="break", .id = Id.Keyword_break}, - KeywordId{.bytes="coldcc", .id = Id.Keyword_coldcc}, KeywordId{.bytes="comptime", .id = Id.Keyword_comptime}, KeywordId{.bytes="const", .id = Id.Keyword_const}, KeywordId{.bytes="continue", .id = Id.Keyword_continue}, @@ -97,7 +96,6 @@ pub const Token = struct { Keyword_and, Keyword_asm, Keyword_break, - Keyword_coldcc, Keyword_comptime, Keyword_const, Keyword_continue, diff --git a/src/all_types.hpp b/src/all_types.hpp index b4010976470ce978110ef5e5515c53d3a7529365..e6cf9bd7011eee03ba3c748734f17d4c3699bd7f 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -1108,6 +1108,7 @@ struct TypeTableEntry { bool zero_bits; bool is_copyable; + bool gen_h_loop_flag; union { TypeTableEntryPointer pointer; @@ -1204,6 +1205,9 @@ struct FnTableEntry { AstNode *set_alignstack_node; uint32_t alignstack_value; + AstNode *set_cold_node; + bool is_cold; + ZigList export_list; bool calls_errorable_function; }; @@ -1250,6 +1254,7 @@ enum BuiltinFnId { BuiltinFnIdMod, BuiltinFnIdTruncate, BuiltinFnIdIntType, + BuiltinFnIdSetCold, BuiltinFnIdSetDebugSafety, BuiltinFnIdSetFloatMode, BuiltinFnIdTypeName, @@ -1830,6 +1835,7 @@ enum IrInstructionId { IrInstructionIdTypeOf, IrInstructionIdToPtrType, IrInstructionIdPtrTypeChild, + IrInstructionIdSetCold, IrInstructionIdSetDebugSafety, IrInstructionIdSetFloatMode, IrInstructionIdArrayType, @@ -2202,6 +2208,12 @@ struct IrInstructionPtrTypeChild { IrInstruction *value; }; +struct IrInstructionSetCold { + IrInstruction base; + + IrInstruction *is_cold; +}; + struct IrInstructionSetDebugSafety { IrInstruction base; diff --git a/src/analyze.cpp b/src/analyze.cpp index ade9b4b1fe4d014f19885747924c9029f7bf1df4..1880b71fbbd9b00fb6ff311a2af7ffbbc6145ffb 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -1158,6 +1158,104 @@ static bool analyze_const_string(CodeGen *g, Scope *scope, AstNode *node, Buf ** return true; } +static bool type_allowed_in_packed_struct(TypeTableEntry *type_entry) { + switch (type_entry->id) { + case TypeTableEntryIdInvalid: + case TypeTableEntryIdVar: + zig_unreachable(); + case TypeTableEntryIdMetaType: + case TypeTableEntryIdUnreachable: + case TypeTableEntryIdNumLitFloat: + case TypeTableEntryIdNumLitInt: + case TypeTableEntryIdUndefLit: + case TypeTableEntryIdNullLit: + case TypeTableEntryIdErrorUnion: + case TypeTableEntryIdPureError: + case TypeTableEntryIdNamespace: + case TypeTableEntryIdBlock: + case TypeTableEntryIdBoundFn: + case TypeTableEntryIdArgTuple: + case TypeTableEntryIdOpaque: + return false; + case TypeTableEntryIdVoid: + case TypeTableEntryIdBool: + case TypeTableEntryIdInt: + case TypeTableEntryIdFloat: + case TypeTableEntryIdPointer: + case TypeTableEntryIdArray: + case TypeTableEntryIdFn: + return true; + case TypeTableEntryIdStruct: + return type_entry->data.structure.layout == ContainerLayoutPacked; + case TypeTableEntryIdUnion: + return type_entry->data.unionation.layout == ContainerLayoutPacked; + case TypeTableEntryIdMaybe: + { + TypeTableEntry *child_type = type_entry->data.maybe.child_type; + return child_type->id == TypeTableEntryIdPointer || child_type->id == TypeTableEntryIdFn; + } + case TypeTableEntryIdEnum: + return type_entry->data.enumeration.decl_node->data.container_decl.init_arg_expr != nullptr; + } + zig_unreachable(); +} + +static bool type_allowed_in_extern(CodeGen *g, TypeTableEntry *type_entry) { + switch (type_entry->id) { + case TypeTableEntryIdInvalid: + case TypeTableEntryIdVar: + zig_unreachable(); + case TypeTableEntryIdMetaType: + case TypeTableEntryIdNumLitFloat: + case TypeTableEntryIdNumLitInt: + case TypeTableEntryIdUndefLit: + case TypeTableEntryIdNullLit: + case TypeTableEntryIdErrorUnion: + case TypeTableEntryIdPureError: + case TypeTableEntryIdNamespace: + case TypeTableEntryIdBlock: + case TypeTableEntryIdBoundFn: + case TypeTableEntryIdArgTuple: + return false; + case TypeTableEntryIdOpaque: + case TypeTableEntryIdUnreachable: + case TypeTableEntryIdVoid: + case TypeTableEntryIdBool: + return true; + case TypeTableEntryIdInt: + switch (type_entry->data.integral.bit_count) { + case 8: + case 16: + case 32: + case 64: + case 128: + return true; + default: + return false; + } + case TypeTableEntryIdFloat: + return true; + case TypeTableEntryIdArray: + return type_allowed_in_extern(g, type_entry->data.array.child_type); + case TypeTableEntryIdFn: + return type_entry->data.fn.fn_type_id.cc == CallingConventionC; + case TypeTableEntryIdPointer: + return type_allowed_in_extern(g, type_entry->data.pointer.child_type); + case TypeTableEntryIdStruct: + return type_entry->data.structure.layout == ContainerLayoutExtern; + case TypeTableEntryIdMaybe: + { + TypeTableEntry *child_type = type_entry->data.maybe.child_type; + return child_type->id == TypeTableEntryIdPointer || child_type->id == TypeTableEntryIdFn; + } + case TypeTableEntryIdEnum: + return type_entry->data.enumeration.layout == ContainerLayoutExtern; + case TypeTableEntryIdUnion: + return type_entry->data.unionation.layout == ContainerLayoutExtern; + } + zig_unreachable(); +} + static TypeTableEntry *analyze_fn_type(CodeGen *g, AstNode *proto_node, Scope *child_scope) { assert(proto_node->type == NodeTypeFnProto); AstNodeFnProto *fn_proto = &proto_node->data.fn_proto; @@ -1208,6 +1306,14 @@ static TypeTableEntry *analyze_fn_type(CodeGen *g, AstNode *proto_node, Scope *c } } + if (fn_type_id.cc != CallingConventionUnspecified && !type_allowed_in_extern(g, type_entry)) { + add_node_error(g, param_node->data.param_decl.type, + buf_sprintf("parameter of type '%s' not allowed in function with calling convention '%s'", + buf_ptr(&type_entry->name), + calling_convention_name(fn_type_id.cc))); + return g->builtin_types.entry_invalid; + } + switch (type_entry->id) { case TypeTableEntryIdInvalid: return g->builtin_types.entry_invalid; @@ -1272,6 +1378,14 @@ static TypeTableEntry *analyze_fn_type(CodeGen *g, AstNode *proto_node, Scope *c fn_type_id.return_type = (fn_proto->return_type == nullptr) ? g->builtin_types.entry_void : analyze_type_expr(g, child_scope, fn_proto->return_type); + if (fn_type_id.cc != CallingConventionUnspecified && !type_allowed_in_extern(g, fn_type_id.return_type)) { + add_node_error(g, fn_proto->return_type, + buf_sprintf("return type '%s' not allowed in function with calling convention '%s'", + buf_ptr(&fn_type_id.return_type->name), + calling_convention_name(fn_type_id.cc))); + return g->builtin_types.entry_invalid; + } + switch (fn_type_id.return_type->id) { case TypeTableEntryIdInvalid: return g->builtin_types.entry_invalid; @@ -1424,46 +1538,6 @@ static void resolve_enum_type(CodeGen *g, TypeTableEntry *enum_type) { enum_type->di_type = tag_di_type; } -static bool type_allowed_in_packed_struct(TypeTableEntry *type_entry) { - switch (type_entry->id) { - case TypeTableEntryIdInvalid: - case TypeTableEntryIdVar: - zig_unreachable(); - case TypeTableEntryIdMetaType: - case TypeTableEntryIdUnreachable: - case TypeTableEntryIdNumLitFloat: - case TypeTableEntryIdNumLitInt: - case TypeTableEntryIdUndefLit: - case TypeTableEntryIdNullLit: - case TypeTableEntryIdErrorUnion: - case TypeTableEntryIdPureError: - case TypeTableEntryIdNamespace: - case TypeTableEntryIdBlock: - case TypeTableEntryIdBoundFn: - case TypeTableEntryIdArgTuple: - case TypeTableEntryIdOpaque: - return false; - case TypeTableEntryIdVoid: - case TypeTableEntryIdBool: - case TypeTableEntryIdInt: - case TypeTableEntryIdFloat: - case TypeTableEntryIdPointer: - case TypeTableEntryIdArray: - case TypeTableEntryIdUnion: - case TypeTableEntryIdFn: - return true; - case TypeTableEntryIdStruct: - return type_entry->data.structure.layout == ContainerLayoutPacked; - case TypeTableEntryIdMaybe: - { - TypeTableEntry *child_type = type_entry->data.maybe.child_type; - return child_type->id == TypeTableEntryIdPointer || child_type->id == TypeTableEntryIdFn; - } - case TypeTableEntryIdEnum: - return type_entry->data.enumeration.decl_node->data.container_decl.init_arg_expr != nullptr; - } - zig_unreachable(); -} TypeTableEntry *get_struct_type(CodeGen *g, const char *type_name, const char *field_names[], TypeTableEntry *field_types[], size_t field_count) diff --git a/src/codegen.cpp b/src/codegen.cpp index 15423d6315473d47d2cb107437188d40db7b585a..c79da04b058eb6c612be5dff2fbc1b063043f542 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -485,11 +485,14 @@ static LLVMValueRef fn_llvm_value(CodeGen *g, FnTableEntry *fn_table_entry) { addLLVMFnAttr(fn_table_entry->llvm_value, "naked"); } else { LLVMSetFunctionCallConv(fn_table_entry->llvm_value, get_llvm_cc(g, fn_type->data.fn.fn_type_id.cc)); - if (fn_type->data.fn.fn_type_id.cc == CallingConventionCold) { - ZigLLVMAddFunctionAttrCold(fn_table_entry->llvm_value); - } } + bool want_cold = fn_table_entry->is_cold || fn_type->data.fn.fn_type_id.cc == CallingConventionCold; + if (want_cold) { + ZigLLVMAddFunctionAttrCold(fn_table_entry->llvm_value); + } + + LLVMSetLinkage(fn_table_entry->llvm_value, to_llvm_linkage(linkage)); if (linkage == GlobalLinkageIdInternal) { @@ -3656,6 +3659,7 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable, case IrInstructionIdToPtrType: case IrInstructionIdPtrTypeChild: case IrInstructionIdFieldPtr: + case IrInstructionIdSetCold: case IrInstructionIdSetDebugSafety: case IrInstructionIdSetFloatMode: case IrInstructionIdArrayType: @@ -5233,6 +5237,7 @@ static void define_builtin_fns(CodeGen *g) { create_builtin_fn(g, BuiltinFnIdCompileErr, "compileError", 1); create_builtin_fn(g, BuiltinFnIdCompileLog, "compileLog", SIZE_MAX); create_builtin_fn(g, BuiltinFnIdIntType, "IntType", 2); // TODO rename to Int + create_builtin_fn(g, BuiltinFnIdSetCold, "setCold", 1); create_builtin_fn(g, BuiltinFnIdSetDebugSafety, "setDebugSafety", 2); create_builtin_fn(g, BuiltinFnIdSetFloatMode, "setFloatMode", 2); create_builtin_fn(g, BuiltinFnIdPanic, "panic", 1); @@ -5788,7 +5793,76 @@ static const char *c_int_type_names[] = { "unsigned long long", }; -static void get_c_type(CodeGen *g, TypeTableEntry *type_entry, Buf *out_buf) { +struct GenH { + ZigList types_to_declare; +}; + +static void prepend_c_type_to_decl_list(CodeGen *g, GenH *gen_h, TypeTableEntry *type_entry) { + if (type_entry->gen_h_loop_flag) + return; + type_entry->gen_h_loop_flag = true; + + switch (type_entry->id) { + case TypeTableEntryIdInvalid: + case TypeTableEntryIdVar: + case TypeTableEntryIdMetaType: + case TypeTableEntryIdNumLitFloat: + case TypeTableEntryIdNumLitInt: + case TypeTableEntryIdUndefLit: + case TypeTableEntryIdNullLit: + case TypeTableEntryIdNamespace: + case TypeTableEntryIdBlock: + case TypeTableEntryIdBoundFn: + case TypeTableEntryIdArgTuple: + case TypeTableEntryIdErrorUnion: + case TypeTableEntryIdPureError: + zig_unreachable(); + case TypeTableEntryIdVoid: + case TypeTableEntryIdUnreachable: + case TypeTableEntryIdBool: + case TypeTableEntryIdInt: + case TypeTableEntryIdFloat: + return; + case TypeTableEntryIdOpaque: + gen_h->types_to_declare.append(type_entry); + return; + case TypeTableEntryIdStruct: + for (uint32_t i = 0; i < type_entry->data.structure.src_field_count; i += 1) { + TypeStructField *field = &type_entry->data.structure.fields[i]; + prepend_c_type_to_decl_list(g, gen_h, field->type_entry); + } + gen_h->types_to_declare.append(type_entry); + return; + case TypeTableEntryIdUnion: + for (uint32_t i = 0; i < type_entry->data.unionation.src_field_count; i += 1) { + TypeUnionField *field = &type_entry->data.unionation.fields[i]; + prepend_c_type_to_decl_list(g, gen_h, field->type_entry); + } + gen_h->types_to_declare.append(type_entry); + return; + case TypeTableEntryIdEnum: + prepend_c_type_to_decl_list(g, gen_h, type_entry->data.enumeration.tag_int_type); + gen_h->types_to_declare.append(type_entry); + return; + case TypeTableEntryIdPointer: + prepend_c_type_to_decl_list(g, gen_h, type_entry->data.pointer.child_type); + return; + case TypeTableEntryIdArray: + prepend_c_type_to_decl_list(g, gen_h, type_entry->data.array.child_type); + return; + case TypeTableEntryIdMaybe: + prepend_c_type_to_decl_list(g, gen_h, type_entry->data.maybe.child_type); + return; + case TypeTableEntryIdFn: + for (size_t i = 0; i < type_entry->data.fn.fn_type_id.param_count; i += 1) { + prepend_c_type_to_decl_list(g, gen_h, type_entry->data.fn.fn_type_id.param_info[i].type); + } + prepend_c_type_to_decl_list(g, gen_h, type_entry->data.fn.fn_type_id.return_type); + return; + } +} + +static void get_c_type(CodeGen *g, GenH *gen_h, TypeTableEntry *type_entry, Buf *out_buf) { assert(type_entry); for (size_t i = 0; i < array_length(c_int_type_names); i += 1) { @@ -5816,6 +5890,8 @@ static void get_c_type(CodeGen *g, TypeTableEntry *type_entry, Buf *out_buf) { return; } + prepend_c_type_to_decl_list(g, gen_h, type_entry); + switch (type_entry->id) { case TypeTableEntryIdVoid: buf_init_from_str(out_buf, "void"); @@ -5856,7 +5932,7 @@ static void get_c_type(CodeGen *g, TypeTableEntry *type_entry, Buf *out_buf) { { Buf child_buf = BUF_INIT; TypeTableEntry *child_type = type_entry->data.pointer.child_type; - get_c_type(g, child_type, &child_buf); + get_c_type(g, gen_h, child_type, &child_buf); const char *const_str = type_entry->data.pointer.is_const ? "const " : ""; buf_resize(out_buf, 0); @@ -5872,23 +5948,37 @@ static void get_c_type(CodeGen *g, TypeTableEntry *type_entry, Buf *out_buf) { } else if (child_type->id == TypeTableEntryIdPointer || child_type->id == TypeTableEntryIdFn) { - return get_c_type(g, child_type, out_buf); + return get_c_type(g, gen_h, child_type, out_buf); } else { zig_unreachable(); } } case TypeTableEntryIdStruct: + { + buf_init_from_str(out_buf, "struct "); + buf_append_buf(out_buf, &type_entry->name); + return; + } + case TypeTableEntryIdUnion: + { + buf_init_from_str(out_buf, "union "); + buf_append_buf(out_buf, &type_entry->name); + return; + } + case TypeTableEntryIdEnum: + { + buf_init_from_str(out_buf, "enum "); + buf_append_buf(out_buf, &type_entry->name); + return; + } case TypeTableEntryIdOpaque: { - // TODO add to table of structs we need to declare buf_init_from_buf(out_buf, &type_entry->name); return; } case TypeTableEntryIdArray: case TypeTableEntryIdErrorUnion: case TypeTableEntryIdPureError: - case TypeTableEntryIdEnum: - case TypeTableEntryIdUnion: case TypeTableEntryIdFn: zig_panic("TODO implement get_c_type for more types"); case TypeTableEntryIdInvalid: @@ -5942,6 +6032,9 @@ static void gen_h_file(CodeGen *g) { if (!g->want_h_file) return; + GenH gen_h_data = {0}; + GenH *gen_h = &gen_h_data; + codegen_add_time_event(g, "Generate .h"); assert(!g->is_test_build); @@ -5971,7 +6064,7 @@ static void gen_h_file(CodeGen *g) { FnTypeId *fn_type_id = &fn_table_entry->type_entry->data.fn.fn_type_id; Buf return_type_c = BUF_INIT; - get_c_type(g, fn_type_id->return_type, &return_type_c); + get_c_type(g, gen_h, fn_type_id->return_type, &return_type_c); buf_appendf(&h_buf, "%s %s %s(", buf_ptr(export_macro), @@ -5987,7 +6080,7 @@ static void gen_h_file(CodeGen *g) { const char *comma_str = (param_i == 0) ? "" : ", "; const char *restrict_str = param_info->is_noalias ? "restrict" : ""; - get_c_type(g, param_info->type, ¶m_type_c); + get_c_type(g, gen_h, param_info->type, ¶m_type_c); buf_appendf(&h_buf, "%s%s%s %s", comma_str, buf_ptr(¶m_type_c), restrict_str, buf_ptr(param_name)); } @@ -6027,6 +6120,77 @@ static void gen_h_file(CodeGen *g) { fprintf(out_h, "#endif\n"); fprintf(out_h, "\n"); + for (size_t type_i = 0; type_i < gen_h->types_to_declare.length; type_i += 1) { + TypeTableEntry *type_entry = gen_h->types_to_declare.at(type_i); + switch (type_entry->id) { + case TypeTableEntryIdInvalid: + case TypeTableEntryIdVar: + case TypeTableEntryIdMetaType: + case TypeTableEntryIdVoid: + case TypeTableEntryIdBool: + case TypeTableEntryIdUnreachable: + case TypeTableEntryIdInt: + case TypeTableEntryIdFloat: + case TypeTableEntryIdPointer: + case TypeTableEntryIdNumLitFloat: + case TypeTableEntryIdNumLitInt: + case TypeTableEntryIdArray: + case TypeTableEntryIdUndefLit: + case TypeTableEntryIdNullLit: + case TypeTableEntryIdErrorUnion: + case TypeTableEntryIdPureError: + case TypeTableEntryIdNamespace: + case TypeTableEntryIdBlock: + case TypeTableEntryIdBoundFn: + case TypeTableEntryIdArgTuple: + case TypeTableEntryIdMaybe: + case TypeTableEntryIdFn: + zig_unreachable(); + case TypeTableEntryIdEnum: + assert(type_entry->data.enumeration.layout == ContainerLayoutExtern); + fprintf(out_h, "enum %s {\n", buf_ptr(&type_entry->name)); + for (uint32_t field_i = 0; field_i < type_entry->data.enumeration.src_field_count; field_i += 1) { + TypeEnumField *enum_field = &type_entry->data.enumeration.fields[field_i]; + Buf *value_buf = buf_alloc(); + bigint_append_buf(value_buf, &enum_field->value, 10); + fprintf(out_h, " %s = %s", buf_ptr(enum_field->name), buf_ptr(value_buf)); + if (field_i != type_entry->data.enumeration.src_field_count - 1) { + fprintf(out_h, ","); + } + fprintf(out_h, "\n"); + } + fprintf(out_h, "};\n\n"); + break; + case TypeTableEntryIdStruct: + assert(type_entry->data.structure.layout == ContainerLayoutExtern); + fprintf(out_h, "struct %s {\n", buf_ptr(&type_entry->name)); + for (uint32_t field_i = 0; field_i < type_entry->data.structure.src_field_count; field_i += 1) { + TypeStructField *struct_field = &type_entry->data.structure.fields[field_i]; + + Buf *type_name_buf = buf_alloc(); + get_c_type(g, gen_h, struct_field->type_entry, type_name_buf); + fprintf(out_h, " %s %s;\n", buf_ptr(type_name_buf), buf_ptr(struct_field->name)); + } + fprintf(out_h, "};\n\n"); + break; + case TypeTableEntryIdUnion: + assert(type_entry->data.unionation.layout == ContainerLayoutExtern); + fprintf(out_h, "union %s {\n", buf_ptr(&type_entry->name)); + for (uint32_t field_i = 0; field_i < type_entry->data.unionation.src_field_count; field_i += 1) { + TypeUnionField *union_field = &type_entry->data.unionation.fields[field_i]; + + Buf *type_name_buf = buf_alloc(); + get_c_type(g, gen_h, union_field->type_entry, type_name_buf); + fprintf(out_h, " %s %s;\n", buf_ptr(type_name_buf), buf_ptr(union_field->name)); + } + fprintf(out_h, "};\n\n"); + break; + case TypeTableEntryIdOpaque: + fprintf(out_h, "struct %s;\n\n", buf_ptr(&type_entry->name)); + break; + } + } + fprintf(out_h, "%s", buf_ptr(&h_buf)); fprintf(out_h, "\n#endif\n"); diff --git a/src/ir.cpp b/src/ir.cpp index 3729f50755e892e9aa3fe9c1974308a37c191e0c..66d579239a2b1e5da3aea3fb1b636af3db6b0b91 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -272,6 +272,10 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionPtrTypeChild *) return IrInstructionIdPtrTypeChild; } +static constexpr IrInstructionId ir_instruction_id(IrInstructionSetCold *) { + return IrInstructionIdSetCold; +} + static constexpr IrInstructionId ir_instruction_id(IrInstructionSetDebugSafety *) { return IrInstructionIdSetDebugSafety; } @@ -1262,6 +1266,15 @@ static IrInstruction *ir_build_ptr_type_child(IrBuilder *irb, Scope *scope, AstN return &instruction->base; } +static IrInstruction *ir_build_set_cold(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *is_cold) { + IrInstructionSetCold *instruction = ir_build_instruction(irb, scope, source_node); + instruction->is_cold = is_cold; + + ir_ref_instruction(is_cold, irb->current_basic_block); + + return &instruction->base; +} + static IrInstruction *ir_build_set_debug_safety(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *scope_value, IrInstruction *debug_safety_on) { @@ -3065,6 +3078,15 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo return arg; return ir_build_typeof(irb, scope, node, arg); } + case BuiltinFnIdSetCold: + { + 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; + + return ir_build_set_cold(irb, scope, node, arg0_value); + } case BuiltinFnIdSetDebugSafety: { AstNode *arg0_node = node->data.fn_call_expr.params.at(0); @@ -11555,6 +11577,36 @@ static TypeTableEntry *ir_analyze_instruction_ptr_type_child(IrAnalyze *ira, return ira->codegen->builtin_types.entry_type; } +static TypeTableEntry *ir_analyze_instruction_set_cold(IrAnalyze *ira, IrInstructionSetCold *instruction) { + if (ira->new_irb.exec->is_inline) { + // ignore setCold when running functions at compile time + ir_build_const_from(ira, &instruction->base); + return ira->codegen->builtin_types.entry_void; + } + + IrInstruction *is_cold_value = instruction->is_cold->other; + bool want_cold; + if (!ir_resolve_bool(ira, is_cold_value, &want_cold)) + return ira->codegen->builtin_types.entry_invalid; + + FnTableEntry *fn_entry = scope_fn_entry(instruction->base.scope); + if (fn_entry == nullptr) { + ir_add_error(ira, &instruction->base, buf_sprintf("@setCold outside function")); + return ira->codegen->builtin_types.entry_invalid; + } + + if (fn_entry->set_cold_node != nullptr) { + ErrorMsg *msg = ir_add_error(ira, &instruction->base, buf_sprintf("cold set twice in same function")); + add_error_note(ira->codegen, msg, fn_entry->set_cold_node, buf_sprintf("first set here")); + return ira->codegen->builtin_types.entry_invalid; + } + + fn_entry->set_cold_node = instruction->base.source_node; + fn_entry->is_cold = want_cold; + + ir_build_const_from(ira, &instruction->base); + return ira->codegen->builtin_types.entry_void; +} static TypeTableEntry *ir_analyze_instruction_set_debug_safety(IrAnalyze *ira, IrInstructionSetDebugSafety *set_debug_safety_instruction) { @@ -15239,6 +15291,8 @@ static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructi return ir_analyze_instruction_to_ptr_type(ira, (IrInstructionToPtrType *)instruction); case IrInstructionIdPtrTypeChild: return ir_analyze_instruction_ptr_type_child(ira, (IrInstructionPtrTypeChild *)instruction); + case IrInstructionIdSetCold: + return ir_analyze_instruction_set_cold(ira, (IrInstructionSetCold *)instruction); case IrInstructionIdSetDebugSafety: return ir_analyze_instruction_set_debug_safety(ira, (IrInstructionSetDebugSafety *)instruction); case IrInstructionIdSetFloatMode: @@ -15475,6 +15529,7 @@ bool ir_has_side_effects(IrInstruction *instruction) { case IrInstructionIdCall: case IrInstructionIdReturn: case IrInstructionIdUnreachable: + case IrInstructionIdSetCold: case IrInstructionIdSetDebugSafety: case IrInstructionIdSetFloatMode: case IrInstructionIdImport: diff --git a/src/ir_print.cpp b/src/ir_print.cpp index 930d22f21ab83c9f2dcfaca58702e82070e4de72..4a5604da5a97720971df2ba8b4d9ade612071343 100644 --- a/src/ir_print.cpp +++ b/src/ir_print.cpp @@ -368,6 +368,12 @@ static void ir_print_union_field_ptr(IrPrint *irp, IrInstructionUnionFieldPtr *i fprintf(irp->f, ")"); } +static void ir_print_set_cold(IrPrint *irp, IrInstructionSetCold *instruction) { + fprintf(irp->f, "@setCold("); + ir_print_other_instruction(irp, instruction->is_cold); + fprintf(irp->f, ")"); +} + static void ir_print_set_debug_safety(IrPrint *irp, IrInstructionSetDebugSafety *instruction) { fprintf(irp->f, "@setDebugSafety("); ir_print_other_instruction(irp, instruction->scope_value); @@ -1081,6 +1087,9 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) { case IrInstructionIdUnionFieldPtr: ir_print_union_field_ptr(irp, (IrInstructionUnionFieldPtr *)instruction); break; + case IrInstructionIdSetCold: + ir_print_set_cold(irp, (IrInstructionSetCold *)instruction); + break; case IrInstructionIdSetDebugSafety: ir_print_set_debug_safety(irp, (IrInstructionSetDebugSafety *)instruction); break; diff --git a/src/parser.cpp b/src/parser.cpp index 511936fb30f6abc60efc146ee66847fd8f709a68..de62c8c61254e2516ed5c086e10cce5d1062af90 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -2250,7 +2250,7 @@ static AstNode *ast_parse_block(ParseContext *pc, size_t *token_index, bool mand } /* -FnProto = option("coldcc" | "nakedcc" | "stdcallcc" | "extern") "fn" option(Symbol) ParamDeclList option("align" "(" Expression ")") option("section" "(" Expression ")") option("->" TypeExpr) +FnProto = option("nakedcc" | "stdcallcc" | "extern") "fn" option(Symbol) ParamDeclList option("align" "(" Expression ")") option("section" "(" Expression ")") option("->" TypeExpr) */ static AstNode *ast_parse_fn_proto(ParseContext *pc, size_t *token_index, bool mandatory, VisibMod visib_mod) { Token *first_token = &pc->tokens->at(*token_index); @@ -2258,11 +2258,7 @@ static AstNode *ast_parse_fn_proto(ParseContext *pc, size_t *token_index, bool m CallingConvention cc; bool is_extern = false; - if (first_token->id == TokenIdKeywordColdCC) { - *token_index += 1; - fn_token = ast_eat_token(pc, token_index, TokenIdKeywordFn); - cc = CallingConventionCold; - } else if (first_token->id == TokenIdKeywordNakedCC) { + if (first_token->id == TokenIdKeywordNakedCC) { *token_index += 1; fn_token = ast_eat_token(pc, token_index, TokenIdKeywordFn); cc = CallingConventionNaked; diff --git a/src/tokenizer.cpp b/src/tokenizer.cpp index f12f5396267118a35748bab17f52a2b338b6eb55..57eac899dae6e0d7a655542222315e5d844025d9 100644 --- a/src/tokenizer.cpp +++ b/src/tokenizer.cpp @@ -112,7 +112,6 @@ static const struct ZigKeyword zig_keywords[] = { {"asm", TokenIdKeywordAsm}, {"break", TokenIdKeywordBreak}, {"catch", TokenIdKeywordCatch}, - {"coldcc", TokenIdKeywordColdCC}, {"comptime", TokenIdKeywordCompTime}, {"const", TokenIdKeywordConst}, {"continue", TokenIdKeywordContinue}, @@ -1509,7 +1508,6 @@ const char * token_name(TokenId id) { case TokenIdKeywordAsm: return "asm"; case TokenIdKeywordBreak: return "break"; case TokenIdKeywordCatch: return "catch"; - case TokenIdKeywordColdCC: return "coldcc"; case TokenIdKeywordCompTime: return "comptime"; case TokenIdKeywordConst: return "const"; case TokenIdKeywordContinue: return "continue"; diff --git a/src/tokenizer.hpp b/src/tokenizer.hpp index 43d13090261b323350cecc22b81825655ebaec6f..6c9704224a59bc11b8a5487cfc2a2523ad3a1e99 100644 --- a/src/tokenizer.hpp +++ b/src/tokenizer.hpp @@ -51,7 +51,6 @@ enum TokenId { TokenIdKeywordAsm, TokenIdKeywordBreak, TokenIdKeywordCatch, - TokenIdKeywordColdCC, TokenIdKeywordCompTime, TokenIdKeywordConst, TokenIdKeywordContinue, diff --git a/std/os/index.zig b/std/os/index.zig index fd8eb84ab41b4cff86038187cdbe238037910a98..dbf9ddcb4569f77d476cd601bf165028da0d287d 100644 --- a/std/os/index.zig +++ b/std/os/index.zig @@ -127,7 +127,8 @@ test "os.getRandomBytes" { /// Raises a signal in the current kernel thread, ending its execution. /// If linking against libc, this calls the abort() libc function. Otherwise /// it uses the zig standard library implementation. -pub coldcc fn abort() -> noreturn { +pub fn abort() -> noreturn { + @setCold(true); if (builtin.link_libc) { c.abort(); } @@ -148,7 +149,8 @@ pub coldcc fn abort() -> noreturn { } /// Exits the program cleanly with the specified status code. -pub coldcc fn exit(status: u8) -> noreturn { +pub fn exit(status: u8) -> noreturn { + @setCold(true); if (builtin.link_libc) { c.exit(status); } diff --git a/std/special/builtin.zig b/std/special/builtin.zig index dd77ba9c75c805618a96fe018167aef9f7a70bac..7d648ff95ac3d53ddf5442e49f86636219f0e0a4 100644 --- a/std/special/builtin.zig +++ b/std/special/builtin.zig @@ -5,8 +5,9 @@ const builtin = @import("builtin"); // Avoid dragging in the debug safety mechanisms into this .o file, // unless we're trying to test this file. -pub coldcc fn panic(msg: []const u8, error_return_trace: ?&builtin.StackTrace) -> noreturn { +pub fn panic(msg: []const u8, error_return_trace: ?&builtin.StackTrace) -> noreturn { if (builtin.is_test) { + @setCold(true); @import("std").debug.panic("{}", msg); } else { unreachable; diff --git a/std/special/compiler_rt/index.zig b/std/special/compiler_rt/index.zig index 96f34e56ff64889e2f126f6d4bfb361b5abfb9ad..32659f1bdd3dfe45ae0ee4919fcf3b7394d47956 100644 --- a/std/special/compiler_rt/index.zig +++ b/std/special/compiler_rt/index.zig @@ -74,7 +74,8 @@ const __udivmoddi4 = @import("udivmoddi4.zig").__udivmoddi4; // Avoid dragging in the debug safety mechanisms into this .o file, // unless we're trying to test this file. -pub coldcc fn panic(msg: []const u8, error_return_trace: ?&builtin.StackTrace) -> noreturn { +pub fn panic(msg: []const u8, error_return_trace: ?&builtin.StackTrace) -> noreturn { + @setCold(true); if (is_test) { @import("std").debug.panic("{}", msg); } else { diff --git a/std/special/panic.zig b/std/special/panic.zig index 1b22658c7ff276c66f6688d734839372493956c6..7aaaa78d0af23f75de06693ed9459b202df610d4 100644 --- a/std/special/panic.zig +++ b/std/special/panic.zig @@ -6,7 +6,8 @@ const builtin = @import("builtin"); const std = @import("std"); -pub coldcc fn panic(msg: []const u8, error_return_trace: ?&builtin.StackTrace) -> noreturn { +pub fn panic(msg: []const u8, error_return_trace: ?&builtin.StackTrace) -> noreturn { + @setCold(true); switch (builtin.os) { // TODO: fix panic in zen. builtin.Os.freestanding, builtin.Os.zen => { diff --git a/test/cases/misc.zig b/test/cases/misc.zig index 137fda9c745bb5558242100740135128e1b1bae6..ee88c1359d2a5712788f70ee268d6165cd242b13 100644 --- a/test/cases/misc.zig +++ b/test/cases/misc.zig @@ -608,3 +608,12 @@ test "function closes over local const" { const x = fnThatClosesOverLocalConst().g(); assert(x == 1); } + +test "cold function" { + thisIsAColdFn(); + comptime thisIsAColdFn(); +} + +fn thisIsAColdFn() { + @setCold(true); +} diff --git a/test/compile_errors.zig b/test/compile_errors.zig index d3c0c7cfeedc0ad204d2ba540d0a41c2bae01077..ed3ddb67e2a9ac71e73bd215d55d451a9c838d71 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -1,6 +1,29 @@ const tests = @import("tests.zig"); pub fn addCases(cases: &tests.CompileErrorContext) { + cases.add("function with non-extern enum parameter", + \\const Foo = enum { A, B, C }; + \\export fn entry(foo: Foo) { } + , ".tmp_source.zig:2:22: error: parameter of type 'Foo' not allowed in function with calling convention 'ccc'"); + + cases.add("function with non-extern struct parameter", + \\const Foo = struct { + \\ A: i32, + \\ B: f32, + \\ C: bool, + \\}; + \\export fn entry(foo: Foo) { } + , ".tmp_source.zig:6:22: error: parameter of type 'Foo' not allowed in function with calling convention 'ccc'"); + + cases.add("function with non-extern union parameter", + \\const Foo = union { + \\ A: i32, + \\ B: f32, + \\ C: bool, + \\}; + \\export fn entry(foo: Foo) { } + , ".tmp_source.zig:6:22: error: parameter of type 'Foo' not allowed in function with calling convention 'ccc'"); + cases.add("switch on enum with 1 field with no prongs", \\const Foo = enum { M }; \\ @@ -1590,7 +1613,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { , ".tmp_source.zig:3:28: error: expected struct type, found 'i32'"); cases.add("@fieldParentPtr - bad field name", - \\const Foo = struct { + \\const Foo = extern struct { \\ derp: i32, \\}; \\export fn foo(a: &i32) -> &Foo { @@ -1599,7 +1622,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) { , ".tmp_source.zig:5:33: error: struct 'Foo' has no field 'a'"); cases.add("@fieldParentPtr - field pointer is not pointer", - \\const Foo = struct { + \\const Foo = extern struct { \\ a: i32, \\}; \\export fn foo(a: i32) -> &Foo { diff --git a/test/gen_h.zig b/test/gen_h.zig new file mode 100644 index 0000000000000000000000000000000000000000..7ecafbe876a29222ab6fc6b52fc7150de12f4486 --- /dev/null +++ b/test/gen_h.zig @@ -0,0 +1,53 @@ +const tests = @import("tests.zig"); + +pub fn addCases(cases: &tests.GenHContext) { + cases.add("declare enum", + \\const Foo = extern enum { A, B, C }; + \\export fn entry(foo: Foo) { } + , + \\enum Foo { + \\ A = 0, + \\ B = 1, + \\ C = 2 + \\}; + \\ + \\TEST_EXPORT void entry(enum Foo foo); + \\ + ); + + cases.add("declare struct", + \\const Foo = extern struct { + \\ A: i32, + \\ B: f32, + \\ C: bool, + \\}; + \\export fn entry(foo: Foo) { } + , + \\struct Foo { + \\ int32_t A; + \\ float B; + \\ bool C; + \\}; + \\ + \\TEST_EXPORT void entry(struct Foo foo); + \\ + ); + + cases.add("declare union", + \\const Foo = extern union { + \\ A: i32, + \\ B: f32, + \\ C: bool, + \\}; + \\export fn entry(foo: Foo) { } + , + \\union Foo { + \\ int32_t A; + \\ float B; + \\ bool C; + \\}; + \\ + \\TEST_EXPORT void entry(union Foo foo); + \\ + ); +} diff --git a/test/tests.zig b/test/tests.zig index 1136e365a8a05ea5a5e34210bc0ff2baaed693ad..84b01be11eb797ce99c89f19e73144d19622157d 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -19,6 +19,7 @@ const compile_errors = @import("compile_errors.zig"); const assemble_and_link = @import("assemble_and_link.zig"); const debug_safety = @import("debug_safety.zig"); const translate_c = @import("translate_c.zig"); +const gen_h = @import("gen_h.zig"); const TestTarget = struct { os: builtin.Os, @@ -123,7 +124,7 @@ pub fn addTranslateCTests(b: &build.Builder, test_filter: ?[]const u8) -> &build const cases = b.allocator.create(TranslateCContext) catch unreachable; *cases = TranslateCContext { .b = b, - .step = b.step("test-translate-c", "Run the C header file parsing tests"), + .step = b.step("test-translate-c", "Run the C transation tests"), .test_index = 0, .test_filter = test_filter, }; @@ -133,6 +134,21 @@ pub fn addTranslateCTests(b: &build.Builder, test_filter: ?[]const u8) -> &build return cases.step; } +pub fn addGenHTests(b: &build.Builder, test_filter: ?[]const u8) -> &build.Step { + const cases = b.allocator.create(GenHContext) catch unreachable; + *cases = GenHContext { + .b = b, + .step = b.step("test-gen-h", "Run the C header file generation tests"), + .test_index = 0, + .test_filter = test_filter, + }; + + gen_h.addCases(cases); + + return cases.step; +} + + pub fn addPkgTests(b: &build.Builder, test_filter: ?[]const u8, root_src: []const u8, name:[] const u8, desc: []const u8, with_lldb: bool) -> &build.Step { @@ -977,3 +993,136 @@ pub const TranslateCContext = struct { } } }; + +pub const GenHContext = struct { + b: &build.Builder, + step: &build.Step, + test_index: usize, + test_filter: ?[]const u8, + + const TestCase = struct { + name: []const u8, + sources: ArrayList(SourceFile), + expected_lines: ArrayList([]const u8), + + const SourceFile = struct { + filename: []const u8, + source: []const u8, + }; + + pub fn addSourceFile(self: &TestCase, filename: []const u8, source: []const u8) { + self.sources.append(SourceFile { + .filename = filename, + .source = source, + }) catch unreachable; + } + + pub fn addExpectedLine(self: &TestCase, text: []const u8) { + self.expected_lines.append(text) catch unreachable; + } + }; + + const GenHCmpOutputStep = struct { + step: build.Step, + context: &GenHContext, + h_path: []const u8, + name: []const u8, + test_index: usize, + case: &const TestCase, + + pub fn create(context: &GenHContext, h_path: []const u8, name: []const u8, case: &const TestCase) -> &GenHCmpOutputStep { + const allocator = context.b.allocator; + const ptr = allocator.create(GenHCmpOutputStep) catch unreachable; + *ptr = GenHCmpOutputStep { + .step = build.Step.init("ParseCCmpOutput", allocator, make), + .context = context, + .h_path = h_path, + .name = name, + .test_index = context.test_index, + .case = case, + }; + context.test_index += 1; + return ptr; + } + + fn make(step: &build.Step) -> %void { + const self = @fieldParentPtr(GenHCmpOutputStep, "step", step); + const b = self.context.b; + + warn("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name); + + const full_h_path = b.pathFromRoot(self.h_path); + const actual_h = try io.readFileAlloc(full_h_path, b.allocator); + + for (self.case.expected_lines.toSliceConst()) |expected_line| { + if (mem.indexOf(u8, actual_h, expected_line) == null) { + warn( + \\ + \\========= Expected this output: ================ + \\{} + \\================================================ + \\{} + \\ + , expected_line, actual_h); + return error.TestFailed; + } + } + warn("OK\n"); + } + }; + + fn printInvocation(args: []const []const u8) { + for (args) |arg| { + warn("{} ", arg); + } + warn("\n"); + } + + pub fn create(self: &GenHContext, filename: []const u8, name: []const u8, + source: []const u8, expected_lines: ...) -> &TestCase + { + const tc = self.b.allocator.create(TestCase) catch unreachable; + *tc = TestCase { + .name = name, + .sources = ArrayList(TestCase.SourceFile).init(self.b.allocator), + .expected_lines = ArrayList([]const u8).init(self.b.allocator), + }; + tc.addSourceFile(filename, source); + comptime var arg_i = 0; + inline while (arg_i < expected_lines.len) : (arg_i += 1) { + tc.addExpectedLine(expected_lines[arg_i]); + } + return tc; + } + + pub fn add(self: &GenHContext, name: []const u8, source: []const u8, expected_lines: ...) { + const tc = self.create("test.zig", name, source, expected_lines); + self.addCase(tc); + } + + pub fn addCase(self: &GenHContext, case: &const TestCase) { + const b = self.b; + const root_src = os.path.join(b.allocator, b.cache_root, case.sources.items[0].filename) catch unreachable; + + const mode = builtin.Mode.Debug; + const annotated_case_name = fmt.allocPrint(self.b.allocator, "gen-h {} ({})", case.name, @tagName(mode)) catch unreachable; + if (self.test_filter) |filter| { + if (mem.indexOf(u8, annotated_case_name, filter) == null) + return; + } + + const obj = b.addObject("test", root_src); + obj.setBuildMode(mode); + + for (case.sources.toSliceConst()) |src_file| { + const expanded_src_path = os.path.join(b.allocator, b.cache_root, src_file.filename) catch unreachable; + const write_src = b.addWriteFile(expanded_src_path, src_file.source); + obj.step.dependOn(&write_src.step); + } + + const cmp_h = GenHCmpOutputStep.create(self, obj.getOutputHPath(), annotated_case_name, case); + cmp_h.step.dependOn(&obj.step); + + self.step.dependOn(&cmp_h.step); + } +}; -- 2.54.0