Skip to content

Commit 4a7df9c

Browse files
committed
translate-c: Implement flexible arrays
Fixes #8759
1 parent 43a09f7 commit 4a7df9c

File tree

8 files changed

+283
-21
lines changed

8 files changed

+283
-21
lines changed

lib/std/meta.zig

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1355,3 +1355,34 @@ test "isError" {
13551355
try std.testing.expect(isError(math.absInt(@as(i8, -128))));
13561356
try std.testing.expect(!isError(math.absInt(@as(i8, -127))));
13571357
}
1358+
1359+
/// This function is for translate-c and is not intended for general use.
1360+
/// Constructs a [*c] pointer with the const and volatile annotations
1361+
/// from SelfType for pointing to a C flexible array of ElementType.
1362+
pub fn FlexibleArrayType(comptime SelfType: type, ElementType: type) type {
1363+
switch (@typeInfo(SelfType)) {
1364+
.Pointer => |ptr| {
1365+
return @Type(TypeInfo{ .Pointer = .{
1366+
.size = .C,
1367+
.is_const = ptr.is_const,
1368+
.is_volatile = ptr.is_volatile,
1369+
.alignment = @alignOf(ElementType),
1370+
.child = ElementType,
1371+
.is_allowzero = true,
1372+
.sentinel = null,
1373+
} });
1374+
},
1375+
else => |info| @compileError("Invalid self type \"" ++ @tagName(info) ++ "\" for flexible array getter: " ++ @typeName(SelfType)),
1376+
}
1377+
}
1378+
1379+
test "Flexible Array Type" {
1380+
const Container = extern struct {
1381+
size: usize,
1382+
};
1383+
1384+
try testing.expectEqual(FlexibleArrayType(*Container, c_int), [*c]c_int);
1385+
try testing.expectEqual(FlexibleArrayType(*const Container, c_int), [*c]const c_int);
1386+
try testing.expectEqual(FlexibleArrayType(*volatile Container, c_int), [*c]volatile c_int);
1387+
try testing.expectEqual(FlexibleArrayType(*const volatile Container, c_int), [*c]const volatile c_int);
1388+
}

src/clang.zig

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,14 @@ pub const ArrayType = opaque {
183183
extern fn ZigClangArrayType_getElementType(*const ArrayType) QualType;
184184
};
185185

