Skip to content

Commit

Permalink
stage2 translate-c: implement functions with no prototype
Browse files Browse the repository at this point in the history
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
  • Loading branch information
andrewrk committed May 11, 2019
1 parent 2ef2f9d commit 10e9d47
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 51 deletions.
112 changes: 74 additions & 38 deletions src-self-hosted/translate_c.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -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,
};

Expand Down Expand Up @@ -432,61 +449,67 @@ 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,
source_loc: ZigClangSourceLocation,
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)
Expand Down Expand Up @@ -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,
Expand All @@ -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;
}

Expand Down
10 changes: 9 additions & 1 deletion test/tests.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
}
Expand Down
44 changes: 32 additions & 12 deletions test/translate_c.zig
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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) {};
/////////////// Cases that pass for only stage2 ////////////////
cases.add_2("Parameterless function prototypes",
\\void a() {}
\\void b(void) {}
\\void c();
\\void d(void);
,
\\pub export fn foo() 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 stage2 ////////////////
// (none)

/////////////// 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)
,
Expand Down Expand Up @@ -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 {}
);
}

0 comments on commit 10e9d47

Please sign in to comment.