- 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:
committed by
Veikka Tuominen
parent
88bb0fd288
commit
20563d8457
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
=> {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user