authorgravatar for evan@lagerdata.comEvan Haas <evan@lagerdata.com> 2021-02-08 11:43:57-08:00
committergravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2021-02-10 20:23:27+02:00
loga2ec77041bc4a58d922cd3f8db923b7272b1f8f7
tree2543c29e0d80afe56639e5bcaad1942eb2f939f8
parent1480c428065c01c6feff22ce84021c2e0e30aa9b

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

3 files changed, 79 insertions(+), 8 deletions(-)

src/translate_c.zig+40-4
...@@ -78,6 +78,10 @@ const Scope = struct {...@@ -78,6 +78,10 @@ const Scope = struct {
78 mangle_count: u32 = 0,78 mangle_count: u32 = 0,
79 lbrace: ast.TokenIndex,79 lbrace: ast.TokenIndex,
8080
81 /// When the block corresponds to a function, keep track of the return type
82 /// so that the return expression can be cast, if necessary
83 return_type: ?clang.QualType = null,
84
81 fn init(c: *Context, parent: *Scope, labeled: bool) !Block {85 fn init(c: *Context, parent: *Scope, labeled: bool) !Block {
82 var blk = Block{86 var blk = Block{
83 .base = .{87 .base = .{
...@@ -209,6 +213,21 @@ const Scope = struct {...@@ -209,6 +213,21 @@ const Scope = struct {
209 }213 }
210 }214 }
211215
216 fn findBlockReturnType(inner: *Scope, c: *Context) ?clang.QualType {
217 var scope = inner;
218 while (true) {
219 switch (scope.id) {
220 .Root => return null,
221 .Block => {
222 const block = @fieldParentPtr(Block, "base", scope);
223 if (block.return_type) |qt| return qt;
224 scope = scope.parent.?;
225 },
226 else => scope = scope.parent.?,
227 }
228 }
229 }
230
212 fn getAlias(scope: *Scope, name: []const u8) []const u8 {231 fn getAlias(scope: *Scope, name: []const u8) []const u8 {
213 return switch (scope.id) {232 return switch (scope.id) {
214 .Root => return name,233 .Root => return name,
...@@ -580,6 +599,8 @@ fn visitFnDecl(c: *Context, fn_decl: *const clang.FunctionDecl) Error!void {...@@ -580,6 +599,8 @@ fn visitFnDecl(c: *Context, fn_decl: *const clang.FunctionDecl) Error!void {
580 else => break fn_type,599 else => break fn_type,
581 }600 }
582 } else unreachable;601 } else unreachable;
602 const fn_ty = @ptrCast(*const clang.FunctionType, fn_type);
603 const return_qt = fn_ty.getReturnType();
583604
584 const proto_node = switch (fn_type.getTypeClass()) {605 const proto_node = switch (fn_type.getTypeClass()) {
585 .FunctionProto => blk: {606 .FunctionProto => blk: {
...@@ -617,7 +638,9 @@ fn visitFnDecl(c: *Context, fn_decl: *const clang.FunctionDecl) Error!void {...@@ -617,7 +638,9 @@ fn visitFnDecl(c: *Context, fn_decl: *const clang.FunctionDecl) Error!void {
617 // actual function definition with body638 // actual function definition with body
618 const body_stmt = fn_decl.getBody();639 const body_stmt = fn_decl.getBody();
619 var block_scope = try Scope.Block.init(rp.c, &c.global_scope.base, false);640 var block_scope = try Scope.Block.init(rp.c, &c.global_scope.base, false);
641 block_scope.return_type = return_qt;
620 defer block_scope.deinit();642 defer block_scope.deinit();
643
621 var scope = &block_scope.base;644 var scope = &block_scope.base;
622645
623 var param_id: c_uint = 0;646 var param_id: c_uint = 0;
...@@ -667,10 +690,7 @@ fn visitFnDecl(c: *Context, fn_decl: *const clang.FunctionDecl) Error!void {...@@ -667,10 +690,7 @@ fn visitFnDecl(c: *Context, fn_decl: *const clang.FunctionDecl) Error!void {
667 };690 };
668 // add return statement if the function didn't have one691 // add return statement if the function didn't have one
669 blk: {692 blk: {
670 const fn_ty = @ptrCast(*const clang.FunctionType, fn_type);
671
672 if (fn_ty.getNoReturnAttr()) break :blk;693 if (fn_ty.getNoReturnAttr()) break :blk;
673 const return_qt = fn_ty.getReturnType();
674 if (isCVoid(return_qt)) break :blk;694 if (isCVoid(return_qt)) break :blk;
675695
676 if (block_scope.statements.items.len > 0) {696 if (block_scope.statements.items.len > 0) {
...@@ -2018,16 +2038,32 @@ fn transIntegerLiteral(...@@ -2018,16 +2038,32 @@ fn transIntegerLiteral(
2018 return maybeSuppressResult(rp, scope, result_used, &as_node.base);2038 return maybeSuppressResult(rp, scope, result_used, &as_node.base);
2019}2039}
20202040
2041/// In C if a function has return type `int` and the return value is a boolean
2042/// expression, there is no implicit cast. So the translated Zig will need to
2043/// call @boolToInt
2044fn zigShouldCastBooleanReturnToInt(node: ?*ast.Node, qt: ?clang.QualType) bool {
2045 if (node == null or qt == null) return false;
2046 return isBoolRes(node.?) and cIsNativeInt(qt.?);
2047}
2048
2021fn transReturnStmt(2049fn transReturnStmt(
2022 rp: RestorePoint,2050 rp: RestorePoint,
2023 scope: *Scope,2051 scope: *Scope,
2024 expr: *const clang.ReturnStmt,2052 expr: *const clang.ReturnStmt,
2025) TransError!*ast.Node {2053) TransError!*ast.Node {
2026 const return_kw = try appendToken(rp.c, .Keyword_return, "return");2054 const return_kw = try appendToken(rp.c, .Keyword_return, "return");
2027 const rhs: ?*ast.Node = if (expr.getRetValue()) |val_expr|2055 var rhs: ?*ast.Node = if (expr.getRetValue()) |val_expr|
2028 try transExprCoercing(rp, scope, val_expr, .used, .r_value)2056 try transExprCoercing(rp, scope, val_expr, .used, .r_value)
2029 else2057 else
2030 null;2058 null;
2059 const return_qt = scope.findBlockReturnType(rp.c);
2060 if (zigShouldCastBooleanReturnToInt(rhs, return_qt)) {
2061 const bool_to_int_node = try rp.c.createBuiltinCall("@boolToInt", 1);
2062 bool_to_int_node.params()[0] = rhs.?;
2063 bool_to_int_node.rparen_token = try appendToken(rp.c, .RParen, ")");
2064
2065 rhs = &bool_to_int_node.base;
2066 }
2031 const return_expr = try ast.Node.ControlFlowExpression.create(rp.c.arena, .{2067 const return_expr = try ast.Node.ControlFlowExpression.create(rp.c.arena, .{
2032 .ltoken = return_kw,2068 .ltoken = return_kw,
2033 .tag = .Return,2069 .tag = .Return,
test/run_translated_c.zig+35
...@@ -874,4 +874,39 @@ pub fn addCases(cases: *tests.RunTranslatedCContext) void {...@@ -874,4 +874,39 @@ pub fn addCases(cases: *tests.RunTranslatedCContext) void {
874 \\ return 0;874 \\ return 0;
875 \\}875 \\}
876 , "");876 , "");
877
878 cases.add("Return boolean expression as int; issue #6215",
879 \\#include <stdlib.h>
880 \\#include <stdbool.h>
881 \\bool actual_bool(void) { return 4 - 1 < 4;}
882 \\char char_bool_ret(void) { return 0 || 1; }
883 \\short short_bool_ret(void) { return 0 < 1; }
884 \\int int_bool_ret(void) { return 1 && 1; }
885 \\long long_bool_ret(void) { return !(0 > 1); }
886 \\static int GLOBAL = 1;
887 \\int nested_scopes(int a, int b) {
888 \\ if (a == 1) {
889 \\ int target = 1;
890 \\ return b == target;
891 \\ } else {
892 \\ int target = 2;
893 \\ if (b == target) {
894 \\ return GLOBAL == 1;
895 \\ }
896 \\ return target == 2;
897 \\ }
898 \\}
899 \\int main(void) {
900 \\ if (!actual_bool()) abort();
901 \\ if (!char_bool_ret()) abort();
902 \\ if (!short_bool_ret()) abort();
903 \\ if (!int_bool_ret()) abort();
904 \\ if (!long_bool_ret()) abort();
905 \\ if (!nested_scopes(1, 1)) abort();
906 \\ if (nested_scopes(1, 2)) abort();
907 \\ if (!nested_scopes(0, 2)) abort();
908 \\ if (!nested_scopes(0, 3)) abort();
909 \\ return 1 != 1;
910 \\}
911 , "");
877}912}
test/translate_c.zig+4-4
...@@ -1305,10 +1305,10 @@ pub fn addCases(cases: *tests.TranslateCContext) void {...@@ -1305,10 +1305,10 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
1305 \\ var a: c_int = undefined;1305 \\ var a: c_int = undefined;
1306 \\ var b: f32 = undefined;1306 \\ var b: f32 = undefined;
1307 \\ var c: ?*c_void = undefined;1307 \\ var c: ?*c_void = undefined;
1308 \\ return !(a == @as(c_int, 0));1308 \\ return @boolToInt(!(a == @as(c_int, 0)));
1309 \\ return !(a != 0);1309 \\ return @boolToInt(!(a != 0));
1310 \\ return !(b != 0);1310 \\ return @boolToInt(!(b != 0));
1311 \\ return !(c != null);1311 \\ return @boolToInt(!(c != null));
1312 \\}1312 \\}
1313 });1313 });
13141314