Commit a2ec77041b

Evan Haas <evan@lagerdata.com>
2021-02-08 20:43:57
translate-c: call @boolToInt on return value when necessary
In C, if a function has return type `int` and the return expression is a boolean expression, there is no implicit cast. Therefore the translated Zig code needs to call @boolToInt() on the result. Written with feedback from @Vexu Fixes #6215
1 parent 1480c42
src/translate_c.zig
@@ -78,6 +78,10 @@ const Scope = struct {
         mangle_count: u32 = 0,
         lbrace: ast.TokenIndex,
 
+        /// When the block corresponds to a function, keep track of the return type
+        /// so that the return expression can be cast, if necessary
+        return_type: ?clang.QualType = null,
+
         fn init(c: *Context, parent: *Scope, labeled: bool) !Block {
             var blk = Block{
                 .base = .{
@@ -209,6 +213,21 @@ const Scope = struct {
         }
     }
 
+    fn findBlockReturnType(inner: *Scope, c: *Context) ?clang.QualType {
+        var scope = inner;
+        while (true) {
+            switch (scope.id) {
+                .Root => return null,
+                .Block => {
+                    const block = @fieldParentPtr(Block, "base", scope);
+                    if (block.return_type) |qt| return qt;
+                    scope = scope.parent.?;
+                },
+                else => scope = scope.parent.?,
+            }
+        }
+    }
+
     fn getAlias(scope: *Scope, name: []const u8) []const u8 {
         return switch (scope.id) {
             .Root => return name,
@@ -580,6 +599,8 @@ fn visitFnDecl(c: *Context, fn_decl: *const clang.FunctionDecl) Error!void {
             else => break fn_type,
         }
     } else unreachable;
+    const fn_ty = @ptrCast(*const clang.FunctionType, fn_type);
+    const return_qt = fn_ty.getReturnType();
 
     const proto_node = switch (fn_type.getTypeClass()) {
         .FunctionProto => blk: {
@@ -617,7 +638,9 @@ fn visitFnDecl(c: *Context, fn_decl: *const clang.FunctionDecl) Error!void {
     // actual function definition with body
     const body_stmt = fn_decl.getBody();
     var block_scope = try Scope.Block.init(rp.c, &c.global_scope.base, false);
+    block_scope.return_type = return_qt;
     defer block_scope.deinit();
+
     var scope = &block_scope.base;
 
     var param_id: c_uint = 0;
@@ -667,10 +690,7 @@ fn visitFnDecl(c: *Context, fn_decl: *const clang.FunctionDecl) Error!void {
     };
     // add return statement if the function didn't have one
     blk: {
-        const fn_ty = @ptrCast(*const clang.FunctionType, fn_type);
-
         if (fn_ty.getNoReturnAttr()) break :blk;
-        const return_qt = fn_ty.getReturnType();
         if (isCVoid(return_qt)) break :blk;
 
         if (block_scope.statements.items.len > 0) {
@@ -2018,16 +2038,32 @@ fn transIntegerLiteral(
     return maybeSuppressResult(rp, scope, result_used, &as_node.base);
 }
 
+/// In C if a function has return type `int` and the return value is a boolean
+/// expression, there is no implicit cast. So the translated Zig will need to
+/// call @boolToInt
+fn zigShouldCastBooleanReturnToInt(node: ?*ast.Node, qt: ?clang.QualType) bool {
+    if (node == null or qt == null) return false;
+    return isBoolRes(node.?) and cIsNativeInt(qt.?);
+}
+
 fn transReturnStmt(
     rp: RestorePoint,
     scope: *Scope,
     expr: *const clang.ReturnStmt,
 ) TransError!*ast.Node {
     const return_kw = try appendToken(rp.c, .Keyword_return, "return");
-    const rhs: ?*ast.Node = if (expr.getRetValue()) |val_expr|
+    var rhs: ?*ast.Node = if (expr.getRetValue()) |val_expr|
         try transExprCoercing(rp, scope, val_expr, .used, .r_value)
     else
         null;
+    const return_qt = scope.findBlockReturnType(rp.c);
+    if (zigShouldCastBooleanReturnToInt(rhs, return_qt)) {
+        const bool_to_int_node = try rp.c.createBuiltinCall("@boolToInt", 1);
+        bool_to_int_node.params()[0] = rhs.?;
+        bool_to_int_node.rparen_token = try appendToken(rp.c, .RParen, ")");
+
+        rhs = &bool_to_int_node.base;
+    }
     const return_expr = try ast.Node.ControlFlowExpression.create(rp.c.arena, .{
         .ltoken = return_kw,
         .tag = .Return,
test/run_translated_c.zig
@@ -874,4 +874,39 @@ pub fn addCases(cases: *tests.RunTranslatedCContext) void {
         \\    return 0;
         \\}
     , "");
+
+    cases.add("Return boolean expression as int; issue #6215",
+        \\#include <stdlib.h>
+        \\#include <stdbool.h>
+        \\bool  actual_bool(void)    { return 4 - 1 < 4;}
+        \\char  char_bool_ret(void)  { return 0 || 1; }
+        \\short short_bool_ret(void) { return 0 < 1; }
+        \\int   int_bool_ret(void)   { return 1 && 1; }
+        \\long  long_bool_ret(void)  { return !(0 > 1); }
+        \\static int GLOBAL = 1;
+        \\int nested_scopes(int a, int b) {
+        \\    if (a == 1) {
+        \\        int target = 1;
+        \\        return b == target;
+        \\    } else {
+        \\        int target = 2;
+        \\        if (b == target) {
+        \\            return GLOBAL == 1;
+        \\        }
+        \\        return target == 2;
+        \\    }
+        \\}
+        \\int main(void) {
+        \\    if (!actual_bool()) abort();
+        \\    if (!char_bool_ret()) abort();
+        \\    if (!short_bool_ret()) abort();
+        \\    if (!int_bool_ret()) abort();
+        \\    if (!long_bool_ret()) abort();
+        \\    if (!nested_scopes(1, 1)) abort();
+        \\    if (nested_scopes(1, 2)) abort();
+        \\    if (!nested_scopes(0, 2)) abort();
+        \\    if (!nested_scopes(0, 3)) abort();
+        \\    return 1 != 1;
+        \\}
+    , "");
 }
test/translate_c.zig
@@ -1305,10 +1305,10 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
         \\    var a: c_int = undefined;
         \\    var b: f32 = undefined;
         \\    var c: ?*c_void = undefined;
-        \\    return !(a == @as(c_int, 0));
-        \\    return !(a != 0);
-        \\    return !(b != 0);
-        \\    return !(c != null);
+        \\    return @boolToInt(!(a == @as(c_int, 0)));
+        \\    return @boolToInt(!(a != 0));
+        \\    return @boolToInt(!(b != 0));
+        \\    return @boolToInt(!(c != null));
         \\}
     });