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 189e0e156f..d48c58bbff 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 76d952420c..9ec827ff3e 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 db7d760e98..24cd77accd 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 88b6dd9bb0..5e1975339a 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 5825869149..f6ba130b25 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 b401097647..e6cf9bd701 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 ade9b4b1fe..1880b71fbb 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 15423d6315..c79da04b05 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 3729f50755..66d579239a 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 930d22f21a..4a5604da5a 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 511936fb30..de62c8c612 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 f12f539626..57eac899da 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 43d1309026..6c9704224a 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 fd8eb84ab4..dbf9ddcb45 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 dd77ba9c75..7d648ff95a 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 96f34e56ff..32659f1bdd 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 1b22658c7f..7aaaa78d0a 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 137fda9c74..ee88c1359d 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 d3c0c7cfee..ed3ddb67e2 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 0000000000..7ecafbe876
--- /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 1136e365a8..84b01be11e 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);
+ }
+};