From ccdb81fb31f3fa1384b44b74ecdae345457bd0b5 Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Sun, 13 Dec 2020 08:01:13 -0800 Subject: [PATCH] Improve handling of C compiler intrinsics in translate-c C compiler intrinsics can only appear as part of a function call. When called they are implicitly cast to a function pointer; treat this as a non-null pointer so that it emits as a regular Zig function call. Put `pub usingnamespace @import("std").c.builtins;` at the top of translated C files so that they will have access to builtin functions defined there. Fixes #6707 --- lib/std/c.zig | 1 + lib/std/c/builtins.zig | 118 +++++++++++++++++++++++++++++ src/translate_c.zig | 54 +++++++++++++- test/run_translated_c.zig | 153 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 322 insertions(+), 4 deletions(-) create mode 100644 lib/std/c/builtins.zig diff --git a/lib/std/c.zig b/lib/std/c.zig index 0c691daac7..57a46a1e2a 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -12,6 +12,7 @@ pub const Token = tokenizer.Token; pub const Tokenizer = tokenizer.Tokenizer; pub const parse = @import("c/parse.zig").parse; pub const ast = @import("c/ast.zig"); +pub const builtins = @import("c/builtins.zig"); test "" { _ = tokenizer; diff --git a/lib/std/c/builtins.zig b/lib/std/c/builtins.zig new file mode 100644 index 0000000000..3dd6e23b91 --- /dev/null +++ b/lib/std/c/builtins.zig @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2015-2020 Zig Contributors +// This file is part of [zig](https://ziglang.org/), which is MIT licensed. +// The MIT license requires this copyright notice to be included in all copies +// and substantial portions of the software. + +const std = @import("std"); + +pub inline fn __builtin_bswap16(val: u16) callconv(.C) u16 { return @byteSwap(u16, val); } +pub inline fn __builtin_bswap32(val: u32) callconv(.C) u32 { return @byteSwap(u32, val); } +pub inline fn __builtin_bswap64(val: u64) callconv(.C) u64 { return @byteSwap(u64, val); } + +pub inline fn __builtin_signbit(val: f64) callconv(.C) c_int { return @boolToInt(std.math.signbit(val)); } +pub inline fn __builtin_signbitf(val: f32) callconv(.C) c_int { return @boolToInt(std.math.signbit(val)); } + +pub inline fn __builtin_popcount(val: c_uint) callconv(.C) c_int { + // popcount of a c_uint will never exceed the capacity of a c_int + @setRuntimeSafety(false); + return @bitCast(c_int, @as(c_uint, @popCount(c_uint, val))); +} +pub inline fn __builtin_ctz(val: c_uint) callconv(.C) c_int { + // Returns the number of trailing 0-bits in val, starting at the least significant bit position. + // In C if `val` is 0, the result is undefined; in zig it's the number of bits in a c_uint + @setRuntimeSafety(false); + return @bitCast(c_int, @as(c_uint, @ctz(c_uint, val))); +} +pub inline fn __builtin_clz(val: c_uint) callconv(.C) c_int { + // Returns the number of leading 0-bits in x, starting at the most significant bit position. + // In C if `val` is 0, the result is undefined; in zig it's the number of bits in a c_uint + @setRuntimeSafety(false); + return @bitCast(c_int, @as(c_uint, @clz(c_uint, val))); +} + +pub inline fn __builtin_sqrt(val: f64) callconv(.C) f64 { return @sqrt(val); } +pub inline fn __builtin_sqrtf(val: f32) callconv(.C) f32 { return @sqrt(val); } + +pub inline fn __builtin_sin(val: f64) callconv(.C) f64 { return @sin(val); } +pub inline fn __builtin_sinf(val: f32) callconv(.C) f32 { return @sin(val); } +pub inline fn __builtin_cos(val: f64) callconv(.C) f64 { return @cos(val); } +pub inline fn __builtin_cosf(val: f32) callconv(.C) f32 { return @cos(val); } + +pub inline fn __builtin_exp(val: f64) callconv(.C) f64 { return @exp(val); } +pub inline fn __builtin_expf(val: f32) callconv(.C) f32 { return @exp(val); } +pub inline fn __builtin_exp2(val: f64) callconv(.C) f64 { return @exp2(val); } +pub inline fn __builtin_exp2f(val: f32) callconv(.C) f32 { return @exp2(val); } +pub inline fn __builtin_log(val: f64) callconv(.C) f64 { return @log(val); } +pub inline fn __builtin_logf(val: f32) callconv(.C) f32 { return @log(val); } +pub inline fn __builtin_log2(val: f64) callconv(.C) f64 { return @log2(val); } +pub inline fn __builtin_log2f(val: f32) callconv(.C) f32 { return @log2(val); } +pub inline fn __builtin_log10(val: f64) callconv(.C) f64 { return @log10(val); } +pub inline fn __builtin_log10f(val: f32) callconv(.C) f32 { return @log10(val); } + +// Standard C Library bug: The absolute value of the most negative integer remains negative. +pub inline fn __builtin_abs(val: c_int) callconv(.C) c_int { return std.math.absInt(val) catch std.math.minInt(c_int); } +pub inline fn __builtin_fabs(val: f64) callconv(.C) f64 { return @fabs(val); } +pub inline fn __builtin_fabsf(val: f32) callconv(.C) f32 { return @fabs(val); } + +pub inline fn __builtin_floor(val: f64) callconv(.C) f64 { return @floor(val); } +pub inline fn __builtin_floorf(val: f32) callconv(.C) f32 { return @floor(val); } +pub inline fn __builtin_ceil(val: f64) callconv(.C) f64 { return @ceil(val); } +pub inline fn __builtin_ceilf(val: f32) callconv(.C) f32 { return @ceil(val); } +pub inline fn __builtin_trunc(val: f64) callconv(.C) f64 { return @trunc(val); } +pub inline fn __builtin_truncf(val: f32) callconv(.C) f32 { return @trunc(val); } +pub inline fn __builtin_round(val: f64) callconv(.C) f64 { return @round(val); } +pub inline fn __builtin_roundf(val: f32) callconv(.C) f32 { return @round(val); } + +pub inline fn __builtin_strlen(s: [*c]const u8) callconv(.C) usize { return std.mem.lenZ(s); } +pub inline fn __builtin_strcmp(s1: [*c]const u8, s2: [*c]const u8) callconv(.C) c_int { + return @as(c_int, std.cstr.cmp(s1, s2)); +} + +pub inline fn __builtin_object_size(ptr: ?*const c_void, ty: c_int) callconv(.C) usize { + // clang semantics match gcc's: https://gcc.gnu.org/onlinedocs/gcc/Object-Size-Checking.html + // If it is not possible to determine which objects ptr points to at compile time, + // __builtin_object_size should return (size_t) -1 for type 0 or 1 and (size_t) 0 + // for type 2 or 3. + if (ty == 0 or ty == 1) return @bitCast(usize, -@as(c_long, 1)); + if (ty == 2 or ty == 3) return 0; + unreachable; +} + +pub inline fn __builtin___memset_chk( + dst: ?*c_void, + val: c_int, + len: usize, + remaining: usize, +) callconv(.C) ?*c_void { + if (len > remaining) @panic("std.c.builtins.memset_chk called with len > remaining"); + return __builtin_memset(dst, val, len); +} + +pub inline fn __builtin_memset(dst: ?*c_void, val: c_int, len: usize) callconv(.C) ?*c_void { + const dst_cast = @ptrCast([*c]u8, dst); + @memset(dst_cast, @bitCast(u8, @truncate(i8, val)), len); + return dst; +} + +pub inline fn __builtin___memcpy_chk( + noalias dst: ?*c_void, + noalias src: ?*const c_void, + len: usize, + remaining: usize, +) callconv(.C) ?*c_void { + if (len > remaining) @panic("std.c.builtins.memcpy_chk called with len > remaining"); + return __builtin_memcpy(dst, src, len); +} + +pub inline fn __builtin_memcpy( + noalias dst: ?*c_void, + noalias src: ?*const c_void, + len: usize, +) callconv(.C) ?*c_void { + const dst_cast = @ptrCast([*c]u8, dst); + const src_cast = @ptrCast([*c]const u8, src); + + @memcpy(dst_cast, src_cast, len); + return dst; +} diff --git a/src/translate_c.zig b/src/translate_c.zig index e57a1952af..1a398ffe13 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -328,6 +328,47 @@ pub const Context = struct { } }; +fn addCBuiltinsNamespace(c: *Context) Error!void { + // pub usingnamespace @import("std").c.builtins; + const pub_tok = try appendToken(c, .Keyword_pub, "pub"); + const use_tok = try appendToken(c, .Keyword_usingnamespace, "usingnamespace"); + const import_tok = try appendToken(c, .Builtin, "@import"); + const lparen_tok = try appendToken(c, .LParen, "("); + const std_tok = try appendToken(c, .StringLiteral, "\"std\""); + const rparen_tok = try appendToken(c, .RParen, ")"); + + const std_node = try c.arena.create(ast.Node.OneToken); + std_node.* = .{ + .base = .{ .tag = .StringLiteral }, + .token = std_tok, + }; + + const call_node = try ast.Node.BuiltinCall.alloc(c.arena, 1); + call_node.* = .{ + .builtin_token = import_tok, + .params_len = 1, + .rparen_token = rparen_tok, + }; + call_node.params()[0] = &std_node.base; + + var access_chain = &call_node.base; + access_chain = try transCreateNodeFieldAccess(c, access_chain, "c"); + access_chain = try transCreateNodeFieldAccess(c, access_chain, "builtins"); + + const semi_tok = try appendToken(c, .Semicolon, ";"); + + const bytes = try c.gpa.alignedAlloc(u8, @alignOf(ast.Node.Use), @sizeOf(ast.Node.Use)); + const using_node = @ptrCast(*ast.Node.Use, bytes.ptr); + using_node.* = .{ + .doc_comments = null, + .visib_token = pub_tok, + .use_token = use_tok, + .expr = access_chain, + .semicolon_token = semi_tok, + }; + try c.root_decls.append(c.gpa, &using_node.base); +} + pub fn translate( gpa: *mem.Allocator, args_begin: [*]?[*]const u8, @@ -377,6 +418,8 @@ pub fn translate( context.opaque_demotes.deinit(gpa); } + try addCBuiltinsNamespace(&context); + try prepopulateGlobalNameTable(ast_unit, &context); if (!ast_unit.visitLocalTopLevelDecls(&context, declVisitorC)) { @@ -1726,6 +1769,9 @@ fn transImplicitCastExpr( const rhs_node = try transCreateNodeInt(rp.c, 0); return transCreateNodeInfixOp(rp, scope, sub_expr_node, .BangEqual, op_token, rhs_node, result_used, false); }, + .BuiltinFnToFnPtr => { + return transExpr(rp, scope, sub_expr, .used, .r_value); + }, else => |kind| return revertAndWarn( rp, error.UnsupportedTranslation, @@ -2028,7 +2074,6 @@ fn transCCast( // 1. If src_type is an enum, determine the underlying signed int type // 2. Extend or truncate without changing signed-ness. // 3. Bit-cast to correct signed-ness - const src_type_is_signed = cIsSignedInteger(src_type) or cIsEnum(src_type); const src_int_type = if (cIsInteger(src_type)) src_type else cIntTypeForEnum(src_type); const src_int_expr = if (cIsInteger(src_type)) expr else try transEnumToInt(rp.c, expr); @@ -3048,8 +3093,9 @@ fn transCallExpr(rp: RestorePoint, scope: *Scope, stmt: *const clang.CallExpr, r const fn_expr = if (is_ptr and fn_ty != null) blk: { if (callee.getStmtClass() == .ImplicitCastExprClass) { const implicit_cast = @ptrCast(*const clang.ImplicitCastExpr, callee); - - if (implicit_cast.getCastKind() == .FunctionToPointerDecay) { + const cast_kind = implicit_cast.getCastKind(); + if (cast_kind == .BuiltinFnToFnPtr) break :blk raw_fn_expr; + if (cast_kind == .FunctionToPointerDecay) { const subexpr = implicit_cast.getSubExpr(); if (subexpr.getStmtClass() == .DeclRefExprClass) { const decl_ref = @ptrCast(*const clang.DeclRefExpr, subexpr); @@ -5881,7 +5927,7 @@ fn parseCPrimaryExprInner(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!* return error.ParseError; } - const ident_token = try appendTokenFmt(c, .Identifier, "{}_{}", .{slice, m.slice()}); + const ident_token = try appendTokenFmt(c, .Identifier, "{}_{}", .{ slice, m.slice() }); const identifier = try c.arena.create(ast.Node.OneToken); identifier.* = .{ .base = .{ .tag = .Identifier }, diff --git a/test/run_translated_c.zig b/test/run_translated_c.zig index f51beb18bf..a3bce62569 100644 --- a/test/run_translated_c.zig +++ b/test/run_translated_c.zig @@ -491,4 +491,157 @@ pub fn addCases(cases: *tests.RunTranslatedCContext) void { \\ return 0; \\} , ""); + + cases.add("issue #6707 cast builtin call result to opaque struct pointer", + \\#include + \\struct foo* make_foo(void) + \\{ + \\ return (struct foo*)__builtin_strlen("0123456789ABCDEF"); + \\} + \\int main(void) { + \\ struct foo *foo_pointer = make_foo(); + \\ if (foo_pointer != (struct foo*)16) abort(); + \\ return 0; + \\} + , ""); + + cases.add("C built-ins", + \\#include + \\#include + \\#include + \\#define M_E 2.71828182845904523536 + \\#define M_PI_2 1.57079632679489661923 + \\bool check_clz(unsigned int pos) { + \\ return (__builtin_clz(1 << pos) == (8 * sizeof(unsigned int) - pos - 1)); + \\} + \\int main(void) { + \\ if (__builtin_bswap16(0x0102) != 0x0201) abort(); + \\ if (__builtin_bswap32(0x01020304) != 0x04030201) abort(); + \\ if (__builtin_bswap64(0x0102030405060708) != 0x0807060504030201) abort(); + \\ + \\ if (__builtin_signbit(0.0) != 0) abort(); + \\ if (__builtin_signbitf(0.0f) != 0) abort(); + \\ if (__builtin_signbit(1.0) != 0) abort(); + \\ if (__builtin_signbitf(1.0f) != 0) abort(); + \\ if (__builtin_signbit(-1.0) != 1) abort(); + \\ if (__builtin_signbitf(-1.0f) != 1) abort(); + \\ + \\ if (__builtin_popcount(0) != 0) abort(); + \\ if (__builtin_popcount(0b1) != 1) abort(); + \\ if (__builtin_popcount(0b11) != 2) abort(); + \\ if (__builtin_popcount(0b1111) != 4) abort(); + \\ if (__builtin_popcount(0b11111111) != 8) abort(); + \\ + \\ if (__builtin_ctz(0b1) != 0) abort(); + \\ if (__builtin_ctz(0b10) != 1) abort(); + \\ if (__builtin_ctz(0b100) != 2) abort(); + \\ if (__builtin_ctz(0b10000) != 4) abort(); + \\ if (__builtin_ctz(0b100000000) != 8) abort(); + \\ + \\ if (!check_clz(0)) abort(); + \\ if (!check_clz(1)) abort(); + \\ if (!check_clz(2)) abort(); + \\ if (!check_clz(4)) abort(); + \\ if (!check_clz(8)) abort(); + \\ + \\ if (__builtin_sqrt(__builtin_sqrt(__builtin_sqrt(256))) != 2.0) abort(); + \\ if (__builtin_sqrt(__builtin_sqrt(__builtin_sqrt(256.0))) != 2.0) abort(); + \\ if (__builtin_sqrt(__builtin_sqrt(__builtin_sqrt(256.0f))) != 2.0) abort(); + \\ if (__builtin_sqrtf(__builtin_sqrtf(__builtin_sqrtf(256.0f))) != 2.0f) abort(); + \\ + \\ if (__builtin_sin(1.0) != -__builtin_sin(-1.0)) abort(); + \\ if (__builtin_sinf(1.0f) != -__builtin_sinf(-1.0f)) abort(); + \\ if (__builtin_sin(M_PI_2) != 1.0) abort(); + \\ if (__builtin_sinf(M_PI_2) != 1.0f) abort(); + \\ + \\ if (__builtin_cos(1.0) != __builtin_cos(-1.0)) abort(); + \\ if (__builtin_cosf(1.0f) != __builtin_cosf(-1.0f)) abort(); + \\ if (__builtin_cos(0.0) != 1.0) abort(); + \\ if (__builtin_cosf(0.0f) != 1.0f) abort(); + \\ + \\ if (__builtin_exp(0) != 1.0) abort(); + \\ if (__builtin_fabs(__builtin_exp(1.0) - M_E) > 0.00000001) abort(); + \\ if (__builtin_exp(0.0f) != 1.0f) abort(); + \\ + \\ if (__builtin_exp2(0) != 1.0) abort(); + \\ if (__builtin_exp2(4.0) != 16.0) abort(); + \\ if (__builtin_exp2f(0.0f) != 1.0f) abort(); + \\ if (__builtin_exp2f(4.0f) != 16.0f) abort(); + \\ + \\ if (__builtin_log(M_E) != 1.0) abort(); + \\ if (__builtin_log(1.0) != 0.0) abort(); + \\ if (__builtin_logf(1.0f) != 0.0f) abort(); + \\ + \\ if (__builtin_log2(8.0) != 3.0) abort(); + \\ if (__builtin_log2(1.0) != 0.0) abort(); + \\ if (__builtin_log2f(8.0f) != 3.0f) abort(); + \\ if (__builtin_log2f(1.0f) != 0.0f) abort(); + \\ + \\ if (__builtin_log10(1000.0) != 3.0) abort(); + \\ if (__builtin_log10(1.0) != 0.0) abort(); + \\ if (__builtin_log10f(1000.0f) != 3.0f) abort(); + \\ if (__builtin_log10f(1.0f) != 0.0f) abort(); + \\ + \\ if (__builtin_fabs(-42.0f) != 42.0) abort(); + \\ if (__builtin_fabs(-42.0) != 42.0) abort(); + \\ if (__builtin_fabs(-42) != 42.0) abort(); + \\ if (__builtin_fabsf(-42.0f) != 42.0f) abort(); + \\ + \\ if (__builtin_fabs(-42.0f) != 42.0) abort(); + \\ if (__builtin_fabs(-42.0) != 42.0) abort(); + \\ if (__builtin_fabs(-42) != 42.0) abort(); + \\ if (__builtin_fabsf(-42.0f) != 42.0f) abort(); + \\ + \\ if (__builtin_abs(42) != 42) abort(); + \\ if (__builtin_abs(-42) != 42) abort(); + \\ if (__builtin_abs(INT_MIN) != INT_MIN) abort(); + \\ + \\ if (__builtin_floor(42.9) != 42.0) abort(); + \\ if (__builtin_floor(-42.9) != -43.0) abort(); + \\ if (__builtin_floorf(42.9f) != 42.0f) abort(); + \\ if (__builtin_floorf(-42.9f) != -43.0f) abort(); + \\ + \\ if (__builtin_ceil(42.9) != 43.0) abort(); + \\ if (__builtin_ceil(-42.9) != -42) abort(); + \\ if (__builtin_ceilf(42.9f) != 43.0f) abort(); + \\ if (__builtin_ceilf(-42.9f) != -42.0f) abort(); + \\ + \\ if (__builtin_trunc(42.9) != 42.0) abort(); + \\ if (__builtin_truncf(42.9f) != 42.0f) abort(); + \\ if (__builtin_trunc(-42.9) != -42.0) abort(); + \\ if (__builtin_truncf(-42.9f) != -42.0f) abort(); + \\ + \\ if (__builtin_round(0.5) != 1.0) abort(); + \\ if (__builtin_round(-0.5) != -1.0) abort(); + \\ if (__builtin_roundf(0.5f) != 1.0f) abort(); + \\ if (__builtin_roundf(-0.5f) != -1.0f) abort(); + \\ + \\ if (__builtin_strcmp("abc", "abc") != 0) abort(); + \\ if (__builtin_strcmp("abc", "def") >= 0 ) abort(); + \\ if (__builtin_strcmp("def", "abc") <= 0) abort(); + \\ + \\ if (__builtin_strlen("this is a string") != 16) abort(); + \\ + \\ char *s = malloc(6); + \\ __builtin_memcpy(s, "hello", 5); + \\ s[5] = '\0'; + \\ if (__builtin_strlen(s) != 5) abort(); + \\ + \\ __builtin_memset(s, 42, __builtin_strlen(s)); + \\ if (s[0] != 42 || s[1] != 42 || s[2] != 42 || s[3] != 42 || s[4] != 42) abort(); + \\ + \\ free(s); + \\ + \\ return 0; + \\} + , ""); + + cases.add("function macro that uses builtin", + \\#include + \\#define FOO(x, y) (__builtin_popcount((x)) + __builtin_strlen((y))) + \\int main() { + \\ if (FOO(7, "hello!") != 9) abort(); + \\ return 0; + \\} + , ""); }