186+
pub const ASTRecordLayout = opaque {
187+
pub const getFieldOffset = ZigClangASTRecordLayout_getFieldOffset;
188+
extern fn ZigClangASTRecordLayout_getFieldOffset(*const ASTRecordLayout, c_uint) u64;
189+
190+
pub const getAlignment = ZigClangASTRecordLayout_getAlignment;
191+
extern fn ZigClangASTRecordLayout_getAlignment(*const ASTRecordLayout) i64;
192+
};
193+
186194
pub const AttributedType = opaque {
187195
pub const getEquivalentType = ZigClangAttributedType_getEquivalentType;
188196
extern fn ZigClangAttributedType_getEquivalentType(*const AttributedType) QualType;
@@ -461,6 +469,9 @@ pub const FieldDecl = opaque {
461469

462470
pub const getParent = ZigClangFieldDecl_getParent;
463471
extern fn ZigClangFieldDecl_getParent(*const FieldDecl) ?*const RecordDecl;
472+
473+
pub const getFieldIndex = ZigClangFieldDecl_getFieldIndex;
474+
extern fn ZigClangFieldDecl_getFieldIndex(*const FieldDecl) c_uint;
464475
};
465476

466477
pub const FileID = opaque {};
@@ -752,6 +763,9 @@ pub const RecordDecl = opaque {
752763
pub const getLocation = ZigClangRecordDecl_getLocation;
753764
extern fn ZigClangRecordDecl_getLocation(*const RecordDecl) SourceLocation;
754765

766+
pub const getASTRecordLayout = ZigClangRecordDecl_getASTRecordLayout;
767+
extern fn ZigClangRecordDecl_getASTRecordLayout(*const RecordDecl, *const ASTContext) *const ASTRecordLayout;
768+
755769
pub const field_begin = ZigClangRecordDecl_field_begin;
756770
extern fn ZigClangRecordDecl_field_begin(*const RecordDecl) field_iterator;
757771

src/translate_c.zig

Lines changed: 150 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -819,6 +819,111 @@ fn transTypeDef(c: *Context, scope: *Scope, typedef_decl: *const clang.TypedefNa
819819
}
820820
}
821821

822+
/// Build a getter function for a flexible array member at the end of a C struct
823+
/// e.g. `T items[]` or `T items[0]`. The generated function returns a [*c] pointer
824+
/// to the flexible array with the correct const and volatile qualifiers
825+
fn buildFlexibleArrayFn(
826+
c: *Context,
827+
scope: *Scope,
828+
layout: *const clang.ASTRecordLayout,
829+
field_name: []const u8,
830+
field_decl: *const clang.FieldDecl,
831+
) TypeError!Node {
832+
const field_qt = field_decl.getType();
833+
834+
const u8_type = try Tag.type.create(c.arena, "u8");
835+
const self_param_name = "self";
836+
const self_param = try Tag.identifier.create(c.arena, self_param_name);
837+
const self_type = try Tag.typeof.create(c.arena, self_param);
838+
839+
const fn_params = try c.arena.alloc(ast.Payload.Param, 1);
840+
841+
fn_params[0] = .{
842+
.name = self_param_name,
843+
.type = Tag.@"anytype".init(),
844+
.is_noalias = false,
845+
};
846+
847+
const array_type = @ptrCast(*const clang.ArrayType, field_qt.getTypePtr());
848+
const element_qt = array_type.getElementType();
849+
const element_type = try transQualType(c, scope, element_qt, field_decl.getLocation());
850+
851+
var block_scope = try Scope.Block.init(c, scope, false);
852+
defer block_scope.deinit();
853+
854+
const intermediate_type_name = try block_scope.makeMangledName(c, "Intermediate");
855+
const intermediate_type = try Tag.std_meta_flexible_array_type.create(c.arena, .{ .lhs = self_type, .rhs = u8_type });
856+
const intermediate_type_decl = try Tag.var_simple.create(c.arena, .{
857+
.name = intermediate_type_name,
858+
.init = intermediate_type,
859+
});
860+
try block_scope.statements.append(intermediate_type_decl);
861+
const intermediate_type_ident = try Tag.identifier.create(c.arena, intermediate_type_name);
862+
863+
const return_type_name = try block_scope.makeMangledName(c, "ReturnType");
864+
const return_type = try Tag.std_meta_flexible_array_type.create(c.arena, .{ .lhs = self_type, .rhs = element_type });
865+
const return_type_decl = try Tag.var_simple.create(c.arena, .{
866+
.name = return_type_name,
867+
.init = return_type,
868+
});
869+
try block_scope.statements.append(return_type_decl);
870+
const return_type_ident = try Tag.identifier.create(c.arena, return_type_name);
871+
872+
const field_index = field_decl.getFieldIndex();
873+
const bit_offset = layout.getFieldOffset(field_index); // this is a target-specific constant based on the struct layout
874+
const byte_offset = bit_offset / 8;
875+
876+
const casted_self = try Tag.ptr_cast.create(c.arena, .{
877+
.lhs = intermediate_type_ident,
878+
.rhs = self_param,
879+
});
880+
const field_offset = try transCreateNodeNumber(c, byte_offset, .int);
881+
const field_ptr = try Tag.add.create(c.arena, .{ .lhs = casted_self, .rhs = field_offset });
882+
883+
const alignment = try Tag.alignof.create(c.arena, element_type);
884+
885+
const ptr_val = try Tag.align_cast.create(c.arena, .{ .lhs = alignment, .rhs = field_ptr });
886+
const ptr_cast = try Tag.ptr_cast.create(c.arena, .{ .lhs = return_type_ident, .rhs = ptr_val });
887+
const return_stmt = try Tag.@"return".create(c.arena, ptr_cast);
888+
try block_scope.statements.append(return_stmt);
889+
890+
const payload = try c.arena.create(ast.Payload.Func);
891+
payload.* = .{
892+
.base = .{ .tag = .func },
893+
.data = .{
894+
.is_pub = true,
895+
.is_extern = false,
896+
.is_export = false,
897+
.is_var_args = false,
898+
.name = field_name,
899+
.linksection_string = null,
900+
.explicit_callconv = null,
901+
.params = fn_params,
902+
.return_type = return_type,
903+
.body = try block_scope.complete(c),
904+
.alignment = null,
905+
},
906+
};
907+
return Node.initPayload(&payload.base);
908+
}
909+
910+
fn isFlexibleArrayFieldDecl(c: *Context, field_decl: *const clang.FieldDecl) bool {
911+
return qualTypeCanon(field_decl.getType()).isIncompleteOrZeroLengthArrayType(c.clang_context);
912+
}
913+
914+
/// clang's RecordDecl::hasFlexibleArrayMember is not suitable for determining
915+
/// this because it returns false for a record that ends with a zero-length
916+
/// array, but we consider those to be flexible arrays
917+
fn hasFlexibleArrayField(c: *Context, record_def: *const clang.RecordDecl) bool {
918+
var it = record_def.field_begin();
919+
const end_it = record_def.field_end();
920+
while (it.neq(end_it)) : (it = it.next()) {
921+
const field_decl = it.deref();
922+
if (isFlexibleArrayFieldDecl(c, field_decl)) return true;
923+
}
924+
return false;
925+
}
926+
822927
fn transRecordDecl(c: *Context, scope: *Scope, record_decl: *const clang.RecordDecl) Error!void {
823928
if (c.decl_table.get(@ptrToInt(record_decl.getCanonicalDecl()))) |name|
824929
return; // Avoid processing this decl twice
@@ -868,9 +973,16 @@ fn transRecordDecl(c: *Context, scope: *Scope, record_decl: *const clang.RecordD
868973
var fields = std.ArrayList(ast.Payload.Record.Field).init(c.gpa);
869974
defer fields.deinit();
870975

976+
var functions = std.ArrayList(Node).init(c.gpa);
977+
defer functions.deinit();
978+
979+
const has_flexible_array = hasFlexibleArrayField(c, record_def);
871980
var unnamed_field_count: u32 = 0;
872981
var it = record_def.field_begin();
873982
const end_it = record_def.field_end();
983+
const layout = record_def.getASTRecordLayout(c.clang_context);
984+
const record_alignment = layout.getAlignment();
985+
874986
while (it.neq(end_it)) : (it = it.next()) {
875987
const field_decl = it.deref();
876988
const field_loc = field_decl.getLocation();
@@ -882,12 +994,6 @@ fn transRecordDecl(c: *Context, scope: *Scope, record_decl: *const clang.RecordD
882994
break :blk Tag.opaque_literal.init();
883995
}
884996

885-
if (qualTypeCanon(field_qt).isIncompleteOrZeroLengthArrayType(c.clang_context)) {
886-
try c.opaque_demotes.put(c.gpa, @ptrToInt(record_decl.getCanonicalDecl()), {});
887-
try warn(c, scope, field_loc, "{s} demoted to opaque type - has variable length array", .{container_kind_name});
888-
break :blk Tag.opaque_literal.init();
889-
}
890-
891997
var is_anon = false;
892998
var field_name = try c.str(@ptrCast(*const clang.NamedDecl, field_decl).getName_bytes_begin());
893999
if (field_decl.isAnonymousStructOrUnion() or field_name.len == 0) {
@@ -896,6 +1002,18 @@ fn transRecordDecl(c: *Context, scope: *Scope, record_decl: *const clang.RecordD
8961002
unnamed_field_count += 1;
8971003
is_anon = true;
8981004
}
1005+
if (isFlexibleArrayFieldDecl(c, field_decl)) {
1006+
const flexible_array_fn = buildFlexibleArrayFn(c, scope, layout, field_name, field_decl) catch |err| switch (err) {
1007+
error.UnsupportedType => {
1008+
try c.opaque_demotes.put(c.gpa, @ptrToInt(record_decl.getCanonicalDecl()), {});
1009+
try warn(c, scope, record_loc, "{s} demoted to opaque type - unable to translate type of flexible array field {s}", .{ container_kind_name, field_name });
1010+
break :blk Tag.opaque_literal.init();
1011+
},
1012+
else => |e| return e,
1013+
};
1014+
try functions.append(flexible_array_fn);
1015+
continue;
1016+
}
8991017
const field_type = transQualType(c, scope, field_qt, field_loc) catch |err| switch (err) {
9001018
error.UnsupportedType => {
9011019
try c.opaque_demotes.put(c.gpa, @ptrToInt(record_decl.getCanonicalDecl()), {});
@@ -905,7 +1023,10 @@ fn transRecordDecl(c: *Context, scope: *Scope, record_decl: *const clang.RecordD
9051023
else => |e| return e,
9061024
};
9071025

908-
const alignment = zigAlignment(field_decl.getAlignedAttribute(c.clang_context));
1026+
const alignment = if (has_flexible_array and field_decl.getFieldIndex() == 0)
1027+
@intCast(c_uint, record_alignment)
1028+
else
1029+
zigAlignment(field_decl.getAlignedAttribute(c.clang_context));
9091030

9101031
if (is_anon) {
9111032
try c.decl_table.putNoClobber(c.gpa, @ptrToInt(field_decl.getCanonicalDecl()), field_name);
@@ -924,6 +1045,7 @@ fn transRecordDecl(c: *Context, scope: *Scope, record_decl: *const clang.RecordD
9241045
.data = .{
9251046
.is_packed = is_packed,
9261047
.fields = try c.arena.dupe(ast.Payload.Record.Field, fields.items),
1048+
.functions = try c.arena.dupe(Node, functions.items),
9271049
},
9281050
};
9291051
break :blk Node.initPayload(&record_payload.base);
@@ -1737,12 +1859,12 @@ fn transImplicitCastExpr(
17371859
return maybeSuppressResult(c, scope, result_used, sub_expr_node);
17381860
},
17391861
.ArrayToPointerDecay => {
1740-
if (exprIsNarrowStringLiteral(sub_expr)) {
1741-
const sub_expr_node = try transExpr(c, scope, sub_expr, .used);
1862+
const sub_expr_node = try transExpr(c, scope, sub_expr, .used);
1863+
if (exprIsNarrowStringLiteral(sub_expr) or exprIsFlexibleArrayRef(c, sub_expr)) {
17421864
return maybeSuppressResult(c, scope, result_used, sub_expr_node);
17431865
}
17441866

1745-
const addr = try Tag.address_of.create(c.arena, try transExpr(c, scope, sub_expr, .used));
1867+
const addr = try Tag.address_of.create(c.arena, sub_expr_node);
17461868
const casted = try transCPtrCast(c, scope, expr.getBeginLoc(), dest_type, src_type, addr);
17471869
return maybeSuppressResult(c, scope, result_used, casted);
17481870
},
@@ -1852,6 +1974,19 @@ fn exprIsNarrowStringLiteral(expr: *const clang.Expr) bool {
18521974
}
18531975
}
18541976

1977+
fn exprIsFlexibleArrayRef(c: *Context, expr: *const clang.Expr) bool {
1978+
if (expr.getStmtClass() == .MemberExprClass) {
1979+
const member_expr = @ptrCast(*const clang.MemberExpr, expr);
1980+
const member_decl = member_expr.getMemberDecl();
1981+
const decl_kind = @ptrCast(*const clang.Decl, member_decl).getKind();
1982+
if (decl_kind == .Field) {
1983+
const field_decl = @ptrCast(*const clang.FieldDecl, member_decl);
1984+
return isFlexibleArrayFieldDecl(c, field_decl);
1985+
}
1986+
}
1987+
return false;
1988+
}
1989+
18551990
fn isBoolRes(res: Node) bool {
18561991
switch (res.tag()) {
18571992
.@"or",
@@ -3056,7 +3191,6 @@ fn transStmtExpr(c: *Context, scope: *Scope, stmt: *const clang.StmtExpr, used:
30563191

30573192
fn transMemberExpr(c: *Context, scope: *Scope, stmt: *const clang.MemberExpr, result_used: ResultUsed) TransError!Node {
30583193
var container_node = try transExpr(c, scope, stmt.getBase(), .used);
3059-
30603194
if (stmt.isArrow()) {
30613195
container_node = try Tag.deref.create(c.arena, container_node);
30623196
}
@@ -3076,7 +3210,11 @@ fn transMemberExpr(c: *Context, scope: *Scope, stmt: *const clang.MemberExpr, re
30763210
const decl = @ptrCast(*const clang.NamedDecl, member_decl);
30773211
break :blk try c.str(decl.getName_bytes_begin());
30783212
};
3079-
const node = try Tag.field_access.create(c.arena, .{ .lhs = container_node, .field_name = name });
3213+
3214+
var node = try Tag.field_access.create(c.arena, .{ .lhs = container_node, .field_name = name });
3215+
if (exprIsFlexibleArrayRef(c, @ptrCast(*const clang.Expr, stmt))) {
3216+
node = try Tag.call.create(c.arena, .{ .lhs = node, .args = &.{} });
3217+
}
30803218
return maybeSuppressResult(c, scope, result_used, node);
30813219
}
30823220

src/translate_c/ast.zig

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,8 @@ pub const Node = extern union {
193193

194194
/// @import("std").meta.sizeof(operand)
195195
std_meta_sizeof,
196+
/// @import("std").meta.FlexibleArrayType(lhs, rhs)
197+
std_meta_flexible_array_type,
196198
/// @import("std").meta.shuffleVectorIndex(lhs, rhs)
197199
std_meta_shuffle_vector_index,
198200
/// @import("std").meta.Vector(lhs, rhs)
@@ -328,6 +330,7 @@ pub const Node = extern union {
328330
.align_cast,
329331
.array_access,
330332
.std_mem_zeroinit,
333+
.std_meta_flexible_array_type,
331334
.std_meta_shuffle_vector_index,
332335
.std_meta_vector,
333336
.ptr_cast,
@@ -567,6 +570,7 @@ pub const Payload = struct {
567570
data: struct {
568571
is_packed: bool,
569572
fields: []Field,
573+
functions: []Node,
570574
},
571575

572576
pub const Field = struct {
@@ -909,6 +913,11 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex {
909913
const import_node = try renderStdImport(c, "mem", "zeroInit");
910914
return renderCall(c, import_node, &.{ payload.lhs, payload.rhs });
911915
},
916+
.std_meta_flexible_array_type => {
917+
const payload = node.castTag(.std_meta_flexible_array_type).?.data;
918+
const import_node = try renderStdImport(c, "meta", "FlexibleArrayType");
919+
return renderCall(c, import_node, &.{ payload.lhs, payload.rhs });
920+
},
912921
.std_meta_shuffle_vector_index => {
913922
const payload = node.castTag(.std_meta_shuffle_vector_index).?.data;
914923
const import_node = try renderStdImport(c, "meta", "shuffleVectorIndex");
@@ -1992,7 +2001,10 @@ fn renderRecord(c: *Context, node: Node) !NodeIndex {
19922001
try c.addToken(.keyword_union, "union");
19932002

19942003
_ = try c.addToken(.l_brace, "{");
1995-
const members = try c.gpa.alloc(NodeIndex, std.math.max(payload.fields.len, 2));
2004+
2005+
const num_funcs = payload.functions.len;
2006+
const total_members = payload.fields.len + num_funcs;
2007+
const members = try c.gpa.alloc(NodeIndex, std.math.max(total_members, 2));
19962008
defer c.gpa.free(members);
19972009
members[0] = 0;
19982010
members[1] = 0;
@@ -2033,9 +2045,12 @@ fn renderRecord(c: *Context, node: Node) !NodeIndex {
20332045
});
20342046
_ = try c.addToken(.comma, ",");
20352047
}
2048+
for (payload.functions) |function, i| {
2049+
members[payload.fields.len + i] = try renderNode(c, function);
2050+
}
20362051
_ = try c.addToken(.r_brace, "}");
20372052

2038-
if (payload.fields.len == 0) {
2053+
if (total_members == 0) {
20392054
return c.addNode(.{
20402055
.tag = .container_decl_two,
20412056
.main_token = kind_tok,
@@ -2044,9 +2059,9 @@ fn renderRecord(c: *Context, node: Node) !NodeIndex {
20442059
.rhs = 0,
20452060
},
20462061
});
2047-
} else if (payload.fields.len <= 2) {
2062+
} else if (total_members <= 2) {
20482063
return c.addNode(.{
2049-
.tag = .container_decl_two_trailing,
2064+
.tag = if (num_funcs == 0) .container_decl_two_trailing else .container_decl_two,
20502065
.main_token = kind_tok,
20512066
.data = .{
20522067
.lhs = members[0],
@@ -2056,7 +2071,7 @@ fn renderRecord(c: *Context, node: Node) !NodeIndex {
20562071
} else {
20572072
const span = try c.listToSpan(members);
20582073
return c.addNode(.{
2059-
.tag = .container_decl_trailing,
2074+
.tag = if (num_funcs == 0) .container_decl_trailing else .container_decl,
20602075
.main_token = kind_tok,
20612076
.data = .{
20622077
.lhs = span.start,
@@ -2229,6 +2244,7 @@ fn renderNodeGrouped(c: *Context, node: Node) !NodeIndex {
22292244
.std_meta_promoteIntLiteral,
22302245
.std_meta_vector,
22312246
.std_meta_shuffle_vector_index,
2247+
.std_meta_flexible_array_type,
22322248
.std_mem_zeroinit,
22332249
.integer_literal,
22342250
.float_literal,

0 commit comments

Comments
 (0)