authorgravatar for evan@lagerdata.comEvan Haas <evan@lagerdata.com> 2020-12-13 08:01:13-08:00
committergravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2020-12-22 23:59:30+02:00
logccdb81fb31f3fa1384b44b74ecdae345457bd0b5
treee58de03181610a033949c2079b607336072af5a8
parent6a75cfd0f6d45de3b4d1ad74eaec892dc1cd6e30

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

4 files changed, 322 insertions(+), 4 deletions(-)

lib/std/c.zig+1
...@@ -12,6 +12,7 @@ pub const Token = tokenizer.Token;...@@ -12,6 +12,7 @@ pub const Token = tokenizer.Token;
12pub const Tokenizer = tokenizer.Tokenizer;12pub const Tokenizer = tokenizer.Tokenizer;
13pub const parse = @import("c/parse.zig").parse;13pub const parse = @import("c/parse.zig").parse;
14pub const ast = @import("c/ast.zig");14pub const ast = @import("c/ast.zig");
15pub const builtins = @import("c/builtins.zig");
1516
16test "" {17test "" {
17 _ = tokenizer;18 _ = tokenizer;
lib/std/c/builtins.zig created+118
...@@ -0,0 +1,118 @@
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2020 Zig Contributors
3// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
4// The MIT license requires this copyright notice to be included in all copies
5// and substantial portions of the software.
6
7const std = @import("std");
8
9pub inline fn __builtin_bswap16(val: u16) callconv(.C) u16 { return @byteSwap(u16, val); }
10pub inline fn __builtin_bswap32(val: u32) callconv(.C) u32 { return @byteSwap(u32, val); }
11pub inline fn __builtin_bswap64(val: u64) callconv(.C) u64 { return @byteSwap(u64, val); }
12
13pub inline fn __builtin_signbit(val: f64) callconv(.C) c_int { return @boolToInt(std.math.signbit(val)); }
14pub inline fn __builtin_signbitf(val: f32) callconv(.C) c_int { return @boolToInt(std.math.signbit(val)); }
15
16pub inline fn __builtin_popcount(val: c_uint) callconv(.C) c_int {
17 // popcount of a c_uint will never exceed the capacity of a c_int
18 @setRuntimeSafety(false);
19 return @bitCast(c_int, @as(c_uint, @popCount(c_uint, val)));
20}
21pub inline fn __builtin_ctz(val: c_uint) callconv(.C) c_int {
22 // Returns the number of trailing 0-bits in val, starting at the least significant bit position.
23 // In C if `val` is 0, the result is undefined; in zig it's the number of bits in a c_uint
24 @setRuntimeSafety(false);
25 return @bitCast(c_int, @as(c_uint, @ctz(c_uint, val)));
26}
27pub inline fn __builtin_clz(val: c_uint) callconv(.C) c_int {
28 // Returns the number of leading 0-bits in x, starting at the most significant bit position.
29 // In C if `val` is 0, the result is undefined; in zig it's the number of bits in a c_uint
30 @setRuntimeSafety(false);
31 return @bitCast(c_int, @as(c_uint, @clz(c_uint, val)));
32}
33
34pub inline fn __builtin_sqrt(val: f64) callconv(.C) f64 { return @sqrt(val); }
35pub inline fn __builtin_sqrtf(val: f32) callconv(.C) f32 { return @sqrt(val); }
36
37pub inline fn __builtin_sin(val: f64) callconv(.C) f64 { return @sin(val); }
38pub inline fn __builtin_sinf(val: f32) callconv(.C) f32 { return @sin(val); }
39pub inline fn __builtin_cos(val: f64) callconv(.C) f64 { return @cos(val); }
40pub inline fn __builtin_cosf(val: f32) callconv(.C) f32 { return @cos(val); }
41
42pub inline fn __builtin_exp(val: f64) callconv(.C) f64 { return @exp(val); }
43pub inline fn __builtin_expf(val: f32) callconv(.C) f32 { return @exp(val); }
44pub inline fn __builtin_exp2(val: f64) callconv(.C) f64 { return @exp2(val); }
45pub inline fn __builtin_exp2f(val: f32) callconv(.C) f32 { return @exp2(val); }
46pub inline fn __builtin_log(val: f64) callconv(.C) f64 { return @log(val); }
47pub inline fn __builtin_logf(val: f32) callconv(.C) f32 { return @log(val); }
48pub inline fn __builtin_log2(val: f64) callconv(.C) f64 { return @log2(val); }
49pub inline fn __builtin_log2f(val: f32) callconv(.C) f32 { return @log2(val); }
50pub inline fn __builtin_log10(val: f64) callconv(.C) f64 { return @log10(val); }
51pub inline fn __builtin_log10f(val: f32) callconv(.C) f32 { return @log10(val); }
52
53// Standard C Library bug: The absolute value of the most negative integer remains negative.
54pub inline fn __builtin_abs(val: c_int) callconv(.C) c_int { return std.math.absInt(val) catch std.math.minInt(c_int); }
55pub inline fn __builtin_fabs(val: f64) callconv(.C) f64 { return @fabs(val); }
56pub inline fn __builtin_fabsf(val: f32) callconv(.C) f32 { return @fabs(val); }
57
58pub inline fn __builtin_floor(val: f64) callconv(.C) f64 { return @floor(val); }
59pub inline fn __builtin_floorf(val: f32) callconv(.C) f32 { return @floor(val); }
60pub inline fn __builtin_ceil(val: f64) callconv(.C) f64 { return @ceil(val); }
61pub inline fn __builtin_ceilf(val: f32) callconv(.C) f32 { return @ceil(val); }
62pub inline fn __builtin_trunc(val: f64) callconv(.C) f64 { return @trunc(val); }
63pub inline fn __builtin_truncf(val: f32) callconv(.C) f32 { return @trunc(val); }
64pub inline fn __builtin_round(val: f64) callconv(.C) f64 { return @round(val); }
65pub inline fn __builtin_roundf(val: f32) callconv(.C) f32 { return @round(val); }
66
67pub inline fn __builtin_strlen(s: [*c]const u8) callconv(.C) usize { return std.mem.lenZ(s); }
68pub inline fn __builtin_strcmp(s1: [*c]const u8, s2: [*c]const u8) callconv(.C) c_int {
69 return @as(c_int, std.cstr.cmp(s1, s2));
70}
71
72pub inline fn __builtin_object_size(ptr: ?*const c_void, ty: c_int) callconv(.C) usize {
73 // clang semantics match gcc's: https://gcc.gnu.org/onlinedocs/gcc/Object-Size-Checking.html
74 // If it is not possible to determine which objects ptr points to at compile time,
75 // __builtin_object_size should return (size_t) -1 for type 0 or 1 and (size_t) 0
76 // for type 2 or 3.
77 if (ty == 0 or ty == 1) return @bitCast(usize, -@as(c_long, 1));
78 if (ty == 2 or ty == 3) return 0;
79 unreachable;
80}
81
82pub inline fn __builtin___memset_chk(
83 dst: ?*c_void,
84 val: c_int,
85 len: usize,
86 remaining: usize,
87) callconv(.C) ?*c_void {
88 if (len > remaining) @panic("std.c.builtins.memset_chk called with len > remaining");
89 return __builtin_memset(dst, val, len);
90}
91
92pub inline fn __builtin_memset(dst: ?*c_void, val: c_int, len: usize) callconv(.C) ?*c_void {
93 const dst_cast = @ptrCast([*c]u8, dst);
94 @memset(dst_cast, @bitCast(u8, @truncate(i8, val)), len);
95 return dst;
96}
97
98pub inline fn __builtin___memcpy_chk(
99 noalias dst: ?*c_void,
100 noalias src: ?*const c_void,
101 len: usize,
102 remaining: usize,
103) callconv(.C) ?*c_void {
104 if (len > remaining) @panic("std.c.builtins.memcpy_chk called with len > remaining");
105 return __builtin_memcpy(dst, src, len);
106}
107
108pub inline fn __builtin_memcpy(
109 noalias dst: ?*c_void,
110 noalias src: ?*const c_void,
111 len: usize,
112) callconv(.C) ?*c_void {
113 const dst_cast = @ptrCast([*c]u8, dst);
114 const src_cast = @ptrCast([*c]const u8, src);
115
116 @memcpy(dst_cast, src_cast, len);
117 return dst;
118}
src/translate_c.zig+50-4
...@@ -328,6 +328,47 @@ pub const Context = struct {...@@ -328,6 +328,47 @@ pub const Context = struct {
328 }328 }
329};329};
330330
331fn addCBuiltinsNamespace(c: *Context) Error!void {
332 // pub usingnamespace @import("std").c.builtins;
333 const pub_tok = try appendToken(c, .Keyword_pub, "pub");
334 const use_tok = try appendToken(c, .Keyword_usingnamespace, "usingnamespace");
335 const import_tok = try appendToken(c, .Builtin, "@import");
336 const lparen_tok = try appendToken(c, .LParen, "(");
337 const std_tok = try appendToken(c, .StringLiteral, "\"std\"");
338 const rparen_tok = try appendToken(c, .RParen, ")");
339
340 const std_node = try c.arena.create(ast.Node.OneToken);
341 std_node.* = .{
342 .base = .{ .tag = .StringLiteral },
343 .token = std_tok,
344 };
345
346 const call_node = try ast.Node.BuiltinCall.alloc(c.arena, 1);
347 call_node.* = .{
348 .builtin_token = import_tok,
349 .params_len = 1,
350 .rparen_token = rparen_tok,
351 };
352 call_node.params()[0] = &std_node.base;
353
354 var access_chain = &call_node.base;
355 access_chain = try transCreateNodeFieldAccess(c, access_chain, "c");
356 access_chain = try transCreateNodeFieldAccess(c, access_chain, "builtins");
357
358 const semi_tok = try appendToken(c, .Semicolon, ";");
359
360 const bytes = try c.gpa.alignedAlloc(u8, @alignOf(ast.Node.Use), @sizeOf(ast.Node.Use));
361 const using_node = @ptrCast(*ast.Node.Use, bytes.ptr);
362 using_node.* = .{
363 .doc_comments = null,
364 .visib_token = pub_tok,
365 .use_token = use_tok,
366 .expr = access_chain,
367 .semicolon_token = semi_tok,
368 };
369 try c.root_decls.append(c.gpa, &using_node.base);
370}
371
331pub fn translate(372pub fn translate(
332 gpa: *mem.Allocator,373 gpa: *mem.Allocator,
333 args_begin: [*]?[*]const u8,374 args_begin: [*]?[*]const u8,
...@@ -377,6 +418,8 @@ pub fn translate(...@@ -377,6 +418,8 @@ pub fn translate(
377 context.opaque_demotes.deinit(gpa);418 context.opaque_demotes.deinit(gpa);
378 }419 }
379420
421 try addCBuiltinsNamespace(&context);
422
380 try prepopulateGlobalNameTable(ast_unit, &context);423 try prepopulateGlobalNameTable(ast_unit, &context);
381424
382 if (!ast_unit.visitLocalTopLevelDecls(&context, declVisitorC)) {425 if (!ast_unit.visitLocalTopLevelDecls(&context, declVisitorC)) {
...@@ -1726,6 +1769,9 @@ fn transImplicitCastExpr(...@@ -1726,6 +1769,9 @@ fn transImplicitCastExpr(
1726 const rhs_node = try transCreateNodeInt(rp.c, 0);1769 const rhs_node = try transCreateNodeInt(rp.c, 0);
1727 return transCreateNodeInfixOp(rp, scope, sub_expr_node, .BangEqual, op_token, rhs_node, result_used, false);1770 return transCreateNodeInfixOp(rp, scope, sub_expr_node, .BangEqual, op_token, rhs_node, result_used, false);
1728 },1771 },
1772 .BuiltinFnToFnPtr => {
1773 return transExpr(rp, scope, sub_expr, .used, .r_value);
1774 },
1729 else => |kind| return revertAndWarn(1775 else => |kind| return revertAndWarn(
1730 rp,1776 rp,
1731 error.UnsupportedTranslation,1777 error.UnsupportedTranslation,
...@@ -2028,7 +2074,6 @@ fn transCCast(...@@ -2028,7 +2074,6 @@ fn transCCast(
2028 // 1. If src_type is an enum, determine the underlying signed int type2074 // 1. If src_type is an enum, determine the underlying signed int type
2029 // 2. Extend or truncate without changing signed-ness.2075 // 2. Extend or truncate without changing signed-ness.
2030 // 3. Bit-cast to correct signed-ness2076 // 3. Bit-cast to correct signed-ness
2031
2032 const src_type_is_signed = cIsSignedInteger(src_type) or cIsEnum(src_type);2077 const src_type_is_signed = cIsSignedInteger(src_type) or cIsEnum(src_type);
2033 const src_int_type = if (cIsInteger(src_type)) src_type else cIntTypeForEnum(src_type);2078 const src_int_type = if (cIsInteger(src_type)) src_type else cIntTypeForEnum(src_type);
2034 const src_int_expr = if (cIsInteger(src_type)) expr else try transEnumToInt(rp.c, expr);2079 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...@@ -3048,8 +3093,9 @@ fn transCallExpr(rp: RestorePoint, scope: *Scope, stmt: *const clang.CallExpr, r
3048 const fn_expr = if (is_ptr and fn_ty != null) blk: {3093 const fn_expr = if (is_ptr and fn_ty != null) blk: {
3049 if (callee.getStmtClass() == .ImplicitCastExprClass) {3094 if (callee.getStmtClass() == .ImplicitCastExprClass) {
3050 const implicit_cast = @ptrCast(*const clang.ImplicitCastExpr, callee);3095 const implicit_cast = @ptrCast(*const clang.ImplicitCastExpr, callee);
30513096 const cast_kind = implicit_cast.getCastKind();
3052 if (implicit_cast.getCastKind() == .FunctionToPointerDecay) {3097 if (cast_kind == .BuiltinFnToFnPtr) break :blk raw_fn_expr;
3098 if (cast_kind == .FunctionToPointerDecay) {
3053 const subexpr = implicit_cast.getSubExpr();3099 const subexpr = implicit_cast.getSubExpr();
3054 if (subexpr.getStmtClass() == .DeclRefExprClass) {3100 if (subexpr.getStmtClass() == .DeclRefExprClass) {
3055 const decl_ref = @ptrCast(*const clang.DeclRefExpr, subexpr);3101 const decl_ref = @ptrCast(*const clang.DeclRefExpr, subexpr);
...@@ -5881,7 +5927,7 @@ fn parseCPrimaryExprInner(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!*...@@ -5881,7 +5927,7 @@ fn parseCPrimaryExprInner(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!*
5881 return error.ParseError;5927 return error.ParseError;
5882 }5928 }
58835929
5884 const ident_token = try appendTokenFmt(c, .Identifier, "{}_{}", .{slice, m.slice()});5930 const ident_token = try appendTokenFmt(c, .Identifier, "{}_{}", .{ slice, m.slice() });
5885 const identifier = try c.arena.create(ast.Node.OneToken);5931 const identifier = try c.arena.create(ast.Node.OneToken);
5886 identifier.* = .{5932 identifier.* = .{
5887 .base = .{ .tag = .Identifier },5933 .base = .{ .tag = .Identifier },
test/run_translated_c.zig+153
...@@ -491,4 +491,157 @@ pub fn addCases(cases: *tests.RunTranslatedCContext) void {...@@ -491,4 +491,157 @@ pub fn addCases(cases: *tests.RunTranslatedCContext) void {
491 \\ return 0;491 \\ return 0;
492 \\}492 \\}
493 , "");493 , "");
494
495 cases.add("issue #6707 cast builtin call result to opaque struct pointer",
496 \\#include <stdlib.h>
497 \\struct foo* make_foo(void)
498 \\{
499 \\ return (struct foo*)__builtin_strlen("0123456789ABCDEF");
500 \\}
501 \\int main(void) {
502 \\ struct foo *foo_pointer = make_foo();
503 \\ if (foo_pointer != (struct foo*)16) abort();
504 \\ return 0;
505 \\}
506 , "");
507
508 cases.add("C built-ins",
509 \\#include <stdlib.h>
510 \\#include <limits.h>
511 \\#include <stdbool.h>
512 \\#define M_E 2.71828182845904523536
513 \\#define M_PI_2 1.57079632679489661923
514 \\bool check_clz(unsigned int pos) {
515 \\ return (__builtin_clz(1 << pos) == (8 * sizeof(unsigned int) - pos - 1));
516 \\}
517 \\int main(void) {
518 \\ if (__builtin_bswap16(0x0102) != 0x0201) abort();
519 \\ if (__builtin_bswap32(0x01020304) != 0x04030201) abort();
520 \\ if (__builtin_bswap64(0x0102030405060708) != 0x0807060504030201) abort();
521 \\
522 \\ if (__builtin_signbit(0.0) != 0) abort();
523 \\ if (__builtin_signbitf(0.0f) != 0) abort();
524 \\ if (__builtin_signbit(1.0) != 0) abort();
525 \\ if (__builtin_signbitf(1.0f) != 0) abort();
526 \\ if (__builtin_signbit(-1.0) != 1) abort();
527 \\ if (__builtin_signbitf(-1.0f) != 1) abort();
528 \\
529 \\ if (__builtin_popcount(0) != 0) abort();
530 \\ if (__builtin_popcount(0b1) != 1) abort();
531 \\ if (__builtin_popcount(0b11) != 2) abort();
532 \\ if (__builtin_popcount(0b1111) != 4) abort();
533 \\ if (__builtin_popcount(0b11111111) != 8) abort();
534 \\
535 \\ if (__builtin_ctz(0b1) != 0) abort();
536 \\ if (__builtin_ctz(0b10) != 1) abort();
537 \\ if (__builtin_ctz(0b100) != 2) abort();
538 \\ if (__builtin_ctz(0b10000) != 4) abort();
539 \\ if (__builtin_ctz(0b100000000) != 8) abort();
540 \\
541 \\ if (!check_clz(0)) abort();
542 \\ if (!check_clz(1)) abort();
543 \\ if (!check_clz(2)) abort();
544 \\ if (!check_clz(4)) abort();
545 \\ if (!check_clz(8)) abort();
546 \\
547 \\ if (__builtin_sqrt(__builtin_sqrt(__builtin_sqrt(256))) != 2.0) abort();
548 \\ if (__builtin_sqrt(__builtin_sqrt(__builtin_sqrt(256.0))) != 2.0) abort();
549 \\ if (__builtin_sqrt(__builtin_sqrt(__builtin_sqrt(256.0f))) != 2.0) abort();
550 \\ if (__builtin_sqrtf(__builtin_sqrtf(__builtin_sqrtf(256.0f))) != 2.0f) abort();
551 \\
552 \\ if (__builtin_sin(1.0) != -__builtin_sin(-1.0)) abort();
553 \\ if (__builtin_sinf(1.0f) != -__builtin_sinf(-1.0f)) abort();
554 \\ if (__builtin_sin(M_PI_2) != 1.0) abort();
555 \\ if (__builtin_sinf(M_PI_2) != 1.0f) abort();
556 \\
557 \\ if (__builtin_cos(1.0) != __builtin_cos(-1.0)) abort();
558 \\ if (__builtin_cosf(1.0f) != __builtin_cosf(-1.0f)) abort();
559 \\ if (__builtin_cos(0.0) != 1.0) abort();
560 \\ if (__builtin_cosf(0.0f) != 1.0f) abort();
561 \\
562 \\ if (__builtin_exp(0) != 1.0) abort();
563 \\ if (__builtin_fabs(__builtin_exp(1.0) - M_E) > 0.00000001) abort();
564 \\ if (__builtin_exp(0.0f) != 1.0f) abort();
565 \\
566 \\ if (__builtin_exp2(0) != 1.0) abort();
567 \\ if (__builtin_exp2(4.0) != 16.0) abort();
568 \\ if (__builtin_exp2f(0.0f) != 1.0f) abort();
569 \\ if (__builtin_exp2f(4.0f) != 16.0f) abort();
570 \\
571 \\ if (__builtin_log(M_E) != 1.0) abort();
572 \\ if (__builtin_log(1.0) != 0.0) abort();
573 \\ if (__builtin_logf(1.0f) != 0.0f) abort();
574 \\
575 \\ if (__builtin_log2(8.0) != 3.0) abort();
576 \\ if (__builtin_log2(1.0) != 0.0) abort();
577 \\ if (__builtin_log2f(8.0f) != 3.0f) abort();
578 \\ if (__builtin_log2f(1.0f) != 0.0f) abort();
579 \\
580 \\ if (__builtin_log10(1000.0) != 3.0) abort();
581 \\ if (__builtin_log10(1.0) != 0.0) abort();
582 \\ if (__builtin_log10f(1000.0f) != 3.0f) abort();
583 \\ if (__builtin_log10f(1.0f) != 0.0f) abort();
584 \\
585 \\ if (__builtin_fabs(-42.0f) != 42.0) abort();
586 \\ if (__builtin_fabs(-42.0) != 42.0) abort();
587 \\ if (__builtin_fabs(-42) != 42.0) abort();
588 \\ if (__builtin_fabsf(-42.0f) != 42.0f) abort();
589 \\
590 \\ if (__builtin_fabs(-42.0f) != 42.0) abort();
591 \\ if (__builtin_fabs(-42.0) != 42.0) abort();
592 \\ if (__builtin_fabs(-42) != 42.0) abort();
593 \\ if (__builtin_fabsf(-42.0f) != 42.0f) abort();
594 \\
595 \\ if (__builtin_abs(42) != 42) abort();
596 \\ if (__builtin_abs(-42) != 42) abort();
597 \\ if (__builtin_abs(INT_MIN) != INT_MIN) abort();
598 \\
599 \\ if (__builtin_floor(42.9) != 42.0) abort();
600 \\ if (__builtin_floor(-42.9) != -43.0) abort();
601 \\ if (__builtin_floorf(42.9f) != 42.0f) abort();
602 \\ if (__builtin_floorf(-42.9f) != -43.0f) abort();
603 \\
604 \\ if (__builtin_ceil(42.9) != 43.0) abort();
605 \\ if (__builtin_ceil(-42.9) != -42) abort();
606 \\ if (__builtin_ceilf(42.9f) != 43.0f) abort();
607 \\ if (__builtin_ceilf(-42.9f) != -42.0f) abort();
608 \\
609 \\ if (__builtin_trunc(42.9) != 42.0) abort();
610 \\ if (__builtin_truncf(42.9f) != 42.0f) abort();
611 \\ if (__builtin_trunc(-42.9) != -42.0) abort();
612 \\ if (__builtin_truncf(-42.9f) != -42.0f) abort();
613 \\
614 \\ if (__builtin_round(0.5) != 1.0) abort();
615 \\ if (__builtin_round(-0.5) != -1.0) abort();
616 \\ if (__builtin_roundf(0.5f) != 1.0f) abort();
617 \\ if (__builtin_roundf(-0.5f) != -1.0f) abort();
618 \\
619 \\ if (__builtin_strcmp("abc", "abc") != 0) abort();
620 \\ if (__builtin_strcmp("abc", "def") >= 0 ) abort();
621 \\ if (__builtin_strcmp("def", "abc") <= 0) abort();
622 \\
623 \\ if (__builtin_strlen("this is a string") != 16) abort();
624 \\
625 \\ char *s = malloc(6);
626 \\ __builtin_memcpy(s, "hello", 5);
627 \\ s[5] = '\0';
628 \\ if (__builtin_strlen(s) != 5) abort();
629 \\
630 \\ __builtin_memset(s, 42, __builtin_strlen(s));
631 \\ if (s[0] != 42 || s[1] != 42 || s[2] != 42 || s[3] != 42 || s[4] != 42) abort();
632 \\
633 \\ free(s);
634 \\
635 \\ return 0;
636 \\}
637 , "");
638
639 cases.add("function macro that uses builtin",
640 \\#include <stdlib.h>
641 \\#define FOO(x, y) (__builtin_popcount((x)) + __builtin_strlen((y)))
642 \\int main() {
643 \\ if (FOO(7, "hello!") != 9) abort();
644 \\ return 0;
645 \\}
646 , "");
494}647}