- Added special handling for translating C extern variables declared within scoped blocks

- Added test/cases/run_translated_c/extern_typedef_variables_in_functions.c to test for issue 19687
This commit is contained in:
Hayden Riddiford
2024-06-14 09:06:05 -07:00
committed by Veikka Tuominen
parent 88bb0fd288
commit 20563d8457
4 changed files with 161 additions and 12 deletions

View File

@@ -1277,6 +1277,12 @@ pub fn ScopeExtra(comptime ScopeExtraContext: type, comptime ScopeExtraType: typ
/// struct itself is given this name.
pub const static_inner_name = "static";
/// C extern variables declared within a block are wrapped in a block-local
/// struct. The struct is named ExternLocal_[variable_name], the Zig variable
/// within the struct itself is [variable_name] by neccessity since it's an
/// extern reference to an existing symbol.
pub const extern_inner_prepend = "ExternLocal";
pub fn init(c: *ScopeExtraContext, parent: *ScopeExtraScope, labeled: bool) !Block {
var blk = Block{
.base = .{
@@ -1356,6 +1362,24 @@ pub fn ScopeExtra(comptime ScopeExtraContext: type, comptime ScopeExtraType: typ
return scope.base.parent.?.getAlias(name);
}
/// Finds the (potentially) mangled struct name for a locally scoped extern variable given the original declaration name.
///
/// Block scoped extern declarations translate to:
/// const MangledStructName = struct {extern [qualifiers] original_extern_variable_name: [type]};
/// This finds MangledStructName given original_extern_variable_name for referencing correctly in transDeclRefExpr()
pub fn getLocalExternAlias(scope: *Block, name: []const u8) ?[]const u8 {
for (scope.statements.items) |node| {
if (node.tag() == .extern_local_var) {
const parent_node = node.castTag(.extern_local_var).?;
const init_node = parent_node.data.init.castTag(.var_decl).?;
if (std.mem.eql(u8, init_node.data.name, name)) {
return parent_node.data.name;
}
}
}
return null;
}
pub fn localContains(scope: *Block, name: []const u8) bool {
for (scope.variables.items) |p| {
if (std.mem.eql(u8, p.alias, name))
@@ -1451,6 +1475,16 @@ pub fn ScopeExtra(comptime ScopeExtraContext: type, comptime ScopeExtraType: typ
};
}
pub fn getLocalExternAlias(scope: *ScopeExtraScope, name: []const u8) ?[]const u8 {
return switch (scope.id) {
.block => ret: {
const block = @as(*Block, @fieldParentPtr("base", scope));
break :ret block.getLocalExternAlias(name);
},
.root, .loop, .do_loop, .condition => null,
};
}
pub fn contains(scope: *ScopeExtraScope, name: []const u8) bool {
return switch (scope.id) {
.root => @as(*Root, @fieldParentPtr("base", scope)).contains(name),

View File

@@ -55,6 +55,8 @@ pub const Node = extern union {
var_decl,
/// const name = struct { init }
static_local_var,
/// const ExternLocal_name = struct { init }
extern_local_var,
/// var name = init.*
mut_str,
func,
@@ -365,7 +367,7 @@ pub const Node = extern union {
.c_pointer, .single_pointer => Payload.Pointer,
.array_type, .null_sentinel_array_type => Payload.Array,
.arg_redecl, .alias, .fail_decl => Payload.ArgRedecl,
.var_simple, .pub_var_simple, .static_local_var, .mut_str => Payload.SimpleVarDecl,
.var_simple, .pub_var_simple, .static_local_var, .extern_local_var, .mut_str => Payload.SimpleVarDecl,
.enum_constant => Payload.EnumConstant,
.array_filler => Payload.ArrayFiller,
.pub_inline_fn => Payload.PubInlineFn,
@@ -1269,6 +1271,36 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex {
},
});
},
.extern_local_var => {
const payload = node.castTag(.extern_local_var).?.data;
const const_tok = try c.addToken(.keyword_const, "const");
_ = try c.addIdentifier(payload.name);
_ = try c.addToken(.equal, "=");
const kind_tok = try c.addToken(.keyword_struct, "struct");
_ = try c.addToken(.l_brace, "{");
const container_def = try c.addNode(.{
.tag = .container_decl_two_trailing,
.main_token = kind_tok,
.data = .{
.lhs = try renderNode(c, payload.init),
.rhs = 0,
},
});
_ = try c.addToken(.r_brace, "}");
_ = try c.addToken(.semicolon, ";");
return c.addNode(.{
.tag = .simple_var_decl,
.main_token = const_tok,
.data = .{
.lhs = 0,
.rhs = container_def,
},
});
},
.mut_str => {
const payload = node.castTag(.mut_str).?.data;
@@ -2292,7 +2324,7 @@ fn renderNullSentinelArrayType(c: *Context, len: usize, elem_type: Node) !NodeIn
fn addSemicolonIfNeeded(c: *Context, node: Node) !void {
switch (node.tag()) {
.warning => unreachable,
.var_decl, .var_simple, .arg_redecl, .alias, .block, .empty_block, .block_single, .@"switch", .static_local_var, .mut_str => {},
.var_decl, .var_simple, .arg_redecl, .alias, .block, .empty_block, .block_single, .@"switch", .static_local_var, .extern_local_var, .mut_str => {},
.while_true => {
const payload = node.castTag(.while_true).?.data;
return addSemicolonIfNotBlock(c, payload);
@@ -2388,6 +2420,7 @@ fn renderNodeGrouped(c: *Context, node: Node) !NodeIndex {
.shuffle,
.builtin_extern,
.static_local_var,
.extern_local_var,
.mut_str,
.macro_arithmetic,
=> {

View File

@@ -1734,6 +1734,48 @@ const ClangAlignment = struct {
}
};
/// Translate an "extern" variable that's been declared within a scoped block.
/// Similar to static local variables, this will be wrapped in a struct to work with Zig's syntax requirements.
///
/// Assumptions made:
/// - No need to mangle the actual NamedDecl, as by definition this MUST be the same name as the external symbol it's referencing
/// - It's not valid C to have an initializer with this type of declaration, so we can safely operate assuming no initializer
/// - No need to look for any cleanup attributes with getCleanupAttribute(), not relevant for this type of decl
fn transLocalExternStmt(c: *Context, scope: *Scope, var_decl: *const clang.VarDecl, block_scope: *Scope.Block) TransError!void {
const extern_var_name = try c.str(@as(*const clang.NamedDecl, @ptrCast(var_decl)).getName_bytes_begin());
// Special naming convention for local extern variable wrapper struct
const name = try std.fmt.allocPrint(c.arena, "{s}_{s}", .{ Scope.Block.extern_inner_prepend, extern_var_name });
// On the off chance there's already a variable in scope named "ExternLocal_[extern_var_name]"
const mangled_name = try block_scope.makeMangledName(c, name);
const qual_type = var_decl.getTypeSourceInfo_getType();
const is_const = qual_type.isConstQualified();
const loc = var_decl.getLocation();
const type_node = try transQualType(c, scope, qual_type, loc);
// Inner Node for the extern variable declaration
var node = try Tag.var_decl.create(c.arena, .{
.is_pub = false,
.is_const = is_const,
.is_extern = true,
.is_export = false,
.is_threadlocal = var_decl.getTLSKind() != .None, // TODO: Neccessary?
.linksection_string = null, // TODO: Neccessary?
.alignment = ClangAlignment.forVar(c, var_decl).zigAlignment(),
.name = extern_var_name,
.type = type_node,
.init = null,
});
// Outer Node for the wrapper struct
node = try Tag.extern_local_var.create(c.arena, .{ .name = mangled_name, .init = node });
try block_scope.statements.append(node);
try block_scope.discardVariable(c, mangled_name);
}
fn transDeclStmtOne(
c: *Context,
scope: *Scope,
@@ -1743,6 +1785,13 @@ fn transDeclStmtOne(
switch (decl.getKind()) {
.Var => {
const var_decl = @as(*const clang.VarDecl, @ptrCast(decl));
// Translation behavior for a block scope declared "extern" variable
// is enough of an outlier that it needs it's own function
if (var_decl.getStorageClass() == .Extern) {
return transLocalExternStmt(c, scope, var_decl, block_scope);
}
const decl_init = var_decl.getInit();
const loc = decl.getLocation();
@@ -1750,11 +1799,7 @@ fn transDeclStmtOne(
const name = try c.str(@as(*const clang.NamedDecl, @ptrCast(var_decl)).getName_bytes_begin());
const mangled_name = try block_scope.makeMangledName(c, name);
if (var_decl.getStorageClass() == .Extern) {
// This is actually a global variable, put it in the global scope and reference it.
// `_ = mangled_name;`
return visitVarDecl(c, var_decl, mangled_name);
} else if (qualTypeWasDemotedToOpaque(c, qual_type)) {
if (qualTypeWasDemotedToOpaque(c, qual_type)) {
return fail(c, error.UnsupportedTranslation, loc, "local variable has opaque type", .{});
}
@@ -1851,18 +1896,37 @@ fn transDeclRefExpr(
const value_decl = expr.getDecl();
const name = try c.str(@as(*const clang.NamedDecl, @ptrCast(value_decl)).getName_bytes_begin());
const mangled_name = scope.getAlias(name);
var ref_expr = if (cIsFunctionDeclRef(@as(*const clang.Expr, @ptrCast(expr))))
try Tag.fn_identifier.create(c.arena, mangled_name)
else
try Tag.identifier.create(c.arena, mangled_name);
const decl_is_var = @as(*const clang.Decl, @ptrCast(value_decl)).getKind() == .Var;
const potential_local_extern = if (decl_is_var) ((@as(*const clang.VarDecl, @ptrCast(value_decl)).getStorageClass() == .Extern) and (scope.id == .block)) else false;
if (@as(*const clang.Decl, @ptrCast(value_decl)).getKind() == .Var) {
var confirmed_local_extern = false;
var ref_expr = val: {
if (cIsFunctionDeclRef(@as(*const clang.Expr, @ptrCast(expr)))) {
break :val try Tag.fn_identifier.create(c.arena, mangled_name);
} else if (potential_local_extern) {
if (scope.getLocalExternAlias(name)) |v| {
confirmed_local_extern = true;
break :val try Tag.identifier.create(c.arena, v);
} else {
break :val try Tag.identifier.create(c.arena, mangled_name);
}
} else {
break :val try Tag.identifier.create(c.arena, mangled_name);
}
};
if (decl_is_var) {
const var_decl = @as(*const clang.VarDecl, @ptrCast(value_decl));
if (var_decl.isStaticLocal()) {
ref_expr = try Tag.field_access.create(c.arena, .{
.lhs = ref_expr,
.field_name = Scope.Block.static_inner_name,
});
} else if (confirmed_local_extern) {
ref_expr = try Tag.field_access.create(c.arena, .{
.lhs = ref_expr,
.field_name = name, // by necessity, name will always == mangled_name
});
}
}
scope.skipVariableDiscard(mangled_name);

View File

@@ -0,0 +1,18 @@
const int ev = 40;
static int func(void)
{
typedef int test_type_t;
extern const test_type_t ev;
return ev + 2;
}
int main()
{
if (func() != 42)
return 1;
return 0;
}
// run-translated-c
// c_frontend=clang