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
This commit is contained in:
Andrew Kelley 2019-05-11 12:05:33 -04:00
parent 2ef2f9d71f
commit 10e9d47b49
No known key found for this signature in database
GPG Key ID: 7C5F548F728501A9
3 changed files with 116 additions and 52 deletions

View File

@ -254,11 +254,20 @@ fn visitFnDecl(c: *Context, fn_decl: *const ZigClangFunctionDecl) Error!void {
const fn_qt = ZigClangFunctionDecl_getType(fn_decl);
const fn_type = ZigClangQualType_getTypePtr(fn_qt);
var scope = &c.global_scope.base;
const has_body = ZigClangFunctionDecl_hasBody(fn_decl);
const storage_class = ZigClangFunctionDecl_getStorageClass(fn_decl);
const decl_ctx = FnDeclContext{
.fn_name = fn_name,
.has_body = ZigClangFunctionDecl_hasBody(fn_decl),
.storage_class = ZigClangFunctionDecl_getStorageClass(fn_decl),
.has_body = has_body,
.storage_class = storage_class,
.scope = &scope,
.is_export = switch (storage_class) {
.None => has_body,
.Extern, .Static => false,
.PrivateExtern => return failDecl(c, fn_decl_loc, fn_name, "unsupported storage class: private extern"),
.Auto => unreachable, // Not legal on functions
.Register => unreachable, // Not legal on functions
},
};
const proto_node = switch (ZigClangType_getTypeClass(fn_type)) {
.FunctionProto => blk: {
@ -270,7 +279,15 @@ fn visitFnDecl(c: *Context, fn_decl: *const ZigClangFunctionDecl) Error!void {
error.OutOfMemory => return error.OutOfMemory,
};
},
.FunctionNoProto => return failDecl(c, fn_decl_loc, fn_name, "TODO support functions with no prototype"),
.FunctionNoProto => blk: {
const fn_no_proto_type = @ptrCast(*const ZigClangFunctionType, fn_type);
break :blk transFnNoProto(rp, fn_no_proto_type, fn_decl_loc, decl_ctx) catch |err| switch (err) {
error.UnsupportedType => {
return failDecl(c, fn_decl_loc, fn_name, "unable to resolve prototype of function");
},
error.OutOfMemory => return error.OutOfMemory,
};
},
else => unreachable,
};
@ -432,8 +449,22 @@ const FnDeclContext = struct {
has_body: bool,
storage_class: ZigClangStorageClass,
scope: **Scope,
is_export: bool,
};
fn transCC(
rp: RestorePoint,
fn_ty: *const ZigClangFunctionType,
source_loc: ZigClangSourceLocation,
) !CallingConvention {
const clang_cc = ZigClangFunctionType_getCallConv(fn_ty);
switch (clang_cc) {
.C => return CallingConvention.C,
.X86StdCall => return CallingConvention.Stdcall,
else => return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported calling convention: {}", @tagName(clang_cc)),
}
}
fn transFnProto(
rp: RestorePoint,
fn_proto_ty: *const ZigClangFunctionProtoType,
@ -441,52 +472,44 @@ fn transFnProto(
fn_decl_context: ?FnDeclContext,
) !*ast.Node.FnProto {
const fn_ty = @ptrCast(*const ZigClangFunctionType, fn_proto_ty);
const cc = switch (ZigClangFunctionType_getCallConv(fn_ty)) {
.C => CallingConvention.C,
.X86StdCall => CallingConvention.Stdcall,
.X86FastCall => return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported calling convention: x86 fastcall"),
.X86ThisCall => return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported calling convention: x86 thiscall"),
.X86VectorCall => return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported calling convention: x86 vectorcall"),
.X86Pascal => return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported calling convention: x86 pascal"),
.Win64 => return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported calling convention: win64"),
.X86_64SysV => return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported calling convention: x86 64sysv"),
.X86RegCall => return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported calling convention: x86 reg"),
.AAPCS => return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported calling convention: aapcs"),
.AAPCS_VFP => return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported calling convention: aapcs-vfp"),
.IntelOclBicc => return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported calling convention: intel_ocl_bicc"),
.SpirFunction => return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported calling convention: SPIR function"),
.OpenCLKernel => return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported calling convention: OpenCLKernel"),
.Swift => return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported calling convention: Swift"),
.PreserveMost => return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported calling convention: PreserveMost"),
.PreserveAll => return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported calling convention: PreserveAll"),
.AArch64VectorCall => return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported calling convention: AArch64VectorCall"),
};
const cc = try transCC(rp, fn_ty, source_loc);
const is_var_args = ZigClangFunctionProtoType_isVariadic(fn_proto_ty);
const param_count: usize = ZigClangFunctionProtoType_getNumParams(fn_proto_ty);
var i: usize = 0;
while (i < param_count) : (i += 1) {
return revertAndWarn(rp, error.UnsupportedType, source_loc, "TODO: implement parameters for FunctionProto in transType");
}
return finishTransFnProto(rp, fn_ty, source_loc, fn_decl_context, is_var_args, cc);
}
fn transFnNoProto(
rp: RestorePoint,
fn_ty: *const ZigClangFunctionType,
source_loc: ZigClangSourceLocation,
fn_decl_context: ?FnDeclContext,
) !*ast.Node.FnProto {
const cc = try transCC(rp, fn_ty, source_loc);
const is_var_args = if (fn_decl_context) |ctx| !ctx.is_export else true;
return finishTransFnProto(rp, fn_ty, source_loc, fn_decl_context, is_var_args, cc);
}
fn finishTransFnProto(
rp: RestorePoint,
fn_ty: *const ZigClangFunctionType,
source_loc: ZigClangSourceLocation,
fn_decl_context: ?FnDeclContext,
is_var_args: bool,
cc: CallingConvention,
) !*ast.Node.FnProto {
const is_export = if (fn_decl_context) |ctx| ctx.is_export else false;
// TODO check for always_inline attribute
// TODO check for align attribute
// pub extern fn name(...) T
const pub_tok = try appendToken(rp.c, .Keyword_pub, "pub");
const cc_tok = if (cc == .Stdcall) try appendToken(rp.c, .Keyword_stdcallcc, "stdcallcc") else null;
const is_export = exp: {
const decl_ctx = fn_decl_context orelse break :exp false;
break :exp switch (decl_ctx.storage_class) {
.None => switch (rp.c.mode) {
.import => false,
.translate => decl_ctx.has_body,
},
.Extern, .Static => false,
.PrivateExtern => return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported storage class: private extern"),
.Auto => unreachable, // Not legal on functions
.Register => unreachable, // Not legal on functions
};
};
const extern_export_inline_tok = if (is_export)
try appendToken(rp.c, .Keyword_export, "export")
else if (cc == .C)
@ -527,7 +550,7 @@ fn transFnProto(
.name_token = name_tok,
.params = ast.Node.FnProto.ParamList.init(rp.c.a()),
.return_type = ast.Node.FnProto.ReturnType{ .Explicit = return_type_node },
.var_args_token = var_args_tok,
.var_args_token = null, // TODO this field is broken in the AST data model
.extern_export_inline_token = extern_export_inline_tok,
.cc_token = cc_tok,
.async_attr = null,
@ -536,6 +559,19 @@ fn transFnProto(
.align_expr = null,
.section_expr = null,
};
if (is_var_args) {
const var_arg_node = try rp.c.a().create(ast.Node.ParamDecl);
var_arg_node.* = ast.Node.ParamDecl{
.base = ast.Node{ .id = ast.Node.Id.ParamDecl },
.doc_comments = null,
.comptime_token = null,
.noalias_token = null,
.name_token = null,
.type_node = undefined,
.var_args_token = var_args_tok,
};
try fn_proto.params.push(&var_arg_node.base);
}
return fn_proto;
}

View File

@ -1076,6 +1076,14 @@ pub const TranslateCContext = struct {
}
pub fn add_both(self: *TranslateCContext, name: []const u8, source: []const u8, expected_lines: ...) void {
for ([]bool{ false, true }) |stage2| {
const tc = self.create(false, "source.h", name, source, expected_lines);
tc.stage2 = stage2;
self.addCase(tc);
}
}
pub fn addC_both(self: *TranslateCContext, name: []const u8, source: []const u8, expected_lines: ...) void {
for ([]bool{ false, true }) |stage2| {
const tc = self.create(false, "source.c", name, source, expected_lines);
tc.stage2 = stage2;
@ -1084,7 +1092,7 @@ pub const TranslateCContext = struct {
}
pub fn add_2(self: *TranslateCContext, name: []const u8, source: []const u8, expected_lines: ...) void {
const tc = self.create(false, "source.c", name, source, expected_lines);
const tc = self.create(false, "source.h", name, source, expected_lines);
tc.stage2 = true;
self.addCase(tc);
}

View File

@ -1,6 +1,13 @@
const tests = @import("tests.zig");
const builtin = @import("builtin");
// add_both - test for stage1 and stage2, in #include mode
// add - test stage1 only, in #include mode
// add_2 - test stage2 only, in #include mode
// addC_both - test for stage1 and stage2, in -c mode
// addC - test stage1 only, in -c mode
// addC_2 - test stage2 only, in -c mode
pub fn addCases(cases: *tests.TranslateCContext) void {
/////////////// Cases that pass for both stage1/stage2 ////////////////
cases.add_both("simple function prototypes",
@ -11,25 +18,29 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
\\pub extern fn bar() c_int;
);
cases.add_both("simple function definition",
\\void foo(void) {};
,
\\pub export fn foo() void {}
);
/////////////// Cases that pass for only stage2 ////////////////
// (none)
cases.add_2("Parameterless function prototypes",
\\void a() {}
\\void b(void) {}
\\void c();
\\void d(void);
,
\\pub export fn a() void {}
\\pub export fn b() void {}
\\pub extern fn c(...) void;
\\pub extern fn d() void;
);
/////////////// Cases that pass for only stage1 ////////////////
cases.addC("Parameterless function prototypes",
\\void foo() {}
\\void bar(void) {}
cases.add_2("simple function definition",
\\void foo(void) {}
\\static void bar(void) {}
,
\\pub export fn foo() void {}
\\pub export fn bar() void {}
\\pub extern fn bar() void {}
);
/////////////// Cases for only stage1 which are TODO items for stage2 ////////////////
cases.add("macro with left shift",
\\#define REDISMODULE_READ (1<<0)
,
@ -1681,4 +1692,13 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
\\ }
\\}
);
/////////////// Cases for only stage1 because stage2 behavior is better ////////////////
cases.addC("Parameterless function prototypes",
\\void foo() {}
\\void bar(void) {}
,
\\pub export fn foo() void {}
\\pub export fn bar() void {}
);
}