aro translate-c: support for record types added

This commit is contained in:
february cozzocrea 2024-03-02 17:14:41 -08:00 committed by Andrew Kelley
parent bcb534c295
commit c9ad1b5199
25 changed files with 886 additions and 465 deletions

View File

@ -1142,12 +1142,14 @@ pub fn alignof(ty: Type, comp: *const Compilation) u29 {
};
}
pub const QualHandling = enum { standard, preserve_quals };
/// Canonicalize a possibly-typeof() type. If the type is not a typeof() type, simply
/// return it. Otherwise, determine the actual qualified type.
/// The `qual_handling` parameter can be used to return the full set of qualifiers
/// added by typeof() operations, which is useful when determining the elemType of
/// arrays and pointers.
pub fn canonicalize(ty: Type, qual_handling: enum { standard, preserve_quals }) Type {
pub fn canonicalize(ty: Type, qual_handling: QualHandling) Type {
var cur = ty;
if (cur.specifier == .attributed) {
cur = cur.data.attributed.base;

View File

@ -52,18 +52,17 @@ fn getMangle(c: *Context) u32 {
return c.mangle_count;
}
/// Convert a clang source location to a file:line:column string
fn locStr(c: *Context, loc: TokenIndex) ![]const u8 {
_ = c;
_ = loc;
// const spelling_loc = c.source_manager.getSpellingLoc(loc);
// const filename_c = c.source_manager.getFilename(spelling_loc);
// const filename = if (filename_c) |s| try c.str(s) else @as([]const u8, "(no file)");
/// Convert an aro TokenIndex to a 'file:line:column' string
fn locStr(c: *Context, tok_idx: TokenIndex) ![]const u8 {
const token_loc = c.tree.tokens.items(.loc)[tok_idx];
const source = c.comp.getSource(token_loc.id);
const line_col = source.lineCol(token_loc);
const filename = source.path;
// const line = c.source_manager.getSpellingLineNumber(spelling_loc);
// const column = c.source_manager.getSpellingColumnNumber(spelling_loc);
// return std.fmt.allocPrint(c.arena, "{s}:{d}:{d}", .{ filename, line, column });
return "somewhere";
const line = source.physicalLine(token_loc);
const col = line_col.col;
return std.fmt.allocPrint(c.arena, "{s}:{d}:{d}", .{ filename, line, col });
}
fn maybeSuppressResult(c: *Context, used: ResultUsed, result: ZigNode) TransError!ZigNode {
@ -184,26 +183,30 @@ fn prepopulateGlobalNameTable(c: *Context) !void {
const node_data = c.tree.nodes.items(.data);
for (c.tree.root_decls) |node| {
const data = node_data[@intFromEnum(node)];
const decl_name = switch (node_tags[@intFromEnum(node)]) {
switch (node_tags[@intFromEnum(node)]) {
.typedef => @panic("TODO"),
.static_assert,
.struct_decl_two,
.union_decl_two,
.struct_decl,
.union_decl,
=> blk: {
const ty = node_types[@intFromEnum(node)];
const name_id = ty.data.record.name;
break :blk c.mapper.lookup(name_id);
},
.struct_forward_decl,
.union_forward_decl,
.enum_decl_two,
.enum_decl,
=> blk: {
const ty = node_types[@intFromEnum(node)];
const name_id = ty.data.@"enum".name;
break :blk c.mapper.lookup(name_id);
.enum_forward_decl,
=> {
const raw_ty = node_types[@intFromEnum(node)];
const ty = raw_ty.canonicalize(.standard);
const name_id = if (ty.isRecord()) ty.data.record.name else ty.data.@"enum".name;
const decl_name = c.mapper.lookup(name_id);
const container_prefix = if (ty.is(.@"struct")) "struct" else if (ty.is(.@"union")) "union" else "enum";
const prefixed_name = try std.fmt.allocPrint(c.arena, "{s}_{s}", .{ container_prefix, decl_name });
// `decl_name` and `prefixed_name` are the preferred names for this type.
// However, we can name it anything else if necessary, so these are "weak names".
try c.weak_global_names.ensureUnusedCapacity(c.gpa, 2);
c.weak_global_names.putAssumeCapacity(decl_name, {});
c.weak_global_names.putAssumeCapacity(prefixed_name, {});
},
.fn_proto,
@ -215,80 +218,256 @@ fn prepopulateGlobalNameTable(c: *Context) !void {
.inline_fn_def,
.inline_static_fn_def,
.@"var",
.extern_var,
.static_var,
.threadlocal_var,
.threadlocal_static_var,
.extern_var,
.threadlocal_extern_var,
=> c.tree.tokSlice(data.decl.name),
else => unreachable,
};
try c.global_names.put(c.gpa, decl_name, {});
}
}
fn transTopLevelDecls(c: *Context) !void {
const node_tags = c.tree.nodes.items(.tag);
const node_data = c.tree.nodes.items(.data);
for (c.tree.root_decls) |node| {
const data = node_data[@intFromEnum(node)];
switch (node_tags[@intFromEnum(node)]) {
.typedef => {
try transTypeDef(c, &c.global_scope.base, node);
},
.static_assert,
.struct_decl_two,
.union_decl_two,
.struct_decl,
.union_decl,
=> {
try transRecordDecl(c, &c.global_scope.base, node);
},
.enum_decl_two => {
var fields = [2]NodeIndex{ data.bin.lhs, data.bin.rhs };
var field_count: u8 = 0;
if (fields[0] != .none) field_count += 1;
if (fields[1] != .none) field_count += 1;
try transEnumDecl(c, &c.global_scope.base, node, fields[0..field_count]);
},
.enum_decl => {
const fields = c.tree.data[data.range.start..data.range.end];
try transEnumDecl(c, &c.global_scope.base, node, fields);
},
.fn_proto,
.static_fn_proto,
.inline_fn_proto,
.inline_static_fn_proto,
.fn_def,
.static_fn_def,
.inline_fn_def,
.inline_static_fn_def,
=> {
try transFnDecl(c, node);
},
.@"var",
.static_var,
.threadlocal_var,
.threadlocal_static_var,
.extern_var,
.threadlocal_extern_var,
=> {
try transVarDecl(c, node, null);
const decl_name = c.tree.tokSlice(data.decl.name);
try c.global_names.put(c.gpa, decl_name, {});
},
else => unreachable,
}
}
}
fn transTopLevelDecls(c: *Context) !void {
for (c.tree.root_decls) |node| {
try transDecl(c, &c.global_scope.base, node);
}
}
fn transDecl(c: *Context, scope: *Scope, decl: NodeIndex) !void {
const node_tags = c.tree.nodes.items(.tag);
const node_data = c.tree.nodes.items(.data);
const data = node_data[@intFromEnum(decl)];
switch (node_tags[@intFromEnum(decl)]) {
.typedef => {
try transTypeDef(c, scope, decl);
},
.struct_decl_two,
.union_decl_two,
=> {
var fields = [2]NodeIndex{ data.bin.lhs, data.bin.rhs };
var field_count: u2 = 0;
if (fields[0] != .none) field_count += 1;
if (fields[1] != .none) field_count += 1;
try transRecordDecl(c, scope, decl, fields[0..field_count]);
},
.struct_decl,
.union_decl,
=> {
const fields = c.tree.data[data.range.start..data.range.end];
try transRecordDecl(c, scope, decl, fields);
},
.enum_decl_two => {
var fields = [2]NodeIndex{ data.bin.lhs, data.bin.rhs };
var field_count: u8 = 0;
if (fields[0] != .none) field_count += 1;
if (fields[1] != .none) field_count += 1;
try transEnumDecl(c, scope, decl, fields[0..field_count]);
},
.enum_decl => {
const fields = c.tree.data[data.range.start..data.range.end];
try transEnumDecl(c, scope, decl, fields);
},
.enum_field_decl,
.record_field_decl,
.indirect_record_field_decl,
.struct_forward_decl,
.union_forward_decl,
.enum_forward_decl,
=> return,
.fn_proto,
.static_fn_proto,
.inline_fn_proto,
.inline_static_fn_proto,
.fn_def,
.static_fn_def,
.inline_fn_def,
.inline_static_fn_def,
=> {
try transFnDecl(c, decl);
},
.@"var",
.extern_var,
.static_var,
.threadlocal_var,
.threadlocal_extern_var,
.threadlocal_static_var,
=> {
try transVarDecl(c, decl, null);
},
else => unreachable,
}
}
fn transTypeDef(_: *Context, _: *Scope, _: NodeIndex) Error!void {
@panic("TODO");
}
fn transRecordDecl(_: *Context, _: *Scope, _: NodeIndex) Error!void {
@panic("TODO");
fn mangleWeakGlobalName(c: *Context, want_name: []const u8) ![]const u8 {
var cur_name = want_name;
if (!c.weak_global_names.contains(want_name)) {
// This type wasn't noticed by the name detection pass, so nothing has been treating this as
// a weak global name. We must mangle it to avoid conflicts with locals.
cur_name = try std.fmt.allocPrint(c.arena, "{s}_{d}", .{ want_name, c.getMangle() });
}
while (c.global_names.contains(cur_name)) {
cur_name = try std.fmt.allocPrint(c.arena, "{s}_{d}", .{ want_name, c.getMangle() });
}
return cur_name;
}
fn transRecordDecl(c: *Context, scope: *Scope, record_node: NodeIndex, field_nodes: []const NodeIndex) Error!void {
const node_types = c.tree.nodes.items(.ty);
const raw_record_ty = node_types[@intFromEnum(record_node)];
const record_decl = raw_record_ty.getRecord().?;
if (c.decl_table.get(@intFromPtr(record_decl))) |_|
return; // Avoid processing this decl twice
const toplevel = scope.id == .root;
const bs: *Scope.Block = if (!toplevel) try scope.findBlockScope(c) else undefined;
const container_kind: ZigTag = if (raw_record_ty.is(.@"union")) .@"union" else .@"struct";
const container_kind_name: []const u8 = @tagName(container_kind);
var is_unnamed = false;
var bare_name: []const u8 = c.mapper.lookup(record_decl.name);
var name = bare_name;
if (c.unnamed_typedefs.get(@intFromPtr(record_decl))) |typedef_name| {
bare_name = typedef_name;
name = typedef_name;
} else {
if (raw_record_ty.isAnonymousRecord(c.comp)) {
bare_name = try std.fmt.allocPrint(c.arena, "unnamed_{d}", .{c.getMangle()});
is_unnamed = true;
}
name = try std.fmt.allocPrint(c.arena, "{s}_{s}", .{ container_kind_name, bare_name });
if (toplevel and !is_unnamed) {
name = try mangleWeakGlobalName(c, name);
}
}
if (!toplevel) name = try bs.makeMangledName(c, name);
try c.decl_table.putNoClobber(c.gpa, @intFromPtr(record_decl), name);
const is_pub = toplevel and !is_unnamed;
const init_node = blk: {
var fields = try std.ArrayList(ast.Payload.Record.Field).initCapacity(c.gpa, record_decl.fields.len);
defer fields.deinit();
// TODO: Add support for flexible array field functions
var functions = std.ArrayList(ZigNode).init(c.gpa);
defer functions.deinit();
var unnamed_field_count: u32 = 0;
// If a record doesn't have any attributes that would affect the alignment and
// layout, then we can just use a simple `extern` type. If it does have attributes,
// then we need to inspect the layout and assign an `align` value for each field.
const has_alignment_attributes = record_decl.field_attributes != null or
raw_record_ty.hasAttribute(.@"packed") or
raw_record_ty.hasAttribute(.aligned);
const head_field_alignment: ?c_uint = headFieldAlignment(record_decl);
// Iterate over field nodes so that we translate any type decls included in this record decl.
// TODO: Move this logic into `fn transType()` instead of handling decl translation here.
for (field_nodes) |field_node| {
const field_raw_ty = node_types[@intFromEnum(field_node)];
if (field_raw_ty.isEnumOrRecord()) try transDecl(c, scope, field_node);
}
for (record_decl.fields, 0..) |field, field_index| {
const field_loc = field.name_tok;
// Demote record to opaque if it contains a bitfield
if (!field.isRegularField()) {
try c.opaque_demotes.put(c.gpa, @intFromPtr(record_decl), {});
try warn(c, scope, field_loc, "{s} demoted to opaque type - has bitfield", .{container_kind_name});
break :blk ZigTag.opaque_literal.init();
}
var field_name = c.mapper.lookup(field.name);
if (!field.isNamed()) {
field_name = try std.fmt.allocPrint(c.arena, "unnamed_{d}", .{unnamed_field_count});
unnamed_field_count += 1;
}
const field_type = transType(c, scope, field.ty, .preserve_quals, field_loc) catch |err| switch (err) {
error.UnsupportedType => {
try c.opaque_demotes.put(c.gpa, @intFromPtr(record_decl), {});
try warn(c, scope, 0, "{s} demoted to opaque type - unable to translate type of field {s}", .{
container_kind_name,
field_name,
});
break :blk ZigTag.opaque_literal.init();
},
else => |e| return e,
};
const field_alignment = if (has_alignment_attributes)
alignmentForField(record_decl, head_field_alignment, field_index)
else
null;
// C99 introduced designated initializers for structs. Omitted fields are implicitly
// initialized to zero. Some C APIs are designed with this in mind. Defaulting to zero
// values for translated struct fields permits Zig code to comfortably use such an API.
const default_value = if (container_kind == .@"struct")
try ZigTag.std_mem_zeroes.create(c.arena, field_type)
else
null;
fields.appendAssumeCapacity(.{
.name = field_name,
.type = field_type,
.alignment = field_alignment,
.default_value = default_value,
});
}
const record_payload = try c.arena.create(ast.Payload.Record);
record_payload.* = .{
.base = .{ .tag = container_kind },
.data = .{
.layout = .@"extern",
.fields = try c.arena.dupe(ast.Payload.Record.Field, fields.items),
.functions = try c.arena.dupe(ZigNode, functions.items),
.variables = &.{},
},
};
break :blk ZigNode.initPayload(&record_payload.base);
};
const payload = try c.arena.create(ast.Payload.SimpleVarDecl);
payload.* = .{
.base = .{ .tag = ([2]ZigTag{ .var_simple, .pub_var_simple })[@intFromBool(is_pub)] },
.data = .{
.name = name,
.init = init_node,
},
};
const node = ZigNode.initPayload(&payload.base);
if (toplevel) {
try addTopLevelDecl(c, name, node);
// Only add the alias if the name is available *and* it was caught by
// name detection. Don't bother performing a weak mangle, since a
// mangled name is of no real use here.
if (!is_unnamed and !c.global_names.contains(bare_name) and c.weak_global_names.contains(bare_name))
try c.alias_list.append(.{ .alias = bare_name, .name = name });
} else {
try scope.appendNode(node);
if (node.tag() != .pub_var_simple) {
try bs.discardVariable(c, name);
}
}
}
fn transFnDecl(c: *Context, fn_decl: NodeIndex) Error!void {
@ -419,7 +598,7 @@ fn transEnumDecl(c: *Context, scope: *Scope, enum_decl: NodeIndex, field_nodes:
enum_val_name = try bs.makeMangledName(c, enum_val_name);
}
const enum_const_type_node: ?ZigNode = transType(c, scope, field.ty, field.name_tok) catch |err| switch (err) {
const enum_const_type_node: ?ZigNode = transType(c, scope, field.ty, .standard, field.name_tok) catch |err| switch (err) {
error.UnsupportedType => null,
else => |e| return e,
};
@ -439,7 +618,7 @@ fn transEnumDecl(c: *Context, scope: *Scope, enum_decl: NodeIndex, field_nodes:
}
}
break :blk transType(c, scope, ty.data.@"enum".tag_ty, 0) catch |err| switch (err) {
break :blk transType(c, scope, ty.data.@"enum".tag_ty, .standard, 0) catch |err| switch (err) {
error.UnsupportedType => {
return failDecl(c, 0, name, "unable to translate enum integer type", .{});
},
@ -472,8 +651,8 @@ fn transEnumDecl(c: *Context, scope: *Scope, enum_decl: NodeIndex, field_nodes:
}
}
fn transType(c: *Context, scope: *Scope, raw_ty: Type, source_loc: TokenIndex) TypeError!ZigNode {
const ty = raw_ty.canonicalize(.standard);
fn transType(c: *Context, scope: *Scope, raw_ty: Type, qual_handling: Type.QualHandling, source_loc: TokenIndex) TypeError!ZigNode {
const ty = raw_ty.canonicalize(qual_handling);
switch (ty.specifier) {
.void => return ZigTag.type.create(c.arena, "anyopaque"),
.bool => return ZigTag.type.create(c.arena, "bool"),
@ -496,16 +675,152 @@ fn transType(c: *Context, scope: *Scope, raw_ty: Type, source_loc: TokenIndex) T
.long_double => return ZigTag.type.create(c.arena, "c_longdouble"),
.float80 => return ZigTag.type.create(c.arena, "f80"),
.float128 => return ZigTag.type.create(c.arena, "f128"),
.@"enum" => @panic("TODO"),
.pointer,
.unspecified_variable_len_array,
.array,
.static_array,
.incomplete_array,
=> @panic("TODO"),
.func,
.var_args_func,
.old_style_func,
=> return transFnType(c, scope, raw_ty, ty, source_loc, .{}),
=> return transFnType(c, scope, ty, ty, source_loc, .{}),
.@"struct",
.@"union",
=> {
var trans_scope = scope;
if (ty.isAnonymousRecord(c.comp)) {
const record_decl = ty.data.record;
const name_id = c.mapper.lookup(record_decl.name);
if (c.weak_global_names.contains(name_id)) trans_scope = &c.global_scope.base;
}
const name = c.decl_table.get(@intFromPtr(ty.data.record)).?;
return ZigTag.identifier.create(c.arena, name);
},
.attributed,
.typeof_type,
.typeof_expr,
=> unreachable,
else => return error.UnsupportedType,
}
}
fn zigAlignment(bit_alignment: u29) u32 {
return bit_alignment / 8;
/// Look ahead through the fields of the record to determine what the alignment of the record
/// would be without any align/packed/etc. attributes. This helps us determine whether or not
/// the fields with 0 offset need an `align` qualifier. Strictly speaking, we could just
/// pedantically assign those fields the same alignment as the parent's pointer alignment,
/// but this helps the generated code to be a little less verbose.
fn headFieldAlignment(record_decl: *const Type.Record) ?c_uint {
const bits_per_byte = 8;
const parent_ptr_alignment_bits = record_decl.type_layout.pointer_alignment_bits;
const parent_ptr_alignment = parent_ptr_alignment_bits / bits_per_byte;
var max_field_alignment_bits: u64 = 0;
for (record_decl.fields) |field| {
if (field.ty.getRecord()) |field_record_decl| {
const child_record_alignment = field_record_decl.type_layout.field_alignment_bits;
if (child_record_alignment > max_field_alignment_bits)
max_field_alignment_bits = child_record_alignment;
} else {
const field_size = field.layout.size_bits;
if (field_size > max_field_alignment_bits)
max_field_alignment_bits = field_size;
}
}
if (max_field_alignment_bits != parent_ptr_alignment_bits) {
return parent_ptr_alignment;
} else {
return null;
}
}
/// This function returns a ?c_uint to match Clang's behaviour of using c_uint.
/// This can be changed to a u29 after the Clang frontend for translate-c is removed.
fn alignmentForField(
record_decl: *const Type.Record,
head_field_alignment: ?c_uint,
field_index: usize,
) ?c_uint {
const fields = record_decl.fields;
assert(fields.len != 0);
const field = fields[field_index];
const bits_per_byte = 8;
const parent_ptr_alignment_bits = record_decl.type_layout.pointer_alignment_bits;
const parent_ptr_alignment = parent_ptr_alignment_bits / bits_per_byte;
// bitfields aren't supported yet. Until support is added, records with bitfields
// should be demoted to opaque, and this function shouldn't be called for them.
if (!field.isRegularField()) {
@panic("TODO: add bitfield support for records");
}
const field_offset_bits: u64 = field.layout.offset_bits;
const field_size_bits: u64 = field.layout.size_bits;
// Fields with zero width always have an alignment of 1
if (field_size_bits == 0) {
return 1;
}
// Fields with 0 offset inherit the parent's pointer alignment.
if (field_offset_bits == 0) {
return head_field_alignment;
}
// Records have a natural alignment when used as a field, and their size is
// a multiple of this alignment value. For all other types, the natural alignment
// is their size.
const field_natural_alignment_bits: u64 = if (field.ty.getRecord()) |record| record.type_layout.field_alignment_bits else field_size_bits;
const rem_bits = field_offset_bits % field_natural_alignment_bits;
// If there's a remainder, then the alignment is smaller than the field's
// natural alignment
if (rem_bits > 0) {
const rem_alignment = rem_bits / bits_per_byte;
if (rem_alignment > 0 and std.math.isPowerOfTwo(rem_alignment)) {
const actual_alignment = @min(rem_alignment, parent_ptr_alignment);
return @as(c_uint, @truncate(actual_alignment));
} else {
return 1;
}
}
// A field may have an offset which positions it to be naturally aligned, but the
// parent's pointer alignment determines if this is actually true, so we take the minimum
// value.
// For example, a float field (4 bytes wide) with a 4 byte offset is positioned to have natural
// alignment, but if the parent pointer alignment is 2, then the actual alignment of the
// float is 2.
const field_natural_alignment: u64 = field_natural_alignment_bits / bits_per_byte;
const offset_alignment = field_offset_bits / bits_per_byte;
const possible_alignment = @min(parent_ptr_alignment, offset_alignment);
if (possible_alignment == field_natural_alignment) {
return null;
} else if (possible_alignment < field_natural_alignment) {
if (std.math.isPowerOfTwo(possible_alignment)) {
return possible_alignment;
} else {
return 1;
}
} else { // possible_alignment > field_natural_alignment
// Here, the field is positioned be at a higher alignment than it's natural alignment. This means we
// need to determine whether it's a specified alignment. We can determine that from the padding preceding
// the field.
const padding_from_prev_field: u64 = blk: {
if (field_offset_bits != 0) {
const previous_field = fields[field_index - 1];
break :blk (field_offset_bits - previous_field.layout.offset_bits) - previous_field.layout.size_bits;
} else {
break :blk 0;
}
};
if (padding_from_prev_field < field_natural_alignment_bits) {
return null;
} else {
return possible_alignment;
}
}
}
const FnProtoContext = struct {
@ -536,7 +851,7 @@ fn transFnType(
else
c.mapper.lookup(param_info.name);
const type_node = try transType(c, scope, param_ty, param_info.name_tok);
const type_node = try transType(c, scope, param_ty, .standard, param_info.name_tok);
param_node.* = .{
.is_noalias = is_noalias,
.name = param_name,
@ -551,7 +866,7 @@ fn transFnType(
break :blk null;
};
const alignment = if (raw_ty.requestedAlignment(c.comp)) |alignment| zigAlignment(alignment) else null;
const alignment: ?c_uint = raw_ty.requestedAlignment(c.comp) orelse null;
const explicit_callconv = null;
// const explicit_callconv = if ((ctx.is_inline or ctx.is_export or ctx.is_extern) and ctx.cc == .C) null else ctx.cc;
@ -565,7 +880,7 @@ fn transFnType(
// convert primitive anyopaque to actual void (only for return type)
break :blk ZigTag.void_type.init();
} else {
break :blk transType(c, scope, return_ty, source_loc) catch |err| switch (err) {
break :blk transType(c, scope, return_ty, .standard, source_loc) catch |err| switch (err) {
error.UnsupportedType => {
try warn(c, scope, source_loc, "unsupported function proto return type", .{});
return err;
@ -642,7 +957,7 @@ fn transExpr(c: *Context, node: NodeIndex, result_used: ResultUsed) TransError!Z
// TODO handle other values
const int = try transCreateNodeAPInt(c, val);
const as_node = try ZigTag.as.create(c.arena, .{
.lhs = try transType(c, undefined, ty, undefined),
.lhs = try transType(c, undefined, ty, .standard, undefined),
.rhs = int,
});
return maybeSuppressResult(c, result_used, as_node);

View File

@ -0,0 +1,20 @@
struct Bar;
struct Foo {
struct Bar *next;
};
struct Bar {
struct Foo *next;
};
// translate-c
// c_frontend=clang
//
// pub const struct_Bar = extern struct {
// next: [*c]struct_Foo = @import("std").mem.zeroes([*c]struct_Foo),
// };
//
// pub const struct_Foo = extern struct {
// next: [*c]struct_Bar = @import("std").mem.zeroes([*c]struct_Bar),
// };

View File

@ -0,0 +1,25 @@
typedef struct Bar Bar;
typedef struct Foo Foo;
struct Foo {
Foo *a;
};
struct Bar {
Foo *a;
};
// translate-c
// c_frontend=clang
//
// pub const struct_Foo = extern struct {
// a: [*c]Foo = @import("std").mem.zeroes([*c]Foo),
// };
//
// pub const Foo = struct_Foo;
//
// pub const struct_Bar = extern struct {
// a: [*c]Foo = @import("std").mem.zeroes([*c]Foo),
// };
//
// pub const Bar = struct_Bar;

View File

@ -0,0 +1,18 @@
unsigned long foo(unsigned long x) {
return ((union{unsigned long _x}){x})._x;
}
// translate-c
// c_frontend=clang
//
// pub export fn foo(arg_x: c_ulong) c_ulong {
// var x = arg_x;
// _ = &x;
// const union_unnamed_1 = extern union {
// _x: c_ulong,
// };
// _ = &union_unnamed_1;
// return (union_unnamed_1{
// ._x = x,
// })._x;
// }

View File

@ -0,0 +1,15 @@
struct foo {
int x;
};
const char *struct_foo = "hello world";
// translate-c
// c_frontend=clang
//
// pub const struct_foo_1 = extern struct {
// x: c_int = @import("std").mem.zeroes(c_int),
// };
//
// pub const foo = struct_foo_1;
//
// pub export var struct_foo: [*c]const u8 = "hello world";

View File

@ -0,0 +1,20 @@
struct __attribute__((packed)) bar {
short a;
float b;
double c;
short x;
float y;
double z;
};
// translate-c
// c_frontend=aro,clang
//
// pub const struct_bar = extern struct {
// a: c_short align(1) = @import("std").mem.zeroes(c_short),
// b: f32 align(1) = @import("std").mem.zeroes(f32),
// c: f64 align(1) = @import("std").mem.zeroes(f64),
// x: c_short align(1) = @import("std").mem.zeroes(c_short),
// y: f32 align(1) = @import("std").mem.zeroes(f32),
// z: f64 align(1) = @import("std").mem.zeroes(f64),
// };

View File

@ -0,0 +1,25 @@
// NOTE: The nested struct is *not* packed/aligned,
// even though the parent struct is
// this is consistent with GCC docs
union Foo{
short x;
double y;
struct {
int b;
} z;
} __attribute__((packed));
// translate-c
// c_frontend=aro,clang
//
// const struct_unnamed_1 = extern struct {
// b: c_int = @import("std").mem.zeroes(c_int),
// };
//
// pub const union_Foo = extern union {
// x: c_short align(1),
// y: f64 align(1),
// z: struct_unnamed_1 align(1),
// };
//
// pub const Foo = union_Foo;

View File

@ -0,0 +1,14 @@
union Foo {
short x;
double y;
} __attribute__((packed));
// translate-c
// c_frontend=aro,clang
//
// pub const union_Foo = extern union {
// x: c_short align(1),
// y: f64 align(1),
// };
//
// pub const Foo = union_Foo;

View File

@ -0,0 +1,15 @@
struct Foo {
unsigned int: 1;
};
struct Bar {
struct Foo *foo;
};
// translate-c
// c_frontend=clang
//
// pub const struct_Foo = opaque {};
//
// pub const struct_Bar = extern struct {
// foo: ?*struct_Foo = @import("std").mem.zeroes(?*struct_Foo),
// };

View File

@ -0,0 +1,25 @@
struct Foo {
int x;
int y;
};
enum Bar {
BarA,
BarB,
};
void func(struct Foo *a, enum Bar **b);
// translate-c
// c_frontend=clang
// target=x86_64-linux,x86_64-macos
//
// pub const struct_Foo = extern struct {
// x: c_int = @import("std").mem.zeroes(c_int),
// y: c_int = @import("std").mem.zeroes(c_int),
// };
// pub const BarA: c_int = 0;
// pub const BarB: c_int = 1;
// pub const enum_Bar = c_uint;
// pub extern fn func(a: [*c]struct_Foo, b: [*c][*c]enum_Bar) void;
//
// pub const Foo = struct_Foo;
// pub const Bar = enum_Bar;

View File

@ -0,0 +1,25 @@
struct Foo {
int x;
int y;
};
enum Bar {
BarA,
BarB,
};
void func(struct Foo *a, enum Bar **b);
// translate-c
// c_frontend=clang
// target=x86_64-windows-msvc
//
// pub const struct_Foo = extern struct {
// x: c_int = @import("std").mem.zeroes(c_int),
// y: c_int = @import("std").mem.zeroes(c_int),
// };
// pub const BarA: c_int = 0;
// pub const BarB: c_int = 1;
// pub const enum_Bar = c_int;
// pub extern fn func(a: [*c]struct_Foo, b: [*c][*c]enum_Bar) void;
//
// pub const Foo = struct_Foo;
// pub const Bar = enum_Bar;

View File

@ -0,0 +1,49 @@
void foo() {
struct Foo {
int A;
int B;
int C;
};
struct Foo a = {0};
{
struct Foo {
int A;
int B;
int C;
};
struct Foo a = {0};
}
}
// translate-c
// c_frontend=clang
//
// pub export fn foo() void {
// const struct_Foo = extern struct {
// A: c_int = @import("std").mem.zeroes(c_int),
// B: c_int = @import("std").mem.zeroes(c_int),
// C: c_int = @import("std").mem.zeroes(c_int),
// };
// _ = &struct_Foo;
// var a: struct_Foo = struct_Foo{
// .A = @as(c_int, 0),
// .B = 0,
// .C = 0,
// };
// _ = &a;
// {
// const struct_Foo_1 = extern struct {
// A: c_int = @import("std").mem.zeroes(c_int),
// B: c_int = @import("std").mem.zeroes(c_int),
// C: c_int = @import("std").mem.zeroes(c_int),
// };
// _ = &struct_Foo_1;
// var a_2: struct_Foo_1 = struct_Foo_1{
// .A = @as(c_int, 0),
// .B = 0,
// .C = 0,
// };
// _ = &a_2;
// }
// }

View File

@ -0,0 +1,12 @@
struct Foo {
int x;
};
// translate-c
// c_frontend=aro,clang
//
// const struct_Foo = extern struct {
// x: c_int = @import("std").mem.zeroes(c_int),
// };
//
// pub const Foo = struct_Foo;

View File

@ -0,0 +1,12 @@
union Foo {
int x;
};
// translate-c
// c_frontend=aro,clang
//
// pub const union_Foo = extern union {
// x: c_int,
// };
//
// pub const Foo = union_Foo;

View File

@ -0,0 +1,24 @@
struct Foo {
int a;
struct Bar {
int a;
} b;
} a = {};
#define PTR void *
// translate-c
// c_frontend=clang
//
// pub const struct_Bar_1 = extern struct {
// a: c_int = @import("std").mem.zeroes(c_int),
// };
// pub const struct_Foo = extern struct {
// a: c_int = @import("std").mem.zeroes(c_int),
// b: struct_Bar_1 = @import("std").mem.zeroes(struct_Bar_1),
// };
// pub export var a: struct_Foo = struct_Foo{
// .a = 0,
// .b = @import("std").mem.zeroes(struct_Bar_1),
// };
//
// pub const PTR = ?*anyopaque;

View File

@ -0,0 +1,10 @@
struct foo {
__attribute__((aligned(4))) short bar;
};
// translate-c
// c_frontend=aro,clang
//
// pub const struct_foo = extern struct {
// bar: c_short align(4) = @import("std").mem.zeroes(c_short),
// };

View File

@ -0,0 +1,35 @@
// The aligned attribute cannot decrease the alignment of a field. The packed attribute is required
// for decreasing the alignment. gcc and clang will compile these structs without error
// (and possibly without warning), but checking the alignment will reveal a different value than
// what was requested. This is consistent with the gcc documentation on type attributes.
//
// This test is currently broken for the clang frontend. See issue #19307.
struct foo {
__attribute__((aligned(1)))int x;
};
struct bar {
__attribute__((aligned(2)))float y;
};
struct baz {
__attribute__((aligned(4)))double z;
};
// translate-c
// c_frontend=aro
// target=x86_64-linux
//
// pub const struct_foo = extern struct {
// x: c_int = @import("std").mem.zeroes(c_int),
// };
//
// pub const struct_bar = extern struct {
// y: f32 = @import("std").mem.zeroes(f32),
// };
//
// pub const struct_baz = extern struct {
// z: f64 = @import("std").mem.zeroes(f64),
// };
//

View File

@ -0,0 +1,19 @@
// When clang uses the <arch>-windows-none, triple it behaves as MSVC and
// interprets the inner `struct Bar` as an anonymous structure
struct Foo {
struct Bar{
int b;
};
struct Bar c;
};
// translate-c
// c_frontend=aro,clang
// target=x86_64-linux-gnu
//
// pub const struct_Bar_1 = extern struct {
// b: c_int = @import("std").mem.zeroes(c_int),
// };
// pub const struct_Foo = extern struct {
// c: struct_Bar_1 = @import("std").mem.zeroes(struct_Bar_1),
// };

View File

@ -0,0 +1,22 @@
union { int x; char c[4]; }
ua = {1},
ub = {.c={'a','b','b','a'}};
// translate-c
// c_frontend=clang
//
// const union_unnamed_1 = extern union {
// x: c_int,
// c: [4]u8,
// };
// pub export var ua: union_unnamed_1 = union_unnamed_1{
// .x = @as(c_int, 1),
// };
// pub export var ub: union_unnamed_1 = union_unnamed_1{
// .c = [4]u8{
// 'a',
// 'b',
// 'b',
// 'a',
// },
// };

View File

@ -0,0 +1,37 @@
struct A;
union B;
enum C;
struct A {
short x;
double y;
};
union B {
short x;
double y;
};
struct Foo {
struct A a;
union B b;
};
// translate-c
// c_frontend=aro,clang
//
// pub const struct_A = extern struct {
// x: c_short = @import("std").mem.zeroes(c_short),
// y: f64 = @import("std").mem.zeroes(f64),
// };
//
// pub const union_B = extern union {
// x: c_short,
// y: f64,
// };
//
// pub const struct_Foo = extern struct {
// a: struct_A = @import("std").mem.zeroes(struct_A),
// b: union_B = @import("std").mem.zeroes(union_B),
// };

View File

@ -0,0 +1,22 @@
struct a {
struct { int x; };
};
struct b {
struct { int y; };
};
// translate-c
// c_frontend=aro,clang
//
// const struct_unnamed_1 = extern struct {
// x: c_int = @import("std").mem.zeroes(c_int),
// };
// pub const struct_a = extern struct {
// unnamed_0: struct_unnamed_1 = @import("std").mem.zeroes(struct_unnamed_1),
// };
// const struct_unnamed_2 = extern struct {
// y: c_int = @import("std").mem.zeroes(c_int),
// };
// pub const struct_b = extern struct {
// unnamed_0: struct_unnamed_2 = @import("std").mem.zeroes(struct_unnamed_2),
// };

View File

@ -0,0 +1,18 @@
struct __attribute__((packed)) foo {
int x;
struct {};
float y;
union {};
};
// translate-c
// c_frontend=aro
//
// const struct_unnamed_1 = extern struct {};
// const union_unnamed_2 = extern union {};
// pub const struct_foo = extern struct {
// x: c_int align(1) = @import("std").mem.zeroes(c_int),
// unnamed_0: struct_unnamed_1 align(1) = @import("std").mem.zeroes(struct_unnamed_1),
// y: f32 align(1) = @import("std").mem.zeroes(f32),
// unnamed_1: union_unnamed_2 align(1) = @import("std").mem.zeroes(union_unnamed_2),
// };

View File

@ -0,0 +1,12 @@
struct comptime {
int defer;
};
// translate-c
// c_frontend=aro,clang
//
// pub const struct_comptime = extern struct {
// @"defer": c_int = @import("std").mem.zeroes(c_int),
// };
//
// pub const @"comptime" = struct_comptime;

View File

@ -74,24 +74,6 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
\\pub extern fn main() c_int;
});
cases.add("field access is grouped if necessary",
\\unsigned long foo(unsigned long x) {
\\ return ((union{unsigned long _x}){x})._x;
\\}
, &[_][]const u8{
\\pub export fn foo(arg_x: c_ulong) c_ulong {
\\ var x = arg_x;
\\ _ = &x;
\\ const union_unnamed_1 = extern union {
\\ _x: c_ulong,
\\ };
\\ _ = &union_unnamed_1;
\\ return (union_unnamed_1{
\\ ._x = x,
\\ })._x;
\\}
});
cases.add("unnamed child types of typedef receive typedef's name",
\\typedef enum {
\\ FooA,
@ -149,78 +131,6 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
\\}
});
cases.add("struct in struct init to zero",
\\struct Foo {
\\ int a;
\\ struct Bar {
\\ int a;
\\ } b;
\\} a = {};
\\#define PTR void *
, &[_][]const u8{
\\pub const struct_Bar_1 = extern struct {
\\ a: c_int = @import("std").mem.zeroes(c_int),
\\};
\\pub const struct_Foo = extern struct {
\\ a: c_int = @import("std").mem.zeroes(c_int),
\\ b: struct_Bar_1 = @import("std").mem.zeroes(struct_Bar_1),
\\};
\\pub export var a: struct_Foo = struct_Foo{
\\ .a = 0,
\\ .b = @import("std").mem.zeroes(struct_Bar_1),
\\};
,
\\pub const PTR = ?*anyopaque;
});
cases.add("scoped record",
\\void foo() {
\\ struct Foo {
\\ int A;
\\ int B;
\\ int C;
\\ };
\\ struct Foo a = {0};
\\ {
\\ struct Foo {
\\ int A;
\\ int B;
\\ int C;
\\ };
\\ struct Foo a = {0};
\\ }
\\}
, &[_][]const u8{
\\pub export fn foo() void {
\\ const struct_Foo = extern struct {
\\ A: c_int = @import("std").mem.zeroes(c_int),
\\ B: c_int = @import("std").mem.zeroes(c_int),
\\ C: c_int = @import("std").mem.zeroes(c_int),
\\ };
\\ _ = &struct_Foo;
\\ var a: struct_Foo = struct_Foo{
\\ .A = @as(c_int, 0),
\\ .B = 0,
\\ .C = 0,
\\ };
\\ _ = &a;
\\ {
\\ const struct_Foo_1 = extern struct {
\\ A: c_int = @import("std").mem.zeroes(c_int),
\\ B: c_int = @import("std").mem.zeroes(c_int),
\\ C: c_int = @import("std").mem.zeroes(c_int),
\\ };
\\ _ = &struct_Foo_1;
\\ var a_2: struct_Foo_1 = struct_Foo_1{
\\ .A = @as(c_int, 0),
\\ .B = 0,
\\ .C = 0,
\\ };
\\ _ = &a_2;
\\ }
\\}
});
cases.add("scoped typedef",
\\void foo() {
\\ typedef union {
@ -466,16 +376,6 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
\\pub const BAR = (@as(c_int, 1) != 0) and (@as(c_int, 2) > @as(c_int, 4));
});
cases.add("struct with aligned fields",
\\struct foo {
\\ __attribute__((aligned(1))) short bar;
\\};
, &[_][]const u8{
\\pub const struct_foo = extern struct {
\\ bar: c_short align(1) = @import("std").mem.zeroes(c_short),
\\};
});
cases.add("struct with flexible array",
\\struct foo { int x; int y[]; };
\\struct bar { int x; int y[0]; };
@ -661,28 +561,6 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
\\}
});
cases.add("union initializer",
\\union { int x; char c[4]; }
\\ ua = {1},
\\ ub = {.c={'a','b','b','a'}};
, &[_][]const u8{
\\const union_unnamed_1 = extern union {
\\ x: c_int,
\\ c: [4]u8,
\\};
\\pub export var ua: union_unnamed_1 = union_unnamed_1{
\\ .x = @as(c_int, 1),
\\};
\\pub export var ub: union_unnamed_1 = union_unnamed_1{
\\ .c = [4]u8{
\\ 'a',
\\ 'b',
\\ 'b',
\\ 'a',
\\ },
\\};
});
cases.add("struct initializer - simple",
\\typedef struct { int x; } foo;
\\struct {double x,y,z;} s0 = {1.2, 1.3};
@ -992,21 +870,6 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
\\};
});
cases.add("pointer to struct demoted to opaque due to bit fields",
\\struct Foo {
\\ unsigned int: 1;
\\};
\\struct Bar {
\\ struct Foo *foo;
\\};
, &[_][]const u8{
\\pub const struct_Foo = opaque {};
,
\\pub const struct_Bar = extern struct {
\\ foo: ?*struct_Foo = @import("std").mem.zeroes(?*struct_Foo),
\\};
});
cases.add("macro with left shift",
\\#define REDISMODULE_READ (1<<0)
, &[_][]const u8{
@ -1022,45 +885,6 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
\\pub const FLASH_BANK_SIZE = FLASH_SIZE >> @as(c_int, 1);
});
cases.add("double define struct",
\\typedef struct Bar Bar;
\\typedef struct Foo Foo;
\\
\\struct Foo {
\\ Foo *a;
\\};
\\
\\struct Bar {
\\ Foo *a;
\\};
, &[_][]const u8{
\\pub const struct_Foo = extern struct {
\\ a: [*c]Foo = @import("std").mem.zeroes([*c]Foo),
\\};
,
\\pub const Foo = struct_Foo;
,
\\pub const struct_Bar = extern struct {
\\ a: [*c]Foo = @import("std").mem.zeroes([*c]Foo),
\\};
,
\\pub const Bar = struct_Bar;
});
cases.add("simple struct",
\\struct Foo {
\\ int x;
\\ char *y;
\\};
, &[_][]const u8{
\\const struct_Foo = extern struct {
\\ x: c_int = @import("std").mem.zeroes(c_int),
\\ y: [*c]u8 = @import("std").mem.zeroes([*c]u8),
\\};
,
\\pub const Foo = struct_Foo;
});
cases.add("self referential struct with function pointer",
\\struct Foo {
\\ void (*derp)(struct Foo *foo);
@ -1099,44 +923,12 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
\\pub const THING2 = THING1;
});
cases.add("circular struct definitions",
\\struct Bar;
\\
\\struct Foo {
\\ struct Bar *next;
\\};
\\
\\struct Bar {
\\ struct Foo *next;
\\};
, &[_][]const u8{
\\pub const struct_Bar = extern struct {
\\ next: [*c]struct_Foo = @import("std").mem.zeroes([*c]struct_Foo),
\\};
,
\\pub const struct_Foo = extern struct {
\\ next: [*c]struct_Bar = @import("std").mem.zeroes([*c]struct_Bar),
\\};
});
cases.add("#define string",
\\#define foo "a string"
, &[_][]const u8{
\\pub const foo = "a string";
});
cases.add("zig keywords in C code",
\\struct comptime {
\\ int defer;
\\};
, &[_][]const u8{
\\pub const struct_comptime = extern struct {
\\ @"defer": c_int = @import("std").mem.zeroes(c_int),
\\};
,
\\pub const @"comptime" = struct_comptime;
});
cases.add("macro with parens around negative number",
\\#define LUA_GLOBALSINDEX (-10002)
, &[_][]const u8{
@ -1393,88 +1185,6 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
\\}
});
cases.add("simple union",
\\union Foo {
\\ int x;
\\ double y;
\\};
, &[_][]const u8{
\\pub const union_Foo = extern union {
\\ x: c_int,
\\ y: f64,
\\};
,
\\pub const Foo = union_Foo;
});
cases.add("packed union - simple",
\\union Foo {
\\ char x;
\\ double y;
\\} __attribute__((packed));
, &[_][]const u8{
\\pub const union_Foo = extern union {
\\ x: u8 align(1),
\\ y: f64 align(1),
\\};
,
\\pub const Foo = union_Foo;
});
cases.add("packed union - nested unpacked",
\\union Foo{
\\ char x;
\\ double y;
\\ struct {
\\ char a;
\\ int b;
\\ } z;
\\} __attribute__((packed));
, &[_][]const u8{
// NOTE: The nested struct is *not* packed/aligned,
// even though the parent struct is
// this is consistent with GCC docs
\\const struct_unnamed_1 = extern struct {
\\ a: u8 = @import("std").mem.zeroes(u8),
\\ b: c_int = @import("std").mem.zeroes(c_int),
\\};
,
\\pub const union_Foo = extern union {
\\ x: u8 align(1),
\\ y: f64 align(1),
\\ z: struct_unnamed_1 align(1),
\\};
,
\\pub const Foo = union_Foo;
});
cases.add("packed union - nested packed",
\\union Foo{
\\ char x;
\\ double y;
\\ struct {
\\ char a;
\\ int b;
\\ } __attribute__((packed)) z;
\\} __attribute__((packed));
, &[_][]const u8{
// in order for the nested struct to be packed, it must
// have an independent packed declaration on
// the nested type (see GCC docs for details)
\\const struct_unnamed_1 = extern struct {
\\ a: u8 align(1) = @import("std").mem.zeroes(u8),
\\ b: c_int align(1) = @import("std").mem.zeroes(c_int),
\\};
,
\\pub const union_Foo = extern union {
\\ x: u8 align(1),
\\ y: f64 align(1),
\\ z: struct_unnamed_1 align(1),
\\};
,
\\pub const Foo = union_Foo;
});
cases.add("string literal",
\\const char *foo(void) {
\\ return "bar";
@ -2395,27 +2105,6 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
\\ }
\\}
});
if (builtin.os.tag != .windows) {
// When clang uses the <arch>-windows-none triple it behaves as MSVC and
// interprets the inner `struct Bar` as an anonymous structure
cases.add("type referenced struct",
\\struct Foo {
\\ struct Bar{
\\ int b;
\\ };
\\ struct Bar c;
\\};
, &[_][]const u8{
\\pub const struct_Bar_1 = extern struct {
\\ b: c_int = @import("std").mem.zeroes(c_int),
\\};
\\pub const struct_Foo = extern struct {
\\ c: struct_Bar_1 = @import("std").mem.zeroes(struct_Bar_1),
\\};
});
}
cases.add("undefined array global",
\\int array[100] = {};
, &[_][]const u8{
@ -2634,32 +2323,6 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
\\pub const Foo = enum_Foo;
});
cases.add("qualified struct and enum",
\\struct Foo {
\\ int x;
\\ int y;
\\};
\\enum Bar {
\\ BarA,
\\ BarB,
\\};
\\void func(struct Foo *a, enum Bar **b);
, &[_][]const u8{
\\pub const struct_Foo = extern struct {
\\ x: c_int = @import("std").mem.zeroes(c_int),
\\ y: c_int = @import("std").mem.zeroes(c_int),
\\};
\\pub const BarA: c_int = 0;
\\pub const BarB: c_int = 1;
\\pub const enum_Bar =
++ " " ++ default_enum_type ++
\\;
\\pub extern fn func(a: [*c]struct_Foo, b: [*c][*c]enum_Bar) void;
,
\\pub const Foo = struct_Foo;
\\pub const Bar = enum_Bar;
});
cases.add("bitwise binary operators, simpler parens",
\\int max(int a, int b) {
\\ return (a & b) ^ (a | b);
@ -3756,24 +3419,6 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
});
}
cases.add("unnamed fields have predictable names",
\\struct a {
\\ struct {};
\\};
\\struct b {
\\ struct {};
\\};
, &[_][]const u8{
\\const struct_unnamed_1 = extern struct {};
\\pub const struct_a = extern struct {
\\ unnamed_0: struct_unnamed_1 = @import("std").mem.zeroes(struct_unnamed_1),
\\};
\\const struct_unnamed_2 = extern struct {};
\\pub const struct_b = extern struct {
\\ unnamed_0: struct_unnamed_2 = @import("std").mem.zeroes(struct_unnamed_2),
\\};
});
cases.add("integer literal promotion",
\\#define GUARANTEED_TO_FIT_1 1024
\\#define GUARANTEED_TO_FIT_2 10241024L
@ -4283,21 +3928,6 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
\\pub const FOO = @compileError("unable to translate macro: untranslatable usage of arg `x`");
});
cases.add("global struct whose default name conflicts with global is mangled",
\\struct foo {
\\ int x;
\\};
\\const char *struct_foo = "hello world";
, &[_][]const u8{
\\pub const struct_foo_1 = extern struct {
\\ x: c_int = @import("std").mem.zeroes(c_int),
\\};
,
\\pub const foo = struct_foo_1;
,
\\pub export var struct_foo: [*c]const u8 = "hello world";
});
cases.add("unsupport declare statement at the last of a compound statement which belongs to a statement expr",
\\void somefunc(void) {
\\ int y;