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
This commit is contained in:
Evan Haas 2021-02-08 11:43:57 -08:00 committed by Veikka Tuominen
parent 1480c42806
commit a2ec77041b
3 changed files with 79 additions and 8 deletions

View File

@ -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,

View File

@ -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;
\\}
, "");
}

View File

@ -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));
\\}
});