aro_translate_c.zig (76078B) - Raw
1 const std = @import("std"); 2 const mem = std.mem; 3 const assert = std.debug.assert; 4 const CallingConvention = std.builtin.CallingConvention; 5 const aro = @import("aro"); 6 const CToken = aro.Tokenizer.Token; 7 const Tree = aro.Tree; 8 const NodeIndex = Tree.NodeIndex; 9 const TokenIndex = Tree.TokenIndex; 10 const Type = aro.Type; 11 pub const ast = @import("aro_translate_c/ast.zig"); 12 const ZigNode = ast.Node; 13 const ZigTag = ZigNode.Tag; 14 const Scope = ScopeExtra(Context, Type); 15 const Context = @This(); 16 17 gpa: mem.Allocator, 18 arena: mem.Allocator, 19 decl_table: std.AutoArrayHashMapUnmanaged(usize, []const u8) = .empty, 20 alias_list: AliasList, 21 global_scope: *Scope.Root, 22 mangle_count: u32 = 0, 23 /// Table of record decls that have been demoted to opaques. 24 opaque_demotes: std.AutoHashMapUnmanaged(usize, void) = .empty, 25 /// Table of unnamed enums and records that are child types of typedefs. 26 unnamed_typedefs: std.AutoHashMapUnmanaged(usize, []const u8) = .empty, 27 /// Needed to decide if we are parsing a typename 28 typedefs: std.StringArrayHashMapUnmanaged(void) = .empty, 29 30 /// This one is different than the root scope's name table. This contains 31 /// a list of names that we found by visiting all the top level decls without 32 /// translating them. The other maps are updated as we translate; this one is updated 33 /// up front in a pre-processing step. 34 global_names: std.StringArrayHashMapUnmanaged(void) = .empty, 35 36 /// This is similar to `global_names`, but contains names which we would 37 /// *like* to use, but do not strictly *have* to if they are unavailable. 38 /// These are relevant to types, which ideally we would name like 39 /// 'struct_foo' with an alias 'foo', but if either of those names is taken, 40 /// may be mangled. 41 /// This is distinct from `global_names` so we can detect at a type 42 /// declaration whether or not the name is available. 43 weak_global_names: std.StringArrayHashMapUnmanaged(void) = .empty, 44 45 pattern_list: PatternList, 46 tree: Tree, 47 comp: *aro.Compilation, 48 mapper: aro.TypeMapper, 49 50 fn getMangle(c: *Context) u32 { 51 c.mangle_count += 1; 52 return c.mangle_count; 53 } 54 55 /// Convert an aro TokenIndex to a 'file:line:column' string 56 fn locStr(c: *Context, tok_idx: TokenIndex) ![]const u8 { 57 const token_loc = c.tree.tokens.items(.loc)[tok_idx]; 58 const source = c.comp.getSource(token_loc.id); 59 const line_col = source.lineCol(token_loc); 60 const filename = source.path; 61 62 const line = source.physicalLine(token_loc); 63 const col = line_col.col; 64 65 return std.fmt.allocPrint(c.arena, "{s}:{d}:{d}", .{ filename, line, col }); 66 } 67 68 fn maybeSuppressResult(c: *Context, used: ResultUsed, result: ZigNode) TransError!ZigNode { 69 if (used == .used) return result; 70 return ZigTag.discard.create(c.arena, .{ .should_skip = false, .value = result }); 71 } 72 73 fn addTopLevelDecl(c: *Context, name: []const u8, decl_node: ZigNode) !void { 74 const gop = try c.global_scope.sym_table.getOrPut(name); 75 if (!gop.found_existing) { 76 gop.value_ptr.* = decl_node; 77 try c.global_scope.nodes.append(decl_node); 78 } 79 } 80 81 fn fail( 82 c: *Context, 83 err: anytype, 84 source_loc: TokenIndex, 85 comptime format: []const u8, 86 args: anytype, 87 ) (@TypeOf(err) || error{OutOfMemory}) { 88 try warn(c, &c.global_scope.base, source_loc, format, args); 89 return err; 90 } 91 92 fn failDecl(c: *Context, loc: TokenIndex, name: []const u8, comptime format: []const u8, args: anytype) Error!void { 93 // location 94 // pub const name = @compileError(msg); 95 const fail_msg = try std.fmt.allocPrint(c.arena, format, args); 96 try addTopLevelDecl(c, name, try ZigTag.fail_decl.create(c.arena, .{ .actual = name, .mangled = fail_msg })); 97 const str = try c.locStr(loc); 98 const location_comment = try std.fmt.allocPrint(c.arena, "// {s}", .{str}); 99 try c.global_scope.nodes.append(try ZigTag.warning.create(c.arena, location_comment)); 100 } 101 102 fn warn(c: *Context, scope: *Scope, loc: TokenIndex, comptime format: []const u8, args: anytype) !void { 103 const str = try c.locStr(loc); 104 const value = try std.fmt.allocPrint(c.arena, "// {s}: warning: " ++ format, .{str} ++ args); 105 try scope.appendNode(try ZigTag.warning.create(c.arena, value)); 106 } 107 108 pub fn translate( 109 gpa: mem.Allocator, 110 comp: *aro.Compilation, 111 args: []const []const u8, 112 ) !std.zig.Ast { 113 try comp.addDefaultPragmaHandlers(); 114 comp.langopts.setEmulatedCompiler(aro.target_util.systemCompiler(comp.target)); 115 116 var driver: aro.Driver = .{ .comp = comp }; 117 defer driver.deinit(); 118 119 var macro_buf = std.array_list.Managed(u8).init(gpa); 120 defer macro_buf.deinit(); 121 122 assert(!try driver.parseArgs(std.io.null_writer, macro_buf.writer(), args)); 123 assert(driver.inputs.items.len == 1); 124 const source = driver.inputs.items[0]; 125 126 const builtin_macros = try comp.generateBuiltinMacros(.include_system_defines); 127 const user_macros = try comp.addSourceFromBuffer("<command line>", macro_buf.items); 128 129 var pp = try aro.Preprocessor.initDefault(comp); 130 defer pp.deinit(); 131 132 try pp.preprocessSources(&.{ source, builtin_macros, user_macros }); 133 134 var tree = try pp.parse(); 135 defer tree.deinit(); 136 137 // Workaround for https://github.com/Vexu/arocc/issues/603 138 for (comp.diagnostics.list.items) |msg| { 139 if (msg.kind == .@"error" or msg.kind == .@"fatal error") return error.ParsingFailed; 140 } 141 142 const mapper = tree.comp.string_interner.getFastTypeMapper(tree.comp.gpa) catch tree.comp.string_interner.getSlowTypeMapper(); 143 defer mapper.deinit(tree.comp.gpa); 144 145 var arena_allocator = std.heap.ArenaAllocator.init(gpa); 146 defer arena_allocator.deinit(); 147 const arena = arena_allocator.allocator(); 148 149 var context = Context{ 150 .gpa = gpa, 151 .arena = arena, 152 .alias_list = AliasList.init(gpa), 153 .global_scope = try arena.create(Scope.Root), 154 .pattern_list = try PatternList.init(gpa), 155 .comp = comp, 156 .mapper = mapper, 157 .tree = tree, 158 }; 159 context.global_scope.* = Scope.Root.init(&context); 160 defer { 161 context.decl_table.deinit(gpa); 162 context.alias_list.deinit(); 163 context.global_names.deinit(gpa); 164 context.opaque_demotes.deinit(gpa); 165 context.unnamed_typedefs.deinit(gpa); 166 context.typedefs.deinit(gpa); 167 context.global_scope.deinit(); 168 context.pattern_list.deinit(gpa); 169 } 170 171 @setEvalBranchQuota(2000); 172 inline for (@typeInfo(std.zig.c_builtins).@"struct".decls) |decl| { 173 const builtin_fn = try ZigTag.pub_var_simple.create(arena, .{ 174 .name = decl.name, 175 .init = try ZigTag.import_c_builtin.create(arena, decl.name), 176 }); 177 try addTopLevelDecl(&context, decl.name, builtin_fn); 178 } 179 180 try prepopulateGlobalNameTable(&context); 181 try transTopLevelDecls(&context); 182 183 for (context.alias_list.items) |alias| { 184 if (!context.global_scope.sym_table.contains(alias.alias)) { 185 const node = try ZigTag.alias.create(arena, .{ .actual = alias.alias, .mangled = alias.name }); 186 try addTopLevelDecl(&context, alias.alias, node); 187 } 188 } 189 190 return ast.render(gpa, context.global_scope.nodes.items); 191 } 192 193 fn prepopulateGlobalNameTable(c: *Context) !void { 194 const node_tags = c.tree.nodes.items(.tag); 195 const node_types = c.tree.nodes.items(.ty); 196 const node_data = c.tree.nodes.items(.data); 197 for (c.tree.root_decls) |node| { 198 const data = node_data[@intFromEnum(node)]; 199 switch (node_tags[@intFromEnum(node)]) { 200 .typedef => {}, 201 202 .struct_decl_two, 203 .union_decl_two, 204 .struct_decl, 205 .union_decl, 206 .struct_forward_decl, 207 .union_forward_decl, 208 .enum_decl_two, 209 .enum_decl, 210 .enum_forward_decl, 211 => { 212 const raw_ty = node_types[@intFromEnum(node)]; 213 const ty = raw_ty.canonicalize(.standard); 214 const name_id = if (ty.isRecord()) ty.data.record.name else ty.data.@"enum".name; 215 const decl_name = c.mapper.lookup(name_id); 216 const container_prefix = if (ty.is(.@"struct")) "struct" else if (ty.is(.@"union")) "union" else "enum"; 217 const prefixed_name = try std.fmt.allocPrint(c.arena, "{s}_{s}", .{ container_prefix, decl_name }); 218 // `decl_name` and `prefixed_name` are the preferred names for this type. 219 // However, we can name it anything else if necessary, so these are "weak names". 220 try c.weak_global_names.ensureUnusedCapacity(c.gpa, 2); 221 c.weak_global_names.putAssumeCapacity(decl_name, {}); 222 c.weak_global_names.putAssumeCapacity(prefixed_name, {}); 223 }, 224 225 .fn_proto, 226 .static_fn_proto, 227 .inline_fn_proto, 228 .inline_static_fn_proto, 229 .fn_def, 230 .static_fn_def, 231 .inline_fn_def, 232 .inline_static_fn_def, 233 .@"var", 234 .extern_var, 235 .static_var, 236 .threadlocal_var, 237 .threadlocal_extern_var, 238 .threadlocal_static_var, 239 => { 240 const decl_name = c.tree.tokSlice(data.decl.name); 241 try c.global_names.put(c.gpa, decl_name, {}); 242 }, 243 .static_assert => {}, 244 else => unreachable, 245 } 246 } 247 } 248 249 fn transTopLevelDecls(c: *Context) !void { 250 for (c.tree.root_decls) |node| { 251 try transDecl(c, &c.global_scope.base, node); 252 } 253 } 254 255 fn transDecl(c: *Context, scope: *Scope, decl: NodeIndex) !void { 256 const node_tags = c.tree.nodes.items(.tag); 257 const node_data = c.tree.nodes.items(.data); 258 const node_ty = c.tree.nodes.items(.ty); 259 const data = node_data[@intFromEnum(decl)]; 260 switch (node_tags[@intFromEnum(decl)]) { 261 .typedef => { 262 try transTypeDef(c, scope, decl); 263 }, 264 265 .struct_decl_two, 266 .union_decl_two, 267 => { 268 try transRecordDecl(c, scope, node_ty[@intFromEnum(decl)]); 269 }, 270 .struct_decl, 271 .union_decl, 272 => { 273 try transRecordDecl(c, scope, node_ty[@intFromEnum(decl)]); 274 }, 275 276 .enum_decl_two => { 277 var fields = [2]NodeIndex{ data.bin.lhs, data.bin.rhs }; 278 var field_count: u8 = 0; 279 if (fields[0] != .none) field_count += 1; 280 if (fields[1] != .none) field_count += 1; 281 const enum_decl = node_ty[@intFromEnum(decl)].canonicalize(.standard).data.@"enum"; 282 try transEnumDecl(c, scope, enum_decl, fields[0..field_count]); 283 }, 284 .enum_decl => { 285 const fields = c.tree.data[data.range.start..data.range.end]; 286 const enum_decl = node_ty[@intFromEnum(decl)].canonicalize(.standard).data.@"enum"; 287 try transEnumDecl(c, scope, enum_decl, fields); 288 }, 289 290 .enum_field_decl, 291 .record_field_decl, 292 .indirect_record_field_decl, 293 .struct_forward_decl, 294 .union_forward_decl, 295 .enum_forward_decl, 296 => return, 297 298 .fn_proto, 299 .static_fn_proto, 300 .inline_fn_proto, 301 .inline_static_fn_proto, 302 .fn_def, 303 .static_fn_def, 304 .inline_fn_def, 305 .inline_static_fn_def, 306 => { 307 try transFnDecl(c, decl, true); 308 }, 309 310 .@"var", 311 .extern_var, 312 .static_var, 313 .threadlocal_var, 314 .threadlocal_extern_var, 315 .threadlocal_static_var, 316 => { 317 try transVarDecl(c, decl); 318 }, 319 .static_assert => try warn(c, &c.global_scope.base, 0, "ignoring _Static_assert declaration", .{}), 320 else => unreachable, 321 } 322 } 323 324 fn transTypeDef(c: *Context, scope: *Scope, typedef_decl: NodeIndex) Error!void { 325 const ty = c.tree.nodes.items(.ty)[@intFromEnum(typedef_decl)]; 326 const data = c.tree.nodes.items(.data)[@intFromEnum(typedef_decl)]; 327 328 const toplevel = scope.id == .root; 329 const bs: *Scope.Block = if (!toplevel) try scope.findBlockScope(c) else undefined; 330 331 var name: []const u8 = c.tree.tokSlice(data.decl.name); 332 try c.typedefs.put(c.gpa, name, {}); 333 334 if (!toplevel) name = try bs.makeMangledName(c, name); 335 336 const typedef_loc = data.decl.name; 337 const init_node = transType(c, scope, ty, .standard, typedef_loc) catch |err| switch (err) { 338 error.UnsupportedType => { 339 return failDecl(c, typedef_loc, name, "unable to resolve typedef child type", .{}); 340 }, 341 error.OutOfMemory => |e| return e, 342 }; 343 344 const payload = try c.arena.create(ast.Payload.SimpleVarDecl); 345 payload.* = .{ 346 .base = .{ .tag = ([2]ZigTag{ .var_simple, .pub_var_simple })[@intFromBool(toplevel)] }, 347 .data = .{ 348 .name = name, 349 .init = init_node, 350 }, 351 }; 352 const node = ZigNode.initPayload(&payload.base); 353 354 if (toplevel) { 355 try addTopLevelDecl(c, name, node); 356 } else { 357 try scope.appendNode(node); 358 if (node.tag() != .pub_var_simple) { 359 try bs.discardVariable(c, name); 360 } 361 } 362 } 363 364 fn mangleWeakGlobalName(c: *Context, want_name: []const u8) ![]const u8 { 365 var cur_name = want_name; 366 367 if (!c.weak_global_names.contains(want_name)) { 368 // This type wasn't noticed by the name detection pass, so nothing has been treating this as 369 // a weak global name. We must mangle it to avoid conflicts with locals. 370 cur_name = try std.fmt.allocPrint(c.arena, "{s}_{d}", .{ want_name, c.getMangle() }); 371 } 372 373 while (c.global_names.contains(cur_name)) { 374 cur_name = try std.fmt.allocPrint(c.arena, "{s}_{d}", .{ want_name, c.getMangle() }); 375 } 376 return cur_name; 377 } 378 379 fn transRecordDecl(c: *Context, scope: *Scope, record_ty: Type) Error!void { 380 const record_decl = record_ty.getRecord().?; 381 if (c.decl_table.get(@intFromPtr(record_decl))) |_| 382 return; // Avoid processing this decl twice 383 const toplevel = scope.id == .root; 384 const bs: *Scope.Block = if (!toplevel) try scope.findBlockScope(c) else undefined; 385 386 const container_kind: ZigTag = if (record_ty.is(.@"union")) .@"union" else .@"struct"; 387 const container_kind_name: []const u8 = @tagName(container_kind); 388 389 var is_unnamed = false; 390 var bare_name: []const u8 = c.mapper.lookup(record_decl.name); 391 var name = bare_name; 392 393 if (c.unnamed_typedefs.get(@intFromPtr(record_decl))) |typedef_name| { 394 bare_name = typedef_name; 395 name = typedef_name; 396 } else { 397 if (record_ty.isAnonymousRecord(c.comp)) { 398 bare_name = try std.fmt.allocPrint(c.arena, "unnamed_{d}", .{c.getMangle()}); 399 is_unnamed = true; 400 } 401 name = try std.fmt.allocPrint(c.arena, "{s}_{s}", .{ container_kind_name, bare_name }); 402 if (toplevel and !is_unnamed) { 403 name = try mangleWeakGlobalName(c, name); 404 } 405 } 406 if (!toplevel) name = try bs.makeMangledName(c, name); 407 try c.decl_table.putNoClobber(c.gpa, @intFromPtr(record_decl), name); 408 409 const is_pub = toplevel and !is_unnamed; 410 const init_node = blk: { 411 if (record_decl.isIncomplete()) { 412 try c.opaque_demotes.put(c.gpa, @intFromPtr(record_decl), {}); 413 break :blk ZigTag.opaque_literal.init(); 414 } 415 416 var fields = try std.array_list.Managed(ast.Payload.Record.Field).initCapacity(c.gpa, record_decl.fields.len); 417 defer fields.deinit(); 418 419 // TODO: Add support for flexible array field functions 420 var functions = std.array_list.Managed(ZigNode).init(c.gpa); 421 defer functions.deinit(); 422 423 var unnamed_field_count: u32 = 0; 424 425 // If a record doesn't have any attributes that would affect the alignment and 426 // layout, then we can just use a simple `extern` type. If it does have attributes, 427 // then we need to inspect the layout and assign an `align` value for each field. 428 const has_alignment_attributes = record_decl.field_attributes != null or 429 record_ty.hasAttribute(.@"packed") or 430 record_ty.hasAttribute(.aligned); 431 const head_field_alignment: ?c_uint = if (has_alignment_attributes) headFieldAlignment(record_decl) else null; 432 433 for (record_decl.fields, 0..) |field, field_index| { 434 const field_loc = field.name_tok; 435 436 // Demote record to opaque if it contains a bitfield 437 if (!field.isRegularField()) { 438 try c.opaque_demotes.put(c.gpa, @intFromPtr(record_decl), {}); 439 try warn(c, scope, field_loc, "{s} demoted to opaque type - has bitfield", .{container_kind_name}); 440 break :blk ZigTag.opaque_literal.init(); 441 } 442 443 var field_name = c.mapper.lookup(field.name); 444 if (!field.isNamed()) { 445 field_name = try std.fmt.allocPrint(c.arena, "unnamed_{d}", .{unnamed_field_count}); 446 unnamed_field_count += 1; 447 } 448 const field_type = transType(c, scope, field.ty, .preserve_quals, field_loc) catch |err| switch (err) { 449 error.UnsupportedType => { 450 try c.opaque_demotes.put(c.gpa, @intFromPtr(record_decl), {}); 451 try warn(c, scope, 0, "{s} demoted to opaque type - unable to translate type of field {s}", .{ 452 container_kind_name, 453 field_name, 454 }); 455 break :blk ZigTag.opaque_literal.init(); 456 }, 457 else => |e| return e, 458 }; 459 460 const field_alignment = if (has_alignment_attributes) 461 alignmentForField(record_decl, head_field_alignment, field_index) 462 else 463 null; 464 465 // C99 introduced designated initializers for structs. Omitted fields are implicitly 466 // initialized to zero. Some C APIs are designed with this in mind. Defaulting to zero 467 // values for translated struct fields permits Zig code to comfortably use such an API. 468 const default_value = if (container_kind == .@"struct") 469 try ZigTag.std_mem_zeroes.create(c.arena, field_type) 470 else 471 null; 472 473 fields.appendAssumeCapacity(.{ 474 .name = field_name, 475 .type = field_type, 476 .alignment = field_alignment, 477 .default_value = default_value, 478 }); 479 } 480 481 const record_payload = try c.arena.create(ast.Payload.Record); 482 record_payload.* = .{ 483 .base = .{ .tag = container_kind }, 484 .data = .{ 485 .layout = .@"extern", 486 .fields = try c.arena.dupe(ast.Payload.Record.Field, fields.items), 487 .functions = try c.arena.dupe(ZigNode, functions.items), 488 .variables = &.{}, 489 }, 490 }; 491 break :blk ZigNode.initPayload(&record_payload.base); 492 }; 493 494 const payload = try c.arena.create(ast.Payload.SimpleVarDecl); 495 payload.* = .{ 496 .base = .{ .tag = ([2]ZigTag{ .var_simple, .pub_var_simple })[@intFromBool(is_pub)] }, 497 .data = .{ 498 .name = name, 499 .init = init_node, 500 }, 501 }; 502 const node = ZigNode.initPayload(&payload.base); 503 if (toplevel) { 504 try addTopLevelDecl(c, name, node); 505 // Only add the alias if the name is available *and* it was caught by 506 // name detection. Don't bother performing a weak mangle, since a 507 // mangled name is of no real use here. 508 if (!is_unnamed and !c.global_names.contains(bare_name) and c.weak_global_names.contains(bare_name)) 509 try c.alias_list.append(.{ .alias = bare_name, .name = name }); 510 } else { 511 try scope.appendNode(node); 512 if (node.tag() != .pub_var_simple) { 513 try bs.discardVariable(c, name); 514 } 515 } 516 } 517 518 fn transFnDecl(c: *Context, fn_decl: NodeIndex, is_pub: bool) Error!void { 519 const raw_ty = c.tree.nodes.items(.ty)[@intFromEnum(fn_decl)]; 520 const fn_ty = raw_ty.canonicalize(.standard); 521 const node_data = c.tree.nodes.items(.data)[@intFromEnum(fn_decl)]; 522 if (c.decl_table.get(@intFromPtr(fn_ty.data.func))) |_| 523 return; // Avoid processing this decl twice 524 525 const fn_name = c.tree.tokSlice(node_data.decl.name); 526 if (c.global_scope.sym_table.contains(fn_name)) 527 return; // Avoid processing this decl twice 528 529 const fn_decl_loc = 0; // TODO 530 const has_body = node_data.decl.node != .none; 531 const is_always_inline = has_body and raw_ty.getAttribute(.always_inline) != null; 532 const proto_ctx = FnProtoContext{ 533 .fn_name = fn_name, 534 .is_inline = is_always_inline, 535 .is_extern = !has_body, 536 .is_export = switch (c.tree.nodes.items(.tag)[@intFromEnum(fn_decl)]) { 537 .fn_proto, .fn_def => has_body and !is_always_inline, 538 539 .inline_fn_proto, .inline_fn_def, .inline_static_fn_proto, .inline_static_fn_def, .static_fn_proto, .static_fn_def => false, 540 541 else => unreachable, 542 }, 543 .is_pub = is_pub, 544 }; 545 546 const proto_node = transFnType(c, &c.global_scope.base, raw_ty, fn_ty, fn_decl_loc, proto_ctx) catch |err| switch (err) { 547 error.UnsupportedType => { 548 return failDecl(c, fn_decl_loc, fn_name, "unable to resolve prototype of function", .{}); 549 }, 550 error.OutOfMemory => |e| return e, 551 }; 552 553 if (!has_body) { 554 return addTopLevelDecl(c, fn_name, proto_node); 555 } 556 const proto_payload = proto_node.castTag(.func).?; 557 558 // actual function definition with body 559 const body_stmt = node_data.decl.node; 560 var block_scope = try Scope.Block.init(c, &c.global_scope.base, false); 561 block_scope.return_type = fn_ty.data.func.return_type; 562 defer block_scope.deinit(); 563 564 var scope = &block_scope.base; 565 _ = &scope; 566 567 var param_id: c_uint = 0; 568 for (proto_payload.data.params, fn_ty.data.func.params) |*param, param_info| { 569 const param_name = param.name orelse { 570 proto_payload.data.is_extern = true; 571 proto_payload.data.is_export = false; 572 proto_payload.data.is_inline = false; 573 try warn(c, &c.global_scope.base, fn_decl_loc, "function {s} parameter has no name, demoted to extern", .{fn_name}); 574 return addTopLevelDecl(c, fn_name, proto_node); 575 }; 576 577 const is_const = param_info.ty.qual.@"const"; 578 579 const mangled_param_name = try block_scope.makeMangledName(c, param_name); 580 param.name = mangled_param_name; 581 582 if (!is_const) { 583 const bare_arg_name = try std.fmt.allocPrint(c.arena, "arg_{s}", .{mangled_param_name}); 584 const arg_name = try block_scope.makeMangledName(c, bare_arg_name); 585 param.name = arg_name; 586 587 const redecl_node = try ZigTag.arg_redecl.create(c.arena, .{ .actual = mangled_param_name, .mangled = arg_name }); 588 try block_scope.statements.append(redecl_node); 589 } 590 try block_scope.discardVariable(c, mangled_param_name); 591 592 param_id += 1; 593 } 594 595 transCompoundStmtInline(c, body_stmt, &block_scope) catch |err| switch (err) { 596 error.OutOfMemory => |e| return e, 597 error.UnsupportedTranslation, 598 error.UnsupportedType, 599 => { 600 proto_payload.data.is_extern = true; 601 proto_payload.data.is_export = false; 602 proto_payload.data.is_inline = false; 603 try warn(c, &c.global_scope.base, fn_decl_loc, "unable to translate function, demoted to extern", .{}); 604 return addTopLevelDecl(c, fn_name, proto_node); 605 }, 606 }; 607 608 proto_payload.data.body = try block_scope.complete(c); 609 return addTopLevelDecl(c, fn_name, proto_node); 610 } 611 612 fn transVarDecl(c: *Context, node: NodeIndex) Error!void { 613 const data = c.tree.nodes.items(.data)[@intFromEnum(node)]; 614 const name = c.tree.tokSlice(data.decl.name); 615 return failDecl(c, data.decl.name, name, "unable to translate variable declaration", .{}); 616 } 617 618 fn transEnumDecl(c: *Context, scope: *Scope, enum_decl: *const Type.Enum, field_nodes: []const NodeIndex) Error!void { 619 if (c.decl_table.get(@intFromPtr(enum_decl))) |_| 620 return; // Avoid processing this decl twice 621 const toplevel = scope.id == .root; 622 const bs: *Scope.Block = if (!toplevel) try scope.findBlockScope(c) else undefined; 623 624 var is_unnamed = false; 625 var bare_name: []const u8 = c.mapper.lookup(enum_decl.name); 626 var name = bare_name; 627 if (c.unnamed_typedefs.get(@intFromPtr(enum_decl))) |typedef_name| { 628 bare_name = typedef_name; 629 name = typedef_name; 630 } else { 631 if (bare_name.len == 0) { 632 bare_name = try std.fmt.allocPrint(c.arena, "unnamed_{d}", .{c.getMangle()}); 633 is_unnamed = true; 634 } 635 name = try std.fmt.allocPrint(c.arena, "enum_{s}", .{bare_name}); 636 } 637 if (!toplevel) name = try bs.makeMangledName(c, name); 638 try c.decl_table.putNoClobber(c.gpa, @intFromPtr(enum_decl), name); 639 640 const enum_type_node = if (!enum_decl.isIncomplete()) blk: { 641 for (enum_decl.fields, field_nodes) |field, field_node| { 642 var enum_val_name: []const u8 = c.mapper.lookup(field.name); 643 if (!toplevel) { 644 enum_val_name = try bs.makeMangledName(c, enum_val_name); 645 } 646 647 const enum_const_type_node: ?ZigNode = transType(c, scope, field.ty, .standard, field.name_tok) catch |err| switch (err) { 648 error.UnsupportedType => null, 649 else => |e| return e, 650 }; 651 652 const val = c.tree.value_map.get(field_node).?; 653 const enum_const_def = try ZigTag.enum_constant.create(c.arena, .{ 654 .name = enum_val_name, 655 .is_public = toplevel, 656 .type = enum_const_type_node, 657 .value = try transCreateNodeAPInt(c, val), 658 }); 659 if (toplevel) 660 try addTopLevelDecl(c, enum_val_name, enum_const_def) 661 else { 662 try scope.appendNode(enum_const_def); 663 try bs.discardVariable(c, enum_val_name); 664 } 665 } 666 667 break :blk transType(c, scope, enum_decl.tag_ty, .standard, 0) catch |err| switch (err) { 668 error.UnsupportedType => { 669 return failDecl(c, 0, name, "unable to translate enum integer type", .{}); 670 }, 671 else => |e| return e, 672 }; 673 } else blk: { 674 try c.opaque_demotes.put(c.gpa, @intFromPtr(enum_decl), {}); 675 break :blk ZigTag.opaque_literal.init(); 676 }; 677 678 const is_pub = toplevel and !is_unnamed; 679 const payload = try c.arena.create(ast.Payload.SimpleVarDecl); 680 payload.* = .{ 681 .base = .{ .tag = ([2]ZigTag{ .var_simple, .pub_var_simple })[@intFromBool(is_pub)] }, 682 .data = .{ 683 .init = enum_type_node, 684 .name = name, 685 }, 686 }; 687 const node = ZigNode.initPayload(&payload.base); 688 if (toplevel) { 689 try addTopLevelDecl(c, name, node); 690 if (!is_unnamed) 691 try c.alias_list.append(.{ .alias = bare_name, .name = name }); 692 } else { 693 try scope.appendNode(node); 694 if (node.tag() != .pub_var_simple) { 695 try bs.discardVariable(c, name); 696 } 697 } 698 } 699 700 fn getTypeStr(c: *Context, ty: Type) ![]const u8 { 701 var buf: std.ArrayListUnmanaged(u8) = .empty; 702 defer buf.deinit(c.gpa); 703 const w = buf.writer(c.gpa); 704 try ty.print(c.mapper, c.comp.langopts, w); 705 return c.arena.dupe(u8, buf.items); 706 } 707 708 fn transType(c: *Context, scope: *Scope, raw_ty: Type, qual_handling: Type.QualHandling, source_loc: TokenIndex) TypeError!ZigNode { 709 const ty = raw_ty.canonicalize(qual_handling); 710 if (ty.qual.atomic) { 711 const type_name = try getTypeStr(c, ty); 712 return fail(c, error.UnsupportedType, source_loc, "unsupported type: '{s}'", .{type_name}); 713 } 714 715 switch (ty.specifier) { 716 .void => return ZigTag.type.create(c.arena, "anyopaque"), 717 .bool => return ZigTag.type.create(c.arena, "bool"), 718 .char => return ZigTag.type.create(c.arena, "c_char"), 719 .schar => return ZigTag.type.create(c.arena, "i8"), 720 .uchar => return ZigTag.type.create(c.arena, "u8"), 721 .short => return ZigTag.type.create(c.arena, "c_short"), 722 .ushort => return ZigTag.type.create(c.arena, "c_ushort"), 723 .int => return ZigTag.type.create(c.arena, "c_int"), 724 .uint => return ZigTag.type.create(c.arena, "c_uint"), 725 .long => return ZigTag.type.create(c.arena, "c_long"), 726 .ulong => return ZigTag.type.create(c.arena, "c_ulong"), 727 .long_long => return ZigTag.type.create(c.arena, "c_longlong"), 728 .ulong_long => return ZigTag.type.create(c.arena, "c_ulonglong"), 729 .int128 => return ZigTag.type.create(c.arena, "i128"), 730 .uint128 => return ZigTag.type.create(c.arena, "u128"), 731 .fp16, .float16 => return ZigTag.type.create(c.arena, "f16"), 732 .float => return ZigTag.type.create(c.arena, "f32"), 733 .double => return ZigTag.type.create(c.arena, "f64"), 734 .long_double => return ZigTag.type.create(c.arena, "c_longdouble"), 735 .float128 => return ZigTag.type.create(c.arena, "f128"), 736 .@"enum" => { 737 const enum_decl = ty.data.@"enum"; 738 var trans_scope = scope; 739 if (enum_decl.name != .empty) { 740 const decl_name = c.mapper.lookup(enum_decl.name); 741 if (c.weak_global_names.contains(decl_name)) trans_scope = &c.global_scope.base; 742 } 743 try transEnumDecl(c, trans_scope, enum_decl, &.{}); 744 return ZigTag.identifier.create(c.arena, c.decl_table.get(@intFromPtr(enum_decl)).?); 745 }, 746 .pointer => { 747 const child_type = ty.elemType(); 748 749 const is_fn_proto = child_type.isFunc(); 750 const is_const = is_fn_proto or child_type.isConst(); 751 const is_volatile = child_type.qual.@"volatile"; 752 const elem_type = try transType(c, scope, child_type, qual_handling, source_loc); 753 const ptr_info: @FieldType(ast.Payload.Pointer, "data") = .{ 754 .is_const = is_const, 755 .is_volatile = is_volatile, 756 .elem_type = elem_type, 757 }; 758 if (is_fn_proto or 759 typeIsOpaque(c, child_type) or 760 typeWasDemotedToOpaque(c, child_type)) 761 { 762 const ptr = try ZigTag.single_pointer.create(c.arena, ptr_info); 763 return ZigTag.optional_type.create(c.arena, ptr); 764 } 765 766 return ZigTag.c_pointer.create(c.arena, ptr_info); 767 }, 768 .unspecified_variable_len_array, .incomplete_array => { 769 const child_type = ty.elemType(); 770 const is_const = child_type.qual.@"const"; 771 const is_volatile = child_type.qual.@"volatile"; 772 const elem_type = try transType(c, scope, child_type, qual_handling, source_loc); 773 774 return ZigTag.c_pointer.create(c.arena, .{ .is_const = is_const, .is_volatile = is_volatile, .elem_type = elem_type }); 775 }, 776 .array, 777 .static_array, 778 => { 779 const size = ty.arrayLen().?; 780 const elem_type = try transType(c, scope, ty.elemType(), qual_handling, source_loc); 781 return ZigTag.array_type.create(c.arena, .{ .len = size, .elem_type = elem_type }); 782 }, 783 .func, 784 .var_args_func, 785 .old_style_func, 786 => return transFnType(c, scope, ty, ty, source_loc, .{}), 787 .@"struct", 788 .@"union", 789 => { 790 var trans_scope = scope; 791 if (ty.isAnonymousRecord(c.comp)) { 792 const record_decl = ty.data.record; 793 const name_id = c.mapper.lookup(record_decl.name); 794 if (c.weak_global_names.contains(name_id)) trans_scope = &c.global_scope.base; 795 } 796 try transRecordDecl(c, trans_scope, ty); 797 const name = c.decl_table.get(@intFromPtr(ty.data.record)).?; 798 return ZigTag.identifier.create(c.arena, name); 799 }, 800 .attributed, 801 .typeof_type, 802 .typeof_expr, 803 => unreachable, 804 else => return error.UnsupportedType, 805 } 806 } 807 808 /// Look ahead through the fields of the record to determine what the alignment of the record 809 /// would be without any align/packed/etc. attributes. This helps us determine whether or not 810 /// the fields with 0 offset need an `align` qualifier. Strictly speaking, we could just 811 /// pedantically assign those fields the same alignment as the parent's pointer alignment, 812 /// but this helps the generated code to be a little less verbose. 813 fn headFieldAlignment(record_decl: *const Type.Record) ?c_uint { 814 const bits_per_byte = 8; 815 const parent_ptr_alignment_bits = record_decl.type_layout.pointer_alignment_bits; 816 const parent_ptr_alignment = parent_ptr_alignment_bits / bits_per_byte; 817 var max_field_alignment_bits: u64 = 0; 818 for (record_decl.fields) |field| { 819 if (field.ty.getRecord()) |field_record_decl| { 820 const child_record_alignment = field_record_decl.type_layout.field_alignment_bits; 821 if (child_record_alignment > max_field_alignment_bits) 822 max_field_alignment_bits = child_record_alignment; 823 } else { 824 const field_size = field.layout.size_bits; 825 if (field_size > max_field_alignment_bits) 826 max_field_alignment_bits = field_size; 827 } 828 } 829 if (max_field_alignment_bits != parent_ptr_alignment_bits) { 830 return parent_ptr_alignment; 831 } else { 832 return null; 833 } 834 } 835 836 /// This function inspects the generated layout of a record to determine the alignment for a 837 /// particular field. This approach is necessary because unlike Zig, a C compiler is not 838 /// required to fulfill the requested alignment, which means we'd risk generating different code 839 /// if we only look at the user-requested alignment. 840 /// 841 /// Returns a ?c_uint to match Clang's behaviour of using c_uint. The return type can be changed 842 /// after the Clang frontend for translate-c is removed. A null value indicates that a field is 843 /// 'naturally aligned'. 844 fn alignmentForField( 845 record_decl: *const Type.Record, 846 head_field_alignment: ?c_uint, 847 field_index: usize, 848 ) ?c_uint { 849 const fields = record_decl.fields; 850 assert(fields.len != 0); 851 const field = fields[field_index]; 852 853 const bits_per_byte = 8; 854 const parent_ptr_alignment_bits = record_decl.type_layout.pointer_alignment_bits; 855 const parent_ptr_alignment = parent_ptr_alignment_bits / bits_per_byte; 856 857 // bitfields aren't supported yet. Until support is added, records with bitfields 858 // should be demoted to opaque, and this function shouldn't be called for them. 859 if (!field.isRegularField()) { 860 @panic("TODO: add bitfield support for records"); 861 } 862 863 const field_offset_bits: u64 = field.layout.offset_bits; 864 const field_size_bits: u64 = field.layout.size_bits; 865 866 // Fields with zero width always have an alignment of 1 867 if (field_size_bits == 0) { 868 return 1; 869 } 870 871 // Fields with 0 offset inherit the parent's pointer alignment. 872 if (field_offset_bits == 0) { 873 return head_field_alignment; 874 } 875 876 // Records have a natural alignment when used as a field, and their size is 877 // a multiple of this alignment value. For all other types, the natural alignment 878 // is their size. 879 const field_natural_alignment_bits: u64 = if (field.ty.getRecord()) |record| record.type_layout.field_alignment_bits else field_size_bits; 880 const rem_bits = field_offset_bits % field_natural_alignment_bits; 881 882 // If there's a remainder, then the alignment is smaller than the field's 883 // natural alignment 884 if (rem_bits > 0) { 885 const rem_alignment = rem_bits / bits_per_byte; 886 if (rem_alignment > 0 and std.math.isPowerOfTwo(rem_alignment)) { 887 const actual_alignment = @min(rem_alignment, parent_ptr_alignment); 888 return @as(c_uint, @truncate(actual_alignment)); 889 } else { 890 return 1; 891 } 892 } 893 894 // A field may have an offset which positions it to be naturally aligned, but the 895 // parent's pointer alignment determines if this is actually true, so we take the minimum 896 // value. 897 // For example, a float field (4 bytes wide) with a 4 byte offset is positioned to have natural 898 // alignment, but if the parent pointer alignment is 2, then the actual alignment of the 899 // float is 2. 900 const field_natural_alignment: u64 = field_natural_alignment_bits / bits_per_byte; 901 const offset_alignment = field_offset_bits / bits_per_byte; 902 const possible_alignment = @min(parent_ptr_alignment, offset_alignment); 903 if (possible_alignment == field_natural_alignment) { 904 return null; 905 } else if (possible_alignment < field_natural_alignment) { 906 if (std.math.isPowerOfTwo(possible_alignment)) { 907 return possible_alignment; 908 } else { 909 return 1; 910 } 911 } else { // possible_alignment > field_natural_alignment 912 // Here, the field is positioned be at a higher alignment than it's natural alignment. This means we 913 // need to determine whether it's a specified alignment. We can determine that from the padding preceding 914 // the field. 915 const padding_from_prev_field: u64 = blk: { 916 if (field_offset_bits != 0) { 917 const previous_field = fields[field_index - 1]; 918 break :blk (field_offset_bits - previous_field.layout.offset_bits) - previous_field.layout.size_bits; 919 } else { 920 break :blk 0; 921 } 922 }; 923 if (padding_from_prev_field < field_natural_alignment_bits) { 924 return null; 925 } else { 926 return possible_alignment; 927 } 928 } 929 } 930 931 const FnProtoContext = struct { 932 is_pub: bool = false, 933 is_export: bool = false, 934 is_extern: bool = false, 935 is_inline: bool = false, 936 fn_name: ?[]const u8 = null, 937 }; 938 939 fn transFnType( 940 c: *Context, 941 scope: *Scope, 942 raw_ty: Type, 943 fn_ty: Type, 944 source_loc: TokenIndex, 945 ctx: FnProtoContext, 946 ) !ZigNode { 947 const param_count: usize = fn_ty.data.func.params.len; 948 const fn_params = try c.arena.alloc(ast.Payload.Param, param_count); 949 950 for (fn_ty.data.func.params, fn_params) |param_info, *param_node| { 951 const param_ty = param_info.ty; 952 const is_noalias = param_ty.qual.restrict; 953 954 const param_name: ?[]const u8 = if (param_info.name == .empty) 955 null 956 else 957 c.mapper.lookup(param_info.name); 958 959 const type_node = try transType(c, scope, param_ty, .standard, param_info.name_tok); 960 param_node.* = .{ 961 .is_noalias = is_noalias, 962 .name = param_name, 963 .type = type_node, 964 }; 965 } 966 967 const linksection_string = blk: { 968 if (raw_ty.getAttribute(.section)) |section| { 969 break :blk c.comp.interner.get(section.name.ref()).bytes; 970 } 971 break :blk null; 972 }; 973 974 const alignment: ?c_uint = raw_ty.requestedAlignment(c.comp) orelse null; 975 976 const explicit_callconv = null; 977 // const explicit_callconv = if ((ctx.is_inline or ctx.is_export or ctx.is_extern) and ctx.cc == .C) null else ctx.cc; 978 979 const return_type_node = blk: { 980 if (raw_ty.getAttribute(.noreturn) != null) { 981 break :blk ZigTag.noreturn_type.init(); 982 } else { 983 const return_ty = fn_ty.data.func.return_type; 984 if (return_ty.is(.void)) { 985 // convert primitive anyopaque to actual void (only for return type) 986 break :blk ZigTag.void_type.init(); 987 } else { 988 break :blk transType(c, scope, return_ty, .standard, source_loc) catch |err| switch (err) { 989 error.UnsupportedType => { 990 try warn(c, scope, source_loc, "unsupported function proto return type", .{}); 991 return err; 992 }, 993 error.OutOfMemory => |e| return e, 994 }; 995 } 996 } 997 }; 998 999 const payload = try c.arena.create(ast.Payload.Func); 1000 payload.* = .{ 1001 .base = .{ .tag = .func }, 1002 .data = .{ 1003 .is_pub = ctx.is_pub, 1004 .is_extern = ctx.is_extern, 1005 .is_export = ctx.is_export, 1006 .is_inline = ctx.is_inline, 1007 .is_var_args = switch (fn_ty.specifier) { 1008 .func => false, 1009 .var_args_func => true, 1010 .old_style_func => !ctx.is_export and !ctx.is_inline, 1011 else => unreachable, 1012 }, 1013 .name = ctx.fn_name, 1014 .linksection_string = linksection_string, 1015 .explicit_callconv = explicit_callconv, 1016 .params = fn_params, 1017 .return_type = return_type_node, 1018 .body = null, 1019 .alignment = alignment, 1020 }, 1021 }; 1022 return ZigNode.initPayload(&payload.base); 1023 } 1024 1025 fn transStmt(c: *Context, node: NodeIndex) TransError!ZigNode { 1026 _ = c; 1027 _ = node; 1028 return error.UnsupportedTranslation; 1029 } 1030 1031 fn transCompoundStmtInline(c: *Context, compound: NodeIndex, block: *Scope.Block) TransError!void { 1032 const data = c.tree.nodes.items(.data)[@intFromEnum(compound)]; 1033 var buf: [2]NodeIndex = undefined; 1034 // TODO move these helpers to Aro 1035 const stmts = switch (c.tree.nodes.items(.tag)[@intFromEnum(compound)]) { 1036 .compound_stmt_two => blk: { 1037 if (data.bin.lhs != .none) buf[0] = data.bin.lhs; 1038 if (data.bin.rhs != .none) buf[1] = data.bin.rhs; 1039 break :blk buf[0 .. @as(u32, @intFromBool(data.bin.lhs != .none)) + @intFromBool(data.bin.rhs != .none)]; 1040 }, 1041 .compound_stmt => c.tree.data[data.range.start..data.range.end], 1042 else => unreachable, 1043 }; 1044 for (stmts) |stmt| { 1045 const result = try transStmt(c, stmt); 1046 switch (result.tag()) { 1047 .declaration, .empty_block => {}, 1048 else => try block.statements.append(result), 1049 } 1050 } 1051 } 1052 1053 fn recordHasBitfield(record: *const Type.Record) bool { 1054 if (record.isIncomplete()) return false; 1055 for (record.fields) |field| { 1056 if (!field.isRegularField()) return true; 1057 } 1058 return false; 1059 } 1060 1061 fn typeIsOpaque(c: *Context, ty: Type) bool { 1062 return switch (ty.specifier) { 1063 .void => true, 1064 .@"struct", .@"union" => recordHasBitfield(ty.getRecord().?), 1065 .typeof_type => typeIsOpaque(c, ty.data.sub_type.*), 1066 .typeof_expr => typeIsOpaque(c, ty.data.expr.ty), 1067 .attributed => typeIsOpaque(c, ty.data.attributed.base), 1068 else => false, 1069 }; 1070 } 1071 1072 fn typeWasDemotedToOpaque(c: *Context, ty: Type) bool { 1073 switch (ty.specifier) { 1074 .@"struct", .@"union" => { 1075 const record = ty.getRecord().?; 1076 if (c.opaque_demotes.contains(@intFromPtr(record))) return true; 1077 for (record.fields) |field| { 1078 if (typeWasDemotedToOpaque(c, field.ty)) return true; 1079 } 1080 return false; 1081 }, 1082 1083 .@"enum" => return c.opaque_demotes.contains(@intFromPtr(ty.data.@"enum")), 1084 1085 .typeof_type => return typeWasDemotedToOpaque(c, ty.data.sub_type.*), 1086 .typeof_expr => return typeWasDemotedToOpaque(c, ty.data.expr.ty), 1087 .attributed => return typeWasDemotedToOpaque(c, ty.data.attributed.base), 1088 else => return false, 1089 } 1090 } 1091 1092 fn transCompoundStmt(c: *Context, scope: *Scope, compound: NodeIndex) TransError!ZigNode { 1093 var block_scope = try Scope.Block.init(c, scope, false); 1094 defer block_scope.deinit(); 1095 try transCompoundStmtInline(c, compound, &block_scope); 1096 return try block_scope.complete(c); 1097 } 1098 1099 fn transExpr(c: *Context, node: NodeIndex, result_used: ResultUsed) TransError!ZigNode { 1100 std.debug.assert(node != .none); 1101 const ty = c.tree.nodes.items(.ty)[@intFromEnum(node)]; 1102 if (c.tree.value_map.get(node)) |val| { 1103 // TODO handle other values 1104 const int = try transCreateNodeAPInt(c, val); 1105 const as_node = try ZigTag.as.create(c.arena, .{ 1106 .lhs = try transType(c, undefined, ty, .standard, undefined), 1107 .rhs = int, 1108 }); 1109 return maybeSuppressResult(c, result_used, as_node); 1110 } 1111 const node_tags = c.tree.nodes.items(.tag); 1112 switch (node_tags[@intFromEnum(node)]) { 1113 else => unreachable, // Not an expression. 1114 } 1115 return .none; 1116 } 1117 1118 fn transCreateNodeAPInt(c: *Context, int: aro.Value) !ZigNode { 1119 var space: aro.Interner.Tag.Int.BigIntSpace = undefined; 1120 var big = int.toBigInt(&space, c.comp); 1121 const is_negative = !big.positive; 1122 big.positive = true; 1123 1124 const str = big.toStringAlloc(c.arena, 10, .lower) catch |err| switch (err) { 1125 error.OutOfMemory => return error.OutOfMemory, 1126 }; 1127 const res = try ZigTag.integer_literal.create(c.arena, str); 1128 if (is_negative) return ZigTag.negate.create(c.arena, res); 1129 return res; 1130 } 1131 1132 pub const PatternList = struct { 1133 patterns: []Pattern, 1134 1135 /// Templates must be function-like macros 1136 /// first element is macro source, second element is the name of the function 1137 /// in std.lib.zig.c_translation.Macros which implements it 1138 const templates = [_][2][]const u8{ 1139 [2][]const u8{ "f_SUFFIX(X) (X ## f)", "F_SUFFIX" }, 1140 [2][]const u8{ "F_SUFFIX(X) (X ## F)", "F_SUFFIX" }, 1141 1142 [2][]const u8{ "u_SUFFIX(X) (X ## u)", "U_SUFFIX" }, 1143 [2][]const u8{ "U_SUFFIX(X) (X ## U)", "U_SUFFIX" }, 1144 1145 [2][]const u8{ "l_SUFFIX(X) (X ## l)", "L_SUFFIX" }, 1146 [2][]const u8{ "L_SUFFIX(X) (X ## L)", "L_SUFFIX" }, 1147 1148 [2][]const u8{ "ul_SUFFIX(X) (X ## ul)", "UL_SUFFIX" }, 1149 [2][]const u8{ "uL_SUFFIX(X) (X ## uL)", "UL_SUFFIX" }, 1150 [2][]const u8{ "Ul_SUFFIX(X) (X ## Ul)", "UL_SUFFIX" }, 1151 [2][]const u8{ "UL_SUFFIX(X) (X ## UL)", "UL_SUFFIX" }, 1152 1153 [2][]const u8{ "ll_SUFFIX(X) (X ## ll)", "LL_SUFFIX" }, 1154 [2][]const u8{ "LL_SUFFIX(X) (X ## LL)", "LL_SUFFIX" }, 1155 1156 [2][]const u8{ "ull_SUFFIX(X) (X ## ull)", "ULL_SUFFIX" }, 1157 [2][]const u8{ "uLL_SUFFIX(X) (X ## uLL)", "ULL_SUFFIX" }, 1158 [2][]const u8{ "Ull_SUFFIX(X) (X ## Ull)", "ULL_SUFFIX" }, 1159 [2][]const u8{ "ULL_SUFFIX(X) (X ## ULL)", "ULL_SUFFIX" }, 1160 1161 [2][]const u8{ "f_SUFFIX(X) X ## f", "F_SUFFIX" }, 1162 [2][]const u8{ "F_SUFFIX(X) X ## F", "F_SUFFIX" }, 1163 1164 [2][]const u8{ "u_SUFFIX(X) X ## u", "U_SUFFIX" }, 1165 [2][]const u8{ "U_SUFFIX(X) X ## U", "U_SUFFIX" }, 1166 1167 [2][]const u8{ "l_SUFFIX(X) X ## l", "L_SUFFIX" }, 1168 [2][]const u8{ "L_SUFFIX(X) X ## L", "L_SUFFIX" }, 1169 1170 [2][]const u8{ "ul_SUFFIX(X) X ## ul", "UL_SUFFIX" }, 1171 [2][]const u8{ "uL_SUFFIX(X) X ## uL", "UL_SUFFIX" }, 1172 [2][]const u8{ "Ul_SUFFIX(X) X ## Ul", "UL_SUFFIX" }, 1173 [2][]const u8{ "UL_SUFFIX(X) X ## UL", "UL_SUFFIX" }, 1174 1175 [2][]const u8{ "ll_SUFFIX(X) X ## ll", "LL_SUFFIX" }, 1176 [2][]const u8{ "LL_SUFFIX(X) X ## LL", "LL_SUFFIX" }, 1177 1178 [2][]const u8{ "ull_SUFFIX(X) X ## ull", "ULL_SUFFIX" }, 1179 [2][]const u8{ "uLL_SUFFIX(X) X ## uLL", "ULL_SUFFIX" }, 1180 [2][]const u8{ "Ull_SUFFIX(X) X ## Ull", "ULL_SUFFIX" }, 1181 [2][]const u8{ "ULL_SUFFIX(X) X ## ULL", "ULL_SUFFIX" }, 1182 1183 [2][]const u8{ "CAST_OR_CALL(X, Y) (X)(Y)", "CAST_OR_CALL" }, 1184 [2][]const u8{ "CAST_OR_CALL(X, Y) ((X)(Y))", "CAST_OR_CALL" }, 1185 1186 [2][]const u8{ 1187 \\wl_container_of(ptr, sample, member) \ 1188 \\(__typeof__(sample))((char *)(ptr) - \ 1189 \\ offsetof(__typeof__(*sample), member)) 1190 , 1191 "WL_CONTAINER_OF", 1192 }, 1193 1194 [2][]const u8{ "IGNORE_ME(X) ((void)(X))", "DISCARD" }, 1195 [2][]const u8{ "IGNORE_ME(X) (void)(X)", "DISCARD" }, 1196 [2][]const u8{ "IGNORE_ME(X) ((const void)(X))", "DISCARD" }, 1197 [2][]const u8{ "IGNORE_ME(X) (const void)(X)", "DISCARD" }, 1198 [2][]const u8{ "IGNORE_ME(X) ((volatile void)(X))", "DISCARD" }, 1199 [2][]const u8{ "IGNORE_ME(X) (volatile void)(X)", "DISCARD" }, 1200 [2][]const u8{ "IGNORE_ME(X) ((const volatile void)(X))", "DISCARD" }, 1201 [2][]const u8{ "IGNORE_ME(X) (const volatile void)(X)", "DISCARD" }, 1202 [2][]const u8{ "IGNORE_ME(X) ((volatile const void)(X))", "DISCARD" }, 1203 [2][]const u8{ "IGNORE_ME(X) (volatile const void)(X)", "DISCARD" }, 1204 }; 1205 1206 /// Assumes that `ms` represents a tokenized function-like macro. 1207 fn buildArgsHash(allocator: mem.Allocator, ms: MacroSlicer, hash: *ArgsPositionMap) MacroProcessingError!void { 1208 assert(ms.tokens.len > 2); 1209 assert(ms.tokens[0].id.isMacroIdentifier()); 1210 assert(ms.tokens[1].id == .l_paren); 1211 1212 var i: usize = 2; 1213 while (true) : (i += 1) { 1214 const token = ms.tokens[i]; 1215 switch (token.id) { 1216 .r_paren => break, 1217 .comma => continue, 1218 .identifier, .extended_identifier => { 1219 const identifier = ms.slice(token); 1220 try hash.put(allocator, identifier, i); 1221 }, 1222 else => return error.UnexpectedMacroToken, 1223 } 1224 } 1225 } 1226 1227 const Pattern = struct { 1228 tokens: []const CToken, 1229 source: []const u8, 1230 impl: []const u8, 1231 args_hash: ArgsPositionMap, 1232 1233 fn init(self: *Pattern, allocator: mem.Allocator, template: [2][]const u8) Error!void { 1234 const source = template[0]; 1235 const impl = template[1]; 1236 1237 var tok_list = std.array_list.Managed(CToken).init(allocator); 1238 defer tok_list.deinit(); 1239 try tokenizeMacro(source, &tok_list); 1240 const tokens = try allocator.dupe(CToken, tok_list.items); 1241 1242 self.* = .{ 1243 .tokens = tokens, 1244 .source = source, 1245 .impl = impl, 1246 .args_hash = .{}, 1247 }; 1248 const ms = MacroSlicer{ .source = source, .tokens = tokens }; 1249 buildArgsHash(allocator, ms, &self.args_hash) catch |err| switch (err) { 1250 error.UnexpectedMacroToken => unreachable, 1251 else => |e| return e, 1252 }; 1253 } 1254 1255 fn deinit(self: *Pattern, allocator: mem.Allocator) void { 1256 self.args_hash.deinit(allocator); 1257 allocator.free(self.tokens); 1258 } 1259 1260 /// This function assumes that `ms` has already been validated to contain a function-like 1261 /// macro, and that the parsed template macro in `self` also contains a function-like 1262 /// macro. Please review this logic carefully if changing that assumption. Two 1263 /// function-like macros are considered equivalent if and only if they contain the same 1264 /// list of tokens, modulo parameter names. 1265 pub fn isEquivalent(self: Pattern, ms: MacroSlicer, args_hash: ArgsPositionMap) bool { 1266 if (self.tokens.len != ms.tokens.len) return false; 1267 if (args_hash.count() != self.args_hash.count()) return false; 1268 1269 var i: usize = 2; 1270 while (self.tokens[i].id != .r_paren) : (i += 1) {} 1271 1272 const pattern_slicer = MacroSlicer{ .source = self.source, .tokens = self.tokens }; 1273 while (i < self.tokens.len) : (i += 1) { 1274 const pattern_token = self.tokens[i]; 1275 const macro_token = ms.tokens[i]; 1276 if (pattern_token.id != macro_token.id) return false; 1277 1278 const pattern_bytes = pattern_slicer.slice(pattern_token); 1279 const macro_bytes = ms.slice(macro_token); 1280 switch (pattern_token.id) { 1281 .identifier, .extended_identifier => { 1282 const pattern_arg_index = self.args_hash.get(pattern_bytes); 1283 const macro_arg_index = args_hash.get(macro_bytes); 1284 1285 if (pattern_arg_index == null and macro_arg_index == null) { 1286 if (!mem.eql(u8, pattern_bytes, macro_bytes)) return false; 1287 } else if (pattern_arg_index != null and macro_arg_index != null) { 1288 if (pattern_arg_index.? != macro_arg_index.?) return false; 1289 } else { 1290 return false; 1291 } 1292 }, 1293 .string_literal, .char_literal, .pp_num => { 1294 if (!mem.eql(u8, pattern_bytes, macro_bytes)) return false; 1295 }, 1296 else => { 1297 // other tags correspond to keywords and operators that do not contain a "payload" 1298 // that can vary 1299 }, 1300 } 1301 } 1302 return true; 1303 } 1304 }; 1305 1306 pub fn init(allocator: mem.Allocator) Error!PatternList { 1307 const patterns = try allocator.alloc(Pattern, templates.len); 1308 for (templates, 0..) |template, i| { 1309 try patterns[i].init(allocator, template); 1310 } 1311 return PatternList{ .patterns = patterns }; 1312 } 1313 1314 pub fn deinit(self: *PatternList, allocator: mem.Allocator) void { 1315 for (self.patterns) |*pattern| pattern.deinit(allocator); 1316 allocator.free(self.patterns); 1317 } 1318 1319 pub fn match(self: PatternList, allocator: mem.Allocator, ms: MacroSlicer) Error!?Pattern { 1320 var args_hash: ArgsPositionMap = .{}; 1321 defer args_hash.deinit(allocator); 1322 1323 buildArgsHash(allocator, ms, &args_hash) catch |err| switch (err) { 1324 error.UnexpectedMacroToken => return null, 1325 else => |e| return e, 1326 }; 1327 1328 for (self.patterns) |pattern| if (pattern.isEquivalent(ms, args_hash)) return pattern; 1329 return null; 1330 } 1331 }; 1332 1333 pub const MacroSlicer = struct { 1334 source: []const u8, 1335 tokens: []const CToken, 1336 1337 pub fn slice(self: MacroSlicer, token: CToken) []const u8 { 1338 return self.source[token.start..token.end]; 1339 } 1340 }; 1341 1342 // Maps macro parameter names to token position, for determining if different 1343 // identifiers refer to the same positional argument in different macros. 1344 pub const ArgsPositionMap = std.StringArrayHashMapUnmanaged(usize); 1345 1346 pub const Error = std.mem.Allocator.Error; 1347 pub const MacroProcessingError = Error || error{UnexpectedMacroToken}; 1348 pub const TypeError = Error || error{UnsupportedType}; 1349 pub const TransError = TypeError || error{UnsupportedTranslation}; 1350 1351 pub const SymbolTable = std.StringArrayHashMap(ast.Node); 1352 pub const AliasList = std.array_list.Managed(struct { 1353 alias: []const u8, 1354 name: []const u8, 1355 }); 1356 1357 pub const ResultUsed = enum { 1358 used, 1359 unused, 1360 }; 1361 1362 pub fn ScopeExtra(comptime ScopeExtraContext: type, comptime ScopeExtraType: type) type { 1363 return struct { 1364 id: Id, 1365 parent: ?*ScopeExtraScope, 1366 1367 const ScopeExtraScope = @This(); 1368 1369 pub const Id = enum { 1370 block, 1371 root, 1372 condition, 1373 loop, 1374 do_loop, 1375 }; 1376 1377 /// Used for the scope of condition expressions, for example `if (cond)`. 1378 /// The block is lazily initialised because it is only needed for rare 1379 /// cases of comma operators being used. 1380 pub const Condition = struct { 1381 base: ScopeExtraScope, 1382 block: ?Block = null, 1383 1384 pub fn getBlockScope(self: *Condition, c: *ScopeExtraContext) !*Block { 1385 if (self.block) |*b| return b; 1386 self.block = try Block.init(c, &self.base, true); 1387 return &self.block.?; 1388 } 1389 1390 pub fn deinit(self: *Condition) void { 1391 if (self.block) |*b| b.deinit(); 1392 } 1393 }; 1394 1395 /// Represents an in-progress Node.Block. This struct is stack-allocated. 1396 /// When it is deinitialized, it produces an Node.Block which is allocated 1397 /// into the main arena. 1398 pub const Block = struct { 1399 base: ScopeExtraScope, 1400 statements: std.array_list.Managed(ast.Node), 1401 variables: AliasList, 1402 mangle_count: u32 = 0, 1403 label: ?[]const u8 = null, 1404 1405 /// By default all variables are discarded, since we do not know in advance if they 1406 /// will be used. This maps the variable's name to the Discard payload, so that if 1407 /// the variable is subsequently referenced we can indicate that the discard should 1408 /// be skipped during the intermediate AST -> Zig AST render step. 1409 variable_discards: std.StringArrayHashMap(*ast.Payload.Discard), 1410 1411 /// When the block corresponds to a function, keep track of the return type 1412 /// so that the return expression can be cast, if necessary 1413 return_type: ?ScopeExtraType = null, 1414 1415 /// C static local variables are wrapped in a block-local struct. The struct 1416 /// is named after the (mangled) variable name, the Zig variable within the 1417 /// struct itself is given this name. 1418 pub const static_inner_name = "static"; 1419 1420 /// C extern variables declared within a block are wrapped in a block-local 1421 /// struct. The struct is named ExternLocal_[variable_name], the Zig variable 1422 /// within the struct itself is [variable_name] by neccessity since it's an 1423 /// extern reference to an existing symbol. 1424 pub const extern_inner_prepend = "ExternLocal"; 1425 1426 pub fn init(c: *ScopeExtraContext, parent: *ScopeExtraScope, labeled: bool) !Block { 1427 var blk = Block{ 1428 .base = .{ 1429 .id = .block, 1430 .parent = parent, 1431 }, 1432 .statements = std.array_list.Managed(ast.Node).init(c.gpa), 1433 .variables = AliasList.init(c.gpa), 1434 .variable_discards = std.StringArrayHashMap(*ast.Payload.Discard).init(c.gpa), 1435 }; 1436 if (labeled) { 1437 blk.label = try blk.makeMangledName(c, "blk"); 1438 } 1439 return blk; 1440 } 1441 1442 pub fn deinit(self: *Block) void { 1443 self.statements.deinit(); 1444 self.variables.deinit(); 1445 self.variable_discards.deinit(); 1446 self.* = undefined; 1447 } 1448 1449 pub fn complete(self: *Block, c: *ScopeExtraContext) !ast.Node { 1450 if (self.base.parent.?.id == .do_loop) { 1451 // We reserve 1 extra statement if the parent is a do_loop. This is in case of 1452 // do while, we want to put `if (cond) break;` at the end. 1453 const alloc_len = self.statements.items.len + @intFromBool(self.base.parent.?.id == .do_loop); 1454 var stmts = try c.arena.alloc(ast.Node, alloc_len); 1455 stmts.len = self.statements.items.len; 1456 @memcpy(stmts[0..self.statements.items.len], self.statements.items); 1457 return ast.Node.Tag.block.create(c.arena, .{ 1458 .label = self.label, 1459 .stmts = stmts, 1460 }); 1461 } 1462 if (self.statements.items.len == 0) return ast.Node.Tag.empty_block.init(); 1463 return ast.Node.Tag.block.create(c.arena, .{ 1464 .label = self.label, 1465 .stmts = try c.arena.dupe(ast.Node, self.statements.items), 1466 }); 1467 } 1468 1469 /// Given the desired name, return a name that does not shadow anything from outer scopes. 1470 /// Inserts the returned name into the scope. 1471 /// The name will not be visible to callers of getAlias. 1472 pub fn reserveMangledName(scope: *Block, c: *ScopeExtraContext, name: []const u8) ![]const u8 { 1473 return scope.createMangledName(c, name, true); 1474 } 1475 1476 /// Same as reserveMangledName, but enables the alias immediately. 1477 pub fn makeMangledName(scope: *Block, c: *ScopeExtraContext, name: []const u8) ![]const u8 { 1478 return scope.createMangledName(c, name, false); 1479 } 1480 1481 pub fn createMangledName(scope: *Block, c: *ScopeExtraContext, name: []const u8, reservation: bool) ![]const u8 { 1482 const name_copy = try c.arena.dupe(u8, name); 1483 var proposed_name = name_copy; 1484 while (scope.contains(proposed_name)) { 1485 scope.mangle_count += 1; 1486 proposed_name = try std.fmt.allocPrint(c.arena, "{s}_{d}", .{ name, scope.mangle_count }); 1487 } 1488 const new_mangle = try scope.variables.addOne(); 1489 if (reservation) { 1490 new_mangle.* = .{ .name = name_copy, .alias = name_copy }; 1491 } else { 1492 new_mangle.* = .{ .name = name_copy, .alias = proposed_name }; 1493 } 1494 return proposed_name; 1495 } 1496 1497 pub fn getAlias(scope: *Block, name: []const u8) []const u8 { 1498 for (scope.variables.items) |p| { 1499 if (std.mem.eql(u8, p.name, name)) 1500 return p.alias; 1501 } 1502 return scope.base.parent.?.getAlias(name); 1503 } 1504 1505 /// Finds the (potentially) mangled struct name for a locally scoped extern variable or function given the original declaration name. 1506 /// 1507 /// Block scoped extern declarations translate to: 1508 /// const MangledStructName = struct {extern [qualifiers] original_extern_variable_name: [type]}; 1509 /// This finds MangledStructName given original_extern_variable_name for referencing correctly in transDeclRefExpr() 1510 pub fn getLocalExternAlias(scope: *Block, name: []const u8) ?[]const u8 { 1511 for (scope.statements.items) |node| { 1512 switch (node.tag()) { 1513 .extern_local_var => { 1514 const parent_node = node.castTag(.extern_local_var).?; 1515 const init_node = parent_node.data.init.castTag(.var_decl).?; 1516 if (std.mem.eql(u8, init_node.data.name, name)) { 1517 return parent_node.data.name; 1518 } 1519 }, 1520 .extern_local_fn => { 1521 const parent_node = node.castTag(.extern_local_fn).?; 1522 const init_node = parent_node.data.init.castTag(.func).?; 1523 if (std.mem.eql(u8, init_node.data.name.?, name)) { 1524 return parent_node.data.name; 1525 } 1526 }, 1527 else => {}, 1528 } 1529 } 1530 return null; 1531 } 1532 1533 pub fn localContains(scope: *Block, name: []const u8) bool { 1534 for (scope.variables.items) |p| { 1535 if (std.mem.eql(u8, p.alias, name)) 1536 return true; 1537 } 1538 return false; 1539 } 1540 1541 pub fn contains(scope: *Block, name: []const u8) bool { 1542 if (scope.localContains(name)) 1543 return true; 1544 return scope.base.parent.?.contains(name); 1545 } 1546 1547 pub fn discardVariable(scope: *Block, c: *ScopeExtraContext, name: []const u8) Error!void { 1548 const name_node = try ast.Node.Tag.identifier.create(c.arena, name); 1549 const discard = try ast.Node.Tag.discard.create(c.arena, .{ .should_skip = false, .value = name_node }); 1550 try scope.statements.append(discard); 1551 try scope.variable_discards.putNoClobber(name, discard.castTag(.discard).?); 1552 } 1553 }; 1554 1555 pub const Root = struct { 1556 base: ScopeExtraScope, 1557 sym_table: SymbolTable, 1558 blank_macros: std.StringArrayHashMap(void), 1559 context: *ScopeExtraContext, 1560 nodes: std.array_list.Managed(ast.Node), 1561 1562 pub fn init(c: *ScopeExtraContext) Root { 1563 return .{ 1564 .base = .{ 1565 .id = .root, 1566 .parent = null, 1567 }, 1568 .sym_table = SymbolTable.init(c.gpa), 1569 .blank_macros = std.StringArrayHashMap(void).init(c.gpa), 1570 .context = c, 1571 .nodes = std.array_list.Managed(ast.Node).init(c.gpa), 1572 }; 1573 } 1574 1575 pub fn deinit(scope: *Root) void { 1576 scope.sym_table.deinit(); 1577 scope.blank_macros.deinit(); 1578 scope.nodes.deinit(); 1579 } 1580 1581 /// Check if the global scope contains this name, without looking into the "future", e.g. 1582 /// ignore the preprocessed decl and macro names. 1583 pub fn containsNow(scope: *Root, name: []const u8) bool { 1584 return scope.sym_table.contains(name); 1585 } 1586 1587 /// Check if the global scope contains the name, includes all decls that haven't been translated yet. 1588 pub fn contains(scope: *Root, name: []const u8) bool { 1589 return scope.containsNow(name) or scope.context.global_names.contains(name) or scope.context.weak_global_names.contains(name); 1590 } 1591 }; 1592 1593 pub fn findBlockScope(inner: *ScopeExtraScope, c: *ScopeExtraContext) !*Block { 1594 var scope = inner; 1595 while (true) { 1596 switch (scope.id) { 1597 .root => unreachable, 1598 .block => return @fieldParentPtr("base", scope), 1599 .condition => return @as(*Condition, @fieldParentPtr("base", scope)).getBlockScope(c), 1600 else => scope = scope.parent.?, 1601 } 1602 } 1603 } 1604 1605 pub fn findBlockReturnType(inner: *ScopeExtraScope) ScopeExtraType { 1606 var scope = inner; 1607 while (true) { 1608 switch (scope.id) { 1609 .root => unreachable, 1610 .block => { 1611 const block: *Block = @fieldParentPtr("base", scope); 1612 if (block.return_type) |ty| return ty; 1613 scope = scope.parent.?; 1614 }, 1615 else => scope = scope.parent.?, 1616 } 1617 } 1618 } 1619 1620 pub fn getAlias(scope: *ScopeExtraScope, name: []const u8) []const u8 { 1621 return switch (scope.id) { 1622 .root => name, 1623 .block => @as(*Block, @fieldParentPtr("base", scope)).getAlias(name), 1624 .loop, .do_loop, .condition => scope.parent.?.getAlias(name), 1625 }; 1626 } 1627 1628 pub fn getLocalExternAlias(scope: *ScopeExtraScope, name: []const u8) ?[]const u8 { 1629 return switch (scope.id) { 1630 .root => null, 1631 .block => ret: { 1632 const block = @as(*Block, @fieldParentPtr("base", scope)); 1633 const alias_name = block.getLocalExternAlias(name); 1634 if (alias_name) |_alias_name| { 1635 break :ret _alias_name; 1636 } 1637 break :ret scope.parent.?.getLocalExternAlias(name); 1638 }, 1639 .loop, .do_loop, .condition => scope.parent.?.getLocalExternAlias(name), 1640 }; 1641 } 1642 1643 pub fn contains(scope: *ScopeExtraScope, name: []const u8) bool { 1644 return switch (scope.id) { 1645 .root => @as(*Root, @fieldParentPtr("base", scope)).contains(name), 1646 .block => @as(*Block, @fieldParentPtr("base", scope)).contains(name), 1647 .loop, .do_loop, .condition => scope.parent.?.contains(name), 1648 }; 1649 } 1650 1651 pub fn getBreakableScope(inner: *ScopeExtraScope) *ScopeExtraScope { 1652 var scope = inner; 1653 while (true) { 1654 switch (scope.id) { 1655 .root => unreachable, 1656 .loop, .do_loop => return scope, 1657 else => scope = scope.parent.?, 1658 } 1659 } 1660 } 1661 1662 /// Appends a node to the first block scope if inside a function, or to the root tree if not. 1663 pub fn appendNode(inner: *ScopeExtraScope, node: ast.Node) !void { 1664 var scope = inner; 1665 while (true) { 1666 switch (scope.id) { 1667 .root => { 1668 const root: *Root = @fieldParentPtr("base", scope); 1669 return root.nodes.append(node); 1670 }, 1671 .block => { 1672 const block: *Block = @fieldParentPtr("base", scope); 1673 return block.statements.append(node); 1674 }, 1675 else => scope = scope.parent.?, 1676 } 1677 } 1678 } 1679 1680 pub fn skipVariableDiscard(inner: *ScopeExtraScope, name: []const u8) void { 1681 if (true) { 1682 // TODO: due to 'local variable is never mutated' errors, we can 1683 // only skip discards if a variable is used as an lvalue, which 1684 // we don't currently have detection for in translate-c. 1685 // Once #17584 is completed, perhaps we can do away with this 1686 // logic entirely, and instead rely on render to fixup code. 1687 return; 1688 } 1689 var scope = inner; 1690 while (true) { 1691 switch (scope.id) { 1692 .root => return, 1693 .block => { 1694 const block: *Block = @fieldParentPtr("base", scope); 1695 if (block.variable_discards.get(name)) |discard| { 1696 discard.data.should_skip = true; 1697 return; 1698 } 1699 }, 1700 else => {}, 1701 } 1702 scope = scope.parent.?; 1703 } 1704 } 1705 }; 1706 } 1707 1708 pub fn tokenizeMacro(source: []const u8, tok_list: *std.array_list.Managed(CToken)) Error!void { 1709 var tokenizer: aro.Tokenizer = .{ 1710 .buf = source, 1711 .source = .unused, 1712 .langopts = .{}, 1713 }; 1714 while (true) { 1715 const tok = tokenizer.next(); 1716 switch (tok.id) { 1717 .whitespace => continue, 1718 .nl, .eof => { 1719 try tok_list.append(tok); 1720 break; 1721 }, 1722 else => {}, 1723 } 1724 try tok_list.append(tok); 1725 } 1726 } 1727 1728 // Testing here instead of test/translate_c.zig allows us to also test that the 1729 // mapped function exists in `std.zig.c_translation.Macros` 1730 test "Macro matching" { 1731 const testing = std.testing; 1732 const helper = struct { 1733 const MacroFunctions = std.zig.c_translation.Macros; 1734 fn checkMacro(allocator: mem.Allocator, pattern_list: PatternList, source: []const u8, comptime expected_match: ?[]const u8) !void { 1735 var tok_list = std.array_list.Managed(CToken).init(allocator); 1736 defer tok_list.deinit(); 1737 try tokenizeMacro(source, &tok_list); 1738 const macro_slicer: MacroSlicer = .{ .source = source, .tokens = tok_list.items }; 1739 const matched = try pattern_list.match(allocator, macro_slicer); 1740 if (expected_match) |expected| { 1741 try testing.expectEqualStrings(expected, matched.?.impl); 1742 try testing.expect(@hasDecl(MacroFunctions, expected)); 1743 } else { 1744 try testing.expectEqual(@as(@TypeOf(matched), null), matched); 1745 } 1746 } 1747 }; 1748 const allocator = std.testing.allocator; 1749 var pattern_list = try PatternList.init(allocator); 1750 defer pattern_list.deinit(allocator); 1751 1752 try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## F)", "F_SUFFIX"); 1753 try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## U)", "U_SUFFIX"); 1754 try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## L)", "L_SUFFIX"); 1755 try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## LL)", "LL_SUFFIX"); 1756 try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## UL)", "UL_SUFFIX"); 1757 try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## ULL)", "ULL_SUFFIX"); 1758 try helper.checkMacro(allocator, pattern_list, 1759 \\container_of(a, b, c) \ 1760 \\(__typeof__(b))((char *)(a) - \ 1761 \\ offsetof(__typeof__(*b), c)) 1762 , "WL_CONTAINER_OF"); 1763 1764 try helper.checkMacro(allocator, pattern_list, "NO_MATCH(X, Y) (X + Y)", null); 1765 try helper.checkMacro(allocator, pattern_list, "CAST_OR_CALL(X, Y) (X)(Y)", "CAST_OR_CALL"); 1766 try helper.checkMacro(allocator, pattern_list, "CAST_OR_CALL(X, Y) ((X)(Y))", "CAST_OR_CALL"); 1767 try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (void)(X)", "DISCARD"); 1768 try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((void)(X))", "DISCARD"); 1769 try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (const void)(X)", "DISCARD"); 1770 try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((const void)(X))", "DISCARD"); 1771 try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (volatile void)(X)", "DISCARD"); 1772 try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((volatile void)(X))", "DISCARD"); 1773 try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (const volatile void)(X)", "DISCARD"); 1774 try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((const volatile void)(X))", "DISCARD"); 1775 try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (volatile const void)(X)", "DISCARD"); 1776 try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((volatile const void)(X))", "DISCARD"); 1777 } 1778 1779 /// Renders errors and fatal errors + associated notes (e.g. "expanded from here"); does not render warnings or associated notes 1780 /// Terminates with exit code 1 1781 fn renderErrorsAndExit(comp: *aro.Compilation) noreturn { 1782 defer std.process.exit(1); 1783 1784 var buffer: [1000]u8 = undefined; 1785 var writer = aro.Diagnostics.defaultMsgWriter(std.io.tty.detectConfig(std.fs.File.stderr()), &buffer); 1786 defer writer.deinit(); // writer deinit must run *before* exit so that stderr is flushed 1787 1788 var saw_error = false; 1789 for (comp.diagnostics.list.items) |msg| { 1790 switch (msg.kind) { 1791 .@"error", .@"fatal error" => { 1792 saw_error = true; 1793 aro.Diagnostics.renderMessage(comp, &writer, msg); 1794 }, 1795 .warning => saw_error = false, 1796 .note => { 1797 if (saw_error) { 1798 aro.Diagnostics.renderMessage(comp, &writer, msg); 1799 } 1800 }, 1801 .off => {}, 1802 .default => unreachable, 1803 } 1804 } 1805 } 1806 1807 pub fn main() !void { 1808 var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator); 1809 defer arena_instance.deinit(); 1810 const arena = arena_instance.allocator(); 1811 1812 var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init; 1813 const gpa = general_purpose_allocator.allocator(); 1814 1815 const args = try std.process.argsAlloc(arena); 1816 1817 var aro_comp = aro.Compilation.init(gpa, std.fs.cwd()); 1818 defer aro_comp.deinit(); 1819 1820 var tree = translate(gpa, &aro_comp, args) catch |err| switch (err) { 1821 error.ParsingFailed, error.FatalError => renderErrorsAndExit(&aro_comp), 1822 error.OutOfMemory => return error.OutOfMemory, 1823 error.StreamTooLong => std.process.fatal("An input file was larger than 4GiB", .{}), 1824 }; 1825 defer tree.deinit(gpa); 1826 1827 const formatted = try tree.renderAlloc(arena); 1828 try std.fs.File.stdout().writeAll(formatted); 1829 return std.process.cleanExit(); 1830 }