Skip to content

Commit 10e9d47

Browse files
committed
stage2 translate-c: implement functions with no prototype
stage1 translate-c actually has this wrong. When exporting a function, it's ok to use empty parameters. But for prototypes, "no prototype" means that it has to be emitted as a function that accepts anything, e.g. extern fn foo(...) void; See #1964
1 parent 2ef2f9d commit 10e9d47

File tree

3 files changed

+115
-51
lines changed

3 files changed

+115
-51
lines changed

src-self-hosted/translate_c.zig

Lines changed: 74 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -254,11 +254,20 @@ fn visitFnDecl(c: *Context, fn_decl: *const ZigClangFunctionDecl) Error!void {
254254
const fn_qt = ZigClangFunctionDecl_getType(fn_decl);
255255
const fn_type = ZigClangQualType_getTypePtr(fn_qt);
256256
var scope = &c.global_scope.base;
257+
const has_body = ZigClangFunctionDecl_hasBody(fn_decl);
258+
const storage_class = ZigClangFunctionDecl_getStorageClass(fn_decl);
257259
const decl_ctx = FnDeclContext{
258260
.fn_name = fn_name,
259-
.has_body = ZigClangFunctionDecl_hasBody(fn_decl),
260-
.storage_class = ZigClangFunctionDecl_getStorageClass(fn_decl),
261+
.has_body = has_body,
262+
.storage_class = storage_class,
261263
.scope = &scope,
264+
.is_export = switch (storage_class) {
265+
.None => has_body,
266+
.Extern, .Static => false,
267+
.PrivateExtern => return failDecl(c, fn_decl_loc, fn_name, "unsupported storage class: private extern"),
268+
.Auto => unreachable, // Not legal on functions
269+
.Register => unreachable, // Not legal on functions
270+
},
262271
};
263272
const proto_node = switch (ZigClangType_getTypeClass(fn_type)) {
264273
.FunctionProto => blk: {
@@ -270,7 +279,15 @@ fn visitFnDecl(c: *Context, fn_decl: *const ZigClangFunctionDecl) Error!void {
270279
error.OutOfMemory => return error.OutOfMemory,
271280
};
272281
},
273-
.FunctionNoProto => return failDecl(c, fn_decl_loc, fn_name, "TODO support functions with no prototype"),
282+
.FunctionNoProto => blk: {
283+
const fn_no_proto_type = @ptrCast(*const ZigClangFunctionType, fn_type);
284+
break :blk transFnNoProto(rp, fn_no_proto_type, fn_decl_loc, decl_ctx) catch |err| switch (err) {
285+
error.UnsupportedType => {
286+
return failDecl(c, fn_decl_loc, fn_name, "unable to resolve prototype of function");
287+
},
288+
error.OutOfMemory => return error.OutOfMemory,
289+
};
290+
},
274291
else => unreachable,
275292
};
276293

@@ -432,61 +449,67 @@ const FnDeclContext = struct {
432449
has_body: bool,
433450
storage_class: ZigClangStorageClass,
434451
scope: **Scope,
452+
is_export: bool,
435453
};
436454

455+
fn transCC(
456+
rp: RestorePoint,
457+
fn_ty: *const ZigClangFunctionType,
458+
source_loc: ZigClangSourceLocation,
459+
) !CallingConvention {
460+
const clang_cc = ZigClangFunctionType_getCallConv(fn_ty);
461+
switch (clang_cc) {
462+
.C => return CallingConvention.C,
463+
.X86StdCall => return CallingConvention.Stdcall,
464+
else => return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported calling convention: {}", @tagName(clang_cc)),
465+
}
466+
}
467+
437468
fn transFnProto(
438469
rp: RestorePoint,
439470
fn_proto_ty: *const ZigClangFunctionProtoType,
440471
source_loc: ZigClangSourceLocation,
441472
fn_decl_context: ?FnDeclContext,
442473
) !*ast.Node.FnProto {
443474
const fn_ty = @ptrCast(*const ZigClangFunctionType, fn_proto_ty);
444-
const cc = switch (ZigClangFunctionType_getCallConv(fn_ty)) {
445-
.C => CallingConvention.C,
446-
.X86StdCall => CallingConvention.Stdcall,
447-
.X86FastCall => return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported calling convention: x86 fastcall"),
448-
.X86ThisCall => return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported calling convention: x86 thiscall"),
449-
.X86VectorCall => return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported calling convention: x86 vectorcall"),
450-
.X86Pascal => return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported calling convention: x86 pascal"),
451-
.Win64 => return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported calling convention: win64"),
452-
.X86_64SysV => return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported calling convention: x86 64sysv"),
453-
.X86RegCall => return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported calling convention: x86 reg"),
454-
.AAPCS => return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported calling convention: aapcs"),
455-
.AAPCS_VFP => return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported calling convention: aapcs-vfp"),
456-
.IntelOclBicc => return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported calling convention: intel_ocl_bicc"),
457-
.SpirFunction => return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported calling convention: SPIR function"),
458-
.OpenCLKernel => return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported calling convention: OpenCLKernel"),
459-
.Swift => return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported calling convention: Swift"),
460-
.PreserveMost => return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported calling convention: PreserveMost"),
461-
.PreserveAll => return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported calling convention: PreserveAll"),
462-
.AArch64VectorCall => return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported calling convention: AArch64VectorCall"),
463-
};
464-
475+
const cc = try transCC(rp, fn_ty, source_loc);
465476
const is_var_args = ZigClangFunctionProtoType_isVariadic(fn_proto_ty);
466477
const param_count: usize = ZigClangFunctionProtoType_getNumParams(fn_proto_ty);
467478
var i: usize = 0;
468479
while (i < param_count) : (i += 1) {
469480
return revertAndWarn(rp, error.UnsupportedType, source_loc, "TODO: implement parameters for FunctionProto in transType");
470481
}
482+
483+
return finishTransFnProto(rp, fn_ty, source_loc, fn_decl_context, is_var_args, cc);
484+
}
485+
486+
fn transFnNoProto(
487+
rp: RestorePoint,
488+
fn_ty: *const ZigClangFunctionType,
489+
source_loc: ZigClangSourceLocation,
490+
fn_decl_context: ?FnDeclContext,
491+
) !*ast.Node.FnProto {
492+
const cc = try transCC(rp, fn_ty, source_loc);
493+
const is_var_args = if (fn_decl_context) |ctx| !ctx.is_export else true;
494+
return finishTransFnProto(rp, fn_ty, source_loc, fn_decl_context, is_var_args, cc);
495+
}
496+
497+
fn finishTransFnProto(
498+
rp: RestorePoint,
499+
fn_ty: *const ZigClangFunctionType,
500+
source_loc: ZigClangSourceLocation,
501+
fn_decl_context: ?FnDeclContext,
502+
is_var_args: bool,
503+
cc: CallingConvention,
504+
) !*ast.Node.FnProto {
505+
const is_export = if (fn_decl_context) |ctx| ctx.is_export else false;
506+
471507
// TODO check for always_inline attribute
472508
// TODO check for align attribute
473509

474510
// pub extern fn name(...) T
475511
const pub_tok = try appendToken(rp.c, .Keyword_pub, "pub");
476512
const cc_tok = if (cc == .Stdcall) try appendToken(rp.c, .Keyword_stdcallcc, "stdcallcc") else null;
477-
const is_export = exp: {
478-
const decl_ctx = fn_decl_context orelse break :exp false;
479-
break :exp switch (decl_ctx.storage_class) {
480-
.None => switch (rp.c.mode) {
481-
.import => false,
482-
.translate => decl_ctx.has_body,
483-
},
484-
.Extern, .Static => false,
485-
.PrivateExtern => return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported storage class: private extern"),
486-
.Auto => unreachable, // Not legal on functions
487-
.Register => unreachable, // Not legal on functions
488-
};
489-
};
490513
const extern_export_inline_tok = if (is_export)
491514
try appendToken(rp.c, .Keyword_export, "export")
492515
else if (cc == .C)
@@ -527,7 +550,7 @@ fn transFnProto(
527550
.name_token = name_tok,
528551
.params = ast.Node.FnProto.ParamList.init(rp.c.a()),
529552
.return_type = ast.Node.FnProto.ReturnType{ .Explicit = return_type_node },
530-
.var_args_token = var_args_tok,
553+
.var_args_token = null, // TODO this field is broken in the AST data model
531554
.extern_export_inline_token = extern_export_inline_tok,
532555
.cc_token = cc_tok,
533556
.async_attr = null,
@@ -536,6 +559,19 @@ fn transFnProto(
536559
.align_expr = null,
537560
.section_expr = null,
538561
};
562+
if (is_var_args) {
563+
const var_arg_node = try rp.c.a().create(ast.Node.ParamDecl);
564+
var_arg_node.* = ast.Node.ParamDecl{
565+
.base = ast.Node{ .id = ast.Node.Id.ParamDecl },
566+
.doc_comments = null,
567+
.comptime_token = null,
568+
.noalias_token = null,
569+
.name_token = null,
570+
.type_node = undefined,
571+
.var_args_token = var_args_tok,
572+
};
573+
try fn_proto.params.push(&var_arg_node.base);
574+
}
539575
return fn_proto;
540576
}
541577

test/tests.zig

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1076,6 +1076,14 @@ pub const TranslateCContext = struct {
10761076
}
10771077

10781078
pub fn add_both(self: *TranslateCContext, name: []const u8, source: []const u8, expected_lines: ...) void {
1079+
for ([]bool{ false, true }) |stage2| {
1080+
const tc = self.create(false, "source.h", name, source, expected_lines);
1081+
tc.stage2 = stage2;
1082+
self.addCase(tc);
1083+
}
1084+
}
1085+
1086+
pub fn addC_both(self: *TranslateCContext, name: []const u8, source: []const u8, expected_lines: ...) void {
10791087
for ([]bool{ false, true }) |stage2| {
10801088
const tc = self.create(false, "source.c", name, source, expected_lines);
10811089
tc.stage2 = stage2;
@@ -1084,7 +1092,7 @@ pub const TranslateCContext = struct {
10841092
}
10851093

10861094
pub fn add_2(self: *TranslateCContext, name: []const u8, source: []const u8, expected_lines: ...) void {
1087-
const tc = self.create(false, "source.c", name, source, expected_lines);
1095+
const tc = self.create(false, "source.h", name, source, expected_lines);
10881096
tc.stage2 = true;
10891097
self.addCase(tc);
10901098
}

test/translate_c.zig

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
const tests = @import("tests.zig");
22
const builtin = @import("builtin");
33

4+
// add_both - test for stage1 and stage2, in #include mode
5+
// add - test stage1 only, in #include mode
6+
// add_2 - test stage2 only, in #include mode
7+
// addC_both - test for stage1 and stage2, in -c mode
8+
// addC - test stage1 only, in -c mode
9+
// addC_2 - test stage2 only, in -c mode
10+
411
pub fn addCases(cases: *tests.TranslateCContext) void {
512
/////////////// Cases that pass for both stage1/stage2 ////////////////
613
cases.add_both("simple function prototypes",
@@ -11,25 +18,29 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
1118
\\pub extern fn bar() c_int;
1219
);
1320

14-
cases.add_both("simple function definition",
15-
\\void foo(void) {};
21+
/////////////// Cases that pass for only stage2 ////////////////
22+
cases.add_2("Parameterless function prototypes",
23+
\\void a() {}
24+
\\void b(void) {}
25+
\\void c();
26+
\\void d(void);
1627
,
17-
\\pub export fn foo() void {}
28+
\\pub export fn a() void {}
29+
\\pub export fn b() void {}
30+
\\pub extern fn c(...) void;
31+
\\pub extern fn d() void;
1832
);
1933

20-
/////////////// Cases that pass for only stage2 ////////////////
21-
// (none)
22-
23-
/////////////// Cases that pass for only stage1 ////////////////
24-
25-
cases.addC("Parameterless function prototypes",
26-
\\void foo() {}
27-
\\void bar(void) {}
34+
cases.add_2("simple function definition",
35+
\\void foo(void) {}
36+
\\static void bar(void) {}
2837
,
2938
\\pub export fn foo() void {}
30-
\\pub export fn bar() void {}
39+
\\pub extern fn bar() void {}
3140
);
3241

42+
/////////////// Cases for only stage1 which are TODO items for stage2 ////////////////
43+
3344
cases.add("macro with left shift",
3445
\\#define REDISMODULE_READ (1<<0)
3546
,
@@ -1681,4 +1692,13 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
16811692
\\ }
16821693
\\}
16831694
);
1695+
1696+
/////////////// Cases for only stage1 because stage2 behavior is better ////////////////
1697+
cases.addC("Parameterless function prototypes",
1698+
\\void foo() {}
1699+
\\void bar(void) {}
1700+
,
1701+
\\pub export fn foo() void {}
1702+
\\pub export fn bar() void {}
1703+
);
16841704
}

0 commit comments

Comments
 (0)