diff --git a/lib/compiler/translate-c/MacroTranslator.zig b/lib/compiler/translate-c/MacroTranslator.zig new file mode 100644 index 0000000000000000000000000000000000000000..2d1824d8a57c234a71197bb057383d8af9cc1594 --- /dev/null +++ b/lib/compiler/translate-c/MacroTranslator.zig @@ -0,0 +1,1307 @@ +const std = @import("std"); +const math = std.math; +const mem = std.mem; +const assert = std.debug.assert; + +const aro = @import("aro"); +const CToken = aro.Tokenizer.Token; + +const ast = @import("ast.zig"); +const builtins = @import("builtins.zig"); +const ZigNode = ast.Node; +const ZigTag = ZigNode.Tag; +const Scope = @import("Scope.zig"); +const Translator = @import("Translator.zig"); + +const Error = Translator.Error; +pub const ParseError = Error || error{ParseError}; + +const MacroTranslator = @This(); + +t: *Translator, +macro: aro.Preprocessor.Macro, +name: []const u8, + +tokens: []const CToken, +source: []const u8, +i: usize = 0, +/// If an object macro references a global var it needs to be converted into +/// an inline function. +refs_var_decl: bool = false, + +fn peek(mt: *MacroTranslator) CToken.Id { + if (mt.i >= mt.tokens.len) return .eof; + return mt.tokens[mt.i].id; +} + +fn eat(mt: *MacroTranslator, expected_id: CToken.Id) bool { + if (mt.peek() == expected_id) { + mt.i += 1; + return true; + } + return false; +} + +fn expect(mt: *MacroTranslator, expected_id: CToken.Id) ParseError!void { + const next_id = mt.peek(); + if (next_id != expected_id and !(expected_id == .identifier and next_id == .extended_identifier)) { + try mt.fail( + "unable to translate C expr: expected '{s}' instead got '{s}'", + .{ expected_id.symbol(), next_id.symbol() }, + ); + return error.ParseError; + } + mt.i += 1; +} + +fn fail(mt: *MacroTranslator, comptime fmt: []const u8, args: anytype) !void { + return mt.t.failDeclExtra(&mt.t.global_scope.base, mt.macro.loc, mt.name, fmt, args); +} + +fn tokSlice(mt: *const MacroTranslator) []const u8 { + const tok = mt.tokens[mt.i]; + return mt.source[tok.start..tok.end]; +} + +pub fn transFnMacro(mt: *MacroTranslator) ParseError!void { + var block_scope = try Scope.Block.init(mt.t, &mt.t.global_scope.base, false); + defer block_scope.deinit(); + const scope = &block_scope.base; + + const fn_params = try mt.t.arena.alloc(ast.Payload.Param, mt.macro.params.len); + for (fn_params, mt.macro.params) |*param, param_name| { + const mangled_name = try block_scope.makeMangledName(param_name); + param.* = .{ + .is_noalias = false, + .name = mangled_name, + .type = ZigTag.@"anytype".init(), + }; + try block_scope.discardVariable(mangled_name); + } + + const expr = try mt.parseCExpr(scope); + const last = mt.peek(); + if (last != .eof) + return mt.fail("unable to translate C expr: unexpected token '{s}'", .{last.symbol()}); + + const typeof_arg = if (expr.castTag(.block)) |some| blk: { + const stmts = some.data.stmts; + const blk_last = stmts[stmts.len - 1]; + const br = blk_last.castTag(.break_val).?; + break :blk br.data.val; + } else expr; + + const return_type = ret: { + if (typeof_arg.castTag(.helper_call)) |some| { + if (std.mem.eql(u8, some.data.name, "cast")) { + break :ret some.data.args[0]; + } + } + if (typeof_arg.castTag(.std_mem_zeroinit)) |some| break :ret some.data.lhs; + if (typeof_arg.castTag(.std_mem_zeroes)) |some| break :ret some.data; + break :ret try ZigTag.typeof.create(mt.t.arena, typeof_arg); + }; + + const return_expr = try ZigTag.@"return".create(mt.t.arena, expr); + try block_scope.statements.append(mt.t.gpa, return_expr); + + const fn_decl = try ZigTag.pub_inline_fn.create(mt.t.arena, .{ + .name = mt.name, + .params = fn_params, + .return_type = return_type, + .body = try block_scope.complete(), + }); + try mt.t.addTopLevelDecl(mt.name, fn_decl); +} + +pub fn transMacro(mt: *MacroTranslator) ParseError!void { + const scope = &mt.t.global_scope.base; + + // Check if the macro only uses other blank macros. + while (true) { + switch (mt.peek()) { + .identifier, .extended_identifier => { + if (mt.t.global_scope.blank_macros.contains(mt.tokSlice())) { + mt.i += 1; + continue; + } + }, + .eof, .nl => { + try mt.t.global_scope.blank_macros.put(mt.t.gpa, mt.name, {}); + const init_node = try ZigTag.string_literal.create(mt.t.arena, "\"\""); + const var_decl = try ZigTag.pub_var_simple.create(mt.t.arena, .{ .name = mt.name, .init = init_node }); + try mt.t.addTopLevelDecl(mt.name, var_decl); + return; + }, + else => {}, + } + break; + } + + const init_node = try mt.parseCExpr(scope); + const last = mt.peek(); + if (last != .eof) + return mt.fail("unable to translate C expr: unexpected token '{s}'", .{last.symbol()}); + + const node = node: { + const var_decl = try ZigTag.pub_var_simple.create(mt.t.arena, .{ .name = mt.name, .init = init_node }); + + if (mt.t.getFnProto(var_decl)) |proto_node| { + // If a macro aliases a global variable which is a function pointer, we conclude that + // the macro is intended to represent a function that assumes the function pointer + // variable is non-null and calls it. + break :node try mt.createMacroFn(mt.name, var_decl, proto_node); + } else if (mt.refs_var_decl) { + const return_type = try ZigTag.typeof.create(mt.t.arena, init_node); + const return_expr = try ZigTag.@"return".create(mt.t.arena, init_node); + const block = try ZigTag.block_single.create(mt.t.arena, return_expr); + + const loc_str = try mt.t.locStr(mt.macro.loc); + const value = try std.fmt.allocPrint(mt.t.arena, "\n// {s}: warning: macro '{s}' contains a runtime value, translated to function", .{ loc_str, mt.name }); + try scope.appendNode(try ZigTag.warning.create(mt.t.arena, value)); + + break :node try ZigTag.pub_inline_fn.create(mt.t.arena, .{ + .name = mt.name, + .params = &.{}, + .return_type = return_type, + .body = block, + }); + } + + break :node var_decl; + }; + + try mt.t.addTopLevelDecl(mt.name, node); +} + +fn createMacroFn(mt: *MacroTranslator, name: []const u8, ref: ZigNode, proto_alias: *ast.Payload.Func) !ZigNode { + var fn_params = std.ArrayList(ast.Payload.Param).init(mt.t.gpa); + defer fn_params.deinit(); + + var block_scope = try Scope.Block.init(mt.t, &mt.t.global_scope.base, false); + defer block_scope.deinit(); + + for (proto_alias.data.params) |param| { + const param_name = try block_scope.makeMangledName(param.name orelse "arg"); + + try fn_params.append(.{ + .name = param_name, + .type = param.type, + .is_noalias = param.is_noalias, + }); + } + + const init = if (ref.castTag(.var_decl)) |v| + v.data.init.? + else if (ref.castTag(.var_simple) orelse ref.castTag(.pub_var_simple)) |v| + v.data.init + else + unreachable; + + const unwrap_expr = try ZigTag.unwrap.create(mt.t.arena, init); + const args = try mt.t.arena.alloc(ZigNode, fn_params.items.len); + for (fn_params.items, 0..) |param, i| { + args[i] = try ZigTag.identifier.create(mt.t.arena, param.name.?); + } + const call_expr = try ZigTag.call.create(mt.t.arena, .{ + .lhs = unwrap_expr, + .args = args, + }); + const return_expr = try ZigTag.@"return".create(mt.t.arena, call_expr); + const block = try ZigTag.block_single.create(mt.t.arena, return_expr); + + return ZigTag.pub_inline_fn.create(mt.t.arena, .{ + .name = name, + .params = try mt.t.arena.dupe(ast.Payload.Param, fn_params.items), + .return_type = proto_alias.data.return_type, + .body = block, + }); +} + +fn parseCExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { + // TODO parseCAssignExpr here + var block_scope = try Scope.Block.init(mt.t, scope, true); + defer block_scope.deinit(); + + const node = try mt.parseCCondExpr(&block_scope.base); + if (!mt.eat(.comma)) return node; + + var last = node; + while (true) { + // suppress result + const ignore = try ZigTag.discard.create(mt.t.arena, .{ .should_skip = false, .value = last }); + try block_scope.statements.append(mt.t.gpa, ignore); + + last = try mt.parseCCondExpr(&block_scope.base); + if (!mt.eat(.comma)) break; + } + + const break_node = try ZigTag.break_val.create(mt.t.arena, .{ + .label = block_scope.label, + .val = last, + }); + try block_scope.statements.append(mt.t.gpa, break_node); + return try block_scope.complete(); +} + +fn parseCNumLit(mt: *MacroTranslator) ParseError!ZigNode { + const lit_bytes = mt.tokSlice(); + mt.i += 1; + + var bytes = try std.ArrayListUnmanaged(u8).initCapacity(mt.t.arena, lit_bytes.len + 3); + + const prefix = aro.Tree.Token.NumberPrefix.fromString(lit_bytes); + switch (prefix) { + .binary => bytes.appendSliceAssumeCapacity("0b"), + .octal => bytes.appendSliceAssumeCapacity("0o"), + .hex => bytes.appendSliceAssumeCapacity("0x"), + .decimal => {}, + } + + const after_prefix = lit_bytes[prefix.stringLen()..]; + const after_int = for (after_prefix, 0..) |c, i| switch (c) { + '.' => { + if (i == 0) { + bytes.appendAssumeCapacity('0'); + } + break after_prefix[i..]; + }, + 'e', 'E' => { + if (prefix != .hex) break after_prefix[i..]; + bytes.appendAssumeCapacity(c); + }, + 'p', 'P' => break after_prefix[i..], + '0'...'9', 'a'...'d', 'A'...'D', 'f', 'F' => { + if (!prefix.digitAllowed(c)) break after_prefix[i..]; + bytes.appendAssumeCapacity(c); + }, + '\'' => { + bytes.appendAssumeCapacity('_'); + }, + else => break after_prefix[i..], + } else ""; + + const after_frac = frac: { + if (after_int.len == 0 or after_int[0] != '.') break :frac after_int; + bytes.appendAssumeCapacity('.'); + for (after_int[1..], 1..) |c, i| { + if (c == '\'') { + bytes.appendAssumeCapacity('_'); + continue; + } + if (!prefix.digitAllowed(c)) break :frac after_int[i..]; + bytes.appendAssumeCapacity(c); + } + break :frac ""; + }; + + const suffix_str = exponent: { + if (after_frac.len == 0) break :exponent after_frac; + switch (after_frac[0]) { + 'e', 'E' => {}, + 'p', 'P' => if (prefix != .hex) break :exponent after_frac, + else => break :exponent after_frac, + } + bytes.appendAssumeCapacity(after_frac[0]); + for (after_frac[1..], 1..) |c, i| switch (c) { + '+', '-', '0'...'9' => { + bytes.appendAssumeCapacity(c); + }, + '\'' => { + bytes.appendAssumeCapacity('_'); + }, + else => break :exponent after_frac[i..], + }; + break :exponent ""; + }; + + const is_float = after_int.len != suffix_str.len; + const suffix = aro.Tree.Token.NumberSuffix.fromString(suffix_str, if (is_float) .float else .int) orelse { + try mt.fail("invalid number suffix: '{s}'", .{suffix_str}); + return error.ParseError; + }; + if (suffix.isImaginary()) { + try mt.fail("TODO: imaginary literals", .{}); + return error.ParseError; + } + if (suffix.isBitInt()) { + try mt.fail("TODO: _BitInt literals", .{}); + return error.ParseError; + } + + if (is_float) { + const type_node = try ZigTag.type.create(mt.t.arena, switch (suffix) { + .F16 => "f16", + .F => "f32", + .None => "f64", + .L => "c_longdouble", + .W => "f80", + .Q, .F128 => "f128", + else => unreachable, + }); + const rhs = try ZigTag.float_literal.create(mt.t.arena, bytes.items); + return ZigTag.as.create(mt.t.arena, .{ .lhs = type_node, .rhs = rhs }); + } else { + const type_node = try ZigTag.type.create(mt.t.arena, switch (suffix) { + .None => "c_int", + .U => "c_uint", + .L => "c_long", + .UL => "c_ulong", + .LL => "c_longlong", + .ULL => "c_ulonglong", + else => unreachable, + }); + const value = std.fmt.parseInt(i128, bytes.items, 0) catch math.maxInt(i128); + + // make the output less noisy by skipping promoteIntLiteral where + // it's guaranteed to not be required because of C standard type constraints + const guaranteed_to_fit = switch (suffix) { + .None => math.cast(i16, value) != null, + .U => math.cast(u16, value) != null, + .L => math.cast(i32, value) != null, + .UL => math.cast(u32, value) != null, + .LL => math.cast(i64, value) != null, + .ULL => math.cast(u64, value) != null, + else => unreachable, + }; + + const literal_node = try ZigTag.integer_literal.create(mt.t.arena, bytes.items); + if (guaranteed_to_fit) { + return ZigTag.as.create(mt.t.arena, .{ .lhs = type_node, .rhs = literal_node }); + } else { + return mt.t.createHelperCallNode(.promoteIntLiteral, &.{ type_node, literal_node, try ZigTag.enum_literal.create(mt.t.arena, @tagName(prefix)) }); + } + } +} + +fn zigifyEscapeSequences(mt: *MacroTranslator, slice: []const u8) ![]const u8 { + var source = slice; + for (source, 0..) |c, i| { + if (c == '\"' or c == '\'') { + source = source[i..]; + break; + } + } + for (source) |c| { + if (c == '\\' or c == '\t') { + break; + } + } else return source; + const bytes = try mt.t.arena.alloc(u8, source.len * 2); + var state: enum { + start, + escape, + hex, + octal, + } = .start; + var i: usize = 0; + var count: u8 = 0; + var num: u8 = 0; + for (source) |c| { + switch (state) { + .escape => { + switch (c) { + 'n', 'r', 't', '\\', '\'', '\"' => { + bytes[i] = c; + }, + '0'...'7' => { + count += 1; + num += c - '0'; + state = .octal; + bytes[i] = 'x'; + }, + 'x' => { + state = .hex; + bytes[i] = 'x'; + }, + 'a' => { + bytes[i] = 'x'; + i += 1; + bytes[i] = '0'; + i += 1; + bytes[i] = '7'; + }, + 'b' => { + bytes[i] = 'x'; + i += 1; + bytes[i] = '0'; + i += 1; + bytes[i] = '8'; + }, + 'f' => { + bytes[i] = 'x'; + i += 1; + bytes[i] = '0'; + i += 1; + bytes[i] = 'C'; + }, + 'v' => { + bytes[i] = 'x'; + i += 1; + bytes[i] = '0'; + i += 1; + bytes[i] = 'B'; + }, + '?' => { + i -= 1; + bytes[i] = '?'; + }, + 'u', 'U' => { + try mt.fail("macro tokenizing failed: TODO unicode escape sequences", .{}); + return error.ParseError; + }, + else => { + try mt.fail("macro tokenizing failed: unknown escape sequence", .{}); + return error.ParseError; + }, + } + i += 1; + if (state == .escape) + state = .start; + }, + .start => { + if (c == '\t') { + bytes[i] = '\\'; + i += 1; + bytes[i] = 't'; + i += 1; + continue; + } + if (c == '\\') { + state = .escape; + } + bytes[i] = c; + i += 1; + }, + .hex => { + switch (c) { + '0'...'9' => { + num = std.math.mul(u8, num, 16) catch { + try mt.fail("macro tokenizing failed: hex literal overflowed", .{}); + return error.ParseError; + }; + num += c - '0'; + }, + 'a'...'f' => { + num = std.math.mul(u8, num, 16) catch { + try mt.fail("macro tokenizing failed: hex literal overflowed", .{}); + return error.ParseError; + }; + num += c - 'a' + 10; + }, + 'A'...'F' => { + num = std.math.mul(u8, num, 16) catch { + try mt.fail("macro tokenizing failed: hex literal overflowed", .{}); + return error.ParseError; + }; + num += c - 'A' + 10; + }, + else => { + i += std.fmt.printInt(bytes[i..], num, 16, .lower, .{ .fill = '0', .width = 2 }); + num = 0; + if (c == '\\') + state = .escape + else + state = .start; + bytes[i] = c; + i += 1; + }, + } + }, + .octal => { + const accept_digit = switch (c) { + // The maximum length of a octal literal is 3 digits + '0'...'7' => count < 3, + else => false, + }; + + if (accept_digit) { + count += 1; + num = std.math.mul(u8, num, 8) catch { + try mt.fail("macro tokenizing failed: octal literal overflowed", .{}); + return error.ParseError; + }; + num += c - '0'; + } else { + i += std.fmt.printInt(bytes[i..], num, 16, .lower, .{ .fill = '0', .width = 2 }); + num = 0; + count = 0; + if (c == '\\') + state = .escape + else + state = .start; + bytes[i] = c; + i += 1; + } + }, + } + } + if (state == .hex or state == .octal) { + i += std.fmt.printInt(bytes[i..], num, 16, .lower, .{ .fill = '0', .width = 2 }); + } + + return bytes[0..i]; +} + +/// non-ASCII characters (mt > 127) are also treated as non-printable by fmtSliceEscapeLower. +/// If a C string literal or char literal in a macro is not valid UTF-8, we need to escape +/// non-ASCII characters so that the Zig source we output will itself be UTF-8. +fn escapeUnprintables(mt: *MacroTranslator) ![]const u8 { + const slice = mt.tokSlice(); + mt.i += 1; + + const zigified = try mt.zigifyEscapeSequences(slice); + if (std.unicode.utf8ValidateSlice(zigified)) return zigified; + + const formatter = std.ascii.hexEscape(zigified, .lower); + const encoded_size = @as(usize, @intCast(std.fmt.count("{f}", .{formatter}))); + const output = try mt.t.arena.alloc(u8, encoded_size); + return std.fmt.bufPrint(output, "{f}", .{formatter}) catch |err| switch (err) { + error.NoSpaceLeft => unreachable, + else => |e| return e, + }; +} + +fn parseCPrimaryExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { + const tok = mt.peek(); + switch (tok) { + .char_literal, + .char_literal_utf_8, + .char_literal_utf_16, + .char_literal_utf_32, + .char_literal_wide, + => { + const slice = mt.tokSlice(); + if (slice[0] != '\'' or slice[1] == '\\' or slice.len == 3) { + return ZigTag.char_literal.create(mt.t.arena, try mt.escapeUnprintables()); + } else { + mt.i += 1; + + const str = try std.fmt.allocPrint(mt.t.arena, "0x{x}", .{slice[1 .. slice.len - 1]}); + return ZigTag.integer_literal.create(mt.t.arena, str); + } + }, + .string_literal, + .string_literal_utf_16, + .string_literal_utf_8, + .string_literal_utf_32, + .string_literal_wide, + => return ZigTag.string_literal.create(mt.t.arena, try mt.escapeUnprintables()), + .pp_num => return mt.parseCNumLit(), + .l_paren => { + mt.i += 1; + const inner_node = try mt.parseCExpr(scope); + + try mt.expect(.r_paren); + return inner_node; + }, + .macro_param, .macro_param_no_expand => { + const param = mt.macro.params[mt.tokens[mt.i].end]; + mt.i += 1; + + const mangled_name = scope.getAlias(param) orelse param; + return try ZigTag.identifier.create(mt.t.arena, mangled_name); + }, + .identifier, .extended_identifier => { + const slice = mt.tokSlice(); + mt.i += 1; + + const mangled_name = scope.getAlias(slice) orelse slice; + if (Translator.builtin_typedef_map.get(mangled_name)) |ty| { + return ZigTag.type.create(mt.t.arena, ty); + } + if (builtins.map.get(mangled_name)) |builtin| { + const builtin_identifier = try ZigTag.identifier.create(mt.t.arena, "__builtin"); + return ZigTag.field_access.create(mt.t.arena, .{ + .lhs = builtin_identifier, + .field_name = builtin.name, + }); + } + + const identifier = try ZigTag.identifier.create(mt.t.arena, mangled_name); + scope.skipVariableDiscard(mangled_name); + refs_var: { + const ident_node = mt.t.global_scope.sym_table.get(slice) orelse break :refs_var; + const var_decl_node = ident_node.castTag(.var_decl) orelse break :refs_var; + if (!var_decl_node.data.is_const) mt.refs_var_decl = true; + } + return identifier; + }, + else => {}, + } + + // for handling type macros (EVIL) + // TODO maybe detect and treat type macros as typedefs in parseCSpecifierQualifierList? + if (try mt.parseCTypeName(scope, true)) |type_name| { + return type_name; + } + + try mt.fail("unable to translate C expr: unexpected token '{s}'", .{tok.symbol()}); + return error.ParseError; +} + +fn macroIntFromBool(mt: *MacroTranslator, node: ZigNode) !ZigNode { + if (!node.isBoolRes()) return node; + + return ZigTag.int_from_bool.create(mt.t.arena, node); +} + +fn macroIntToBool(mt: *MacroTranslator, node: ZigNode) !ZigNode { + if (node.isBoolRes()) return node; + + if (node.tag() == .string_literal) { + // @intFromPtr(node) != 0 + const int_from_ptr = try ZigTag.int_from_ptr.create(mt.t.arena, node); + return ZigTag.not_equal.create(mt.t.arena, .{ .lhs = int_from_ptr, .rhs = ZigTag.zero_literal.init() }); + } + // node != 0 + return ZigTag.not_equal.create(mt.t.arena, .{ .lhs = node, .rhs = ZigTag.zero_literal.init() }); +} + +fn parseCCondExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { + const node = try mt.parseCOrExpr(scope); + if (!mt.eat(.question_mark)) return node; + + const then_body = try mt.parseCOrExpr(scope); + try mt.expect(.colon); + const else_body = try mt.parseCCondExpr(scope); + return ZigTag.@"if".create(mt.t.arena, .{ .cond = node, .then = then_body, .@"else" = else_body }); +} + +fn parseCOrExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { + var node = try mt.parseCAndExpr(scope); + while (mt.eat(.pipe_pipe)) { + const lhs = try mt.macroIntToBool(node); + const rhs = try mt.macroIntToBool(try mt.parseCAndExpr(scope)); + node = try ZigTag.@"or".create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); + } + return node; +} + +fn parseCAndExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { + var node = try mt.parseCBitOrExpr(scope); + while (mt.eat(.ampersand_ampersand)) { + const lhs = try mt.macroIntToBool(node); + const rhs = try mt.macroIntToBool(try mt.parseCBitOrExpr(scope)); + node = try ZigTag.@"and".create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); + } + return node; +} + +fn parseCBitOrExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { + var node = try mt.parseCBitXorExpr(scope); + while (mt.eat(.pipe)) { + const lhs = try mt.macroIntFromBool(node); + const rhs = try mt.macroIntFromBool(try mt.parseCBitXorExpr(scope)); + node = try ZigTag.bit_or.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); + } + return node; +} + +fn parseCBitXorExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { + var node = try mt.parseCBitAndExpr(scope); + while (mt.eat(.caret)) { + const lhs = try mt.macroIntFromBool(node); + const rhs = try mt.macroIntFromBool(try mt.parseCBitAndExpr(scope)); + node = try ZigTag.bit_xor.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); + } + return node; +} + +fn parseCBitAndExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { + var node = try mt.parseCEqExpr(scope); + while (mt.eat(.ampersand)) { + const lhs = try mt.macroIntFromBool(node); + const rhs = try mt.macroIntFromBool(try mt.parseCEqExpr(scope)); + node = try ZigTag.bit_and.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); + } + return node; +} + +fn parseCEqExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { + var node = try mt.parseCRelExpr(scope); + while (true) { + switch (mt.peek()) { + .bang_equal => { + mt.i += 1; + const lhs = try mt.macroIntFromBool(node); + const rhs = try mt.macroIntFromBool(try mt.parseCRelExpr(scope)); + node = try ZigTag.not_equal.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); + }, + .equal_equal => { + mt.i += 1; + const lhs = try mt.macroIntFromBool(node); + const rhs = try mt.macroIntFromBool(try mt.parseCRelExpr(scope)); + node = try ZigTag.equal.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); + }, + else => return node, + } + } +} + +fn parseCRelExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { + var node = try mt.parseCShiftExpr(scope); + while (true) { + switch (mt.peek()) { + .angle_bracket_right => { + mt.i += 1; + const lhs = try mt.macroIntFromBool(node); + const rhs = try mt.macroIntFromBool(try mt.parseCShiftExpr(scope)); + node = try ZigTag.greater_than.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); + }, + .angle_bracket_right_equal => { + mt.i += 1; + const lhs = try mt.macroIntFromBool(node); + const rhs = try mt.macroIntFromBool(try mt.parseCShiftExpr(scope)); + node = try ZigTag.greater_than_equal.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); + }, + .angle_bracket_left => { + mt.i += 1; + const lhs = try mt.macroIntFromBool(node); + const rhs = try mt.macroIntFromBool(try mt.parseCShiftExpr(scope)); + node = try ZigTag.less_than.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); + }, + .angle_bracket_left_equal => { + mt.i += 1; + const lhs = try mt.macroIntFromBool(node); + const rhs = try mt.macroIntFromBool(try mt.parseCShiftExpr(scope)); + node = try ZigTag.less_than_equal.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); + }, + else => return node, + } + } +} + +fn parseCShiftExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { + var node = try mt.parseCAddSubExpr(scope); + while (true) { + switch (mt.peek()) { + .angle_bracket_angle_bracket_left => { + mt.i += 1; + const lhs = try mt.macroIntFromBool(node); + const rhs = try mt.macroIntFromBool(try mt.parseCAddSubExpr(scope)); + node = try ZigTag.shl.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); + }, + .angle_bracket_angle_bracket_right => { + mt.i += 1; + const lhs = try mt.macroIntFromBool(node); + const rhs = try mt.macroIntFromBool(try mt.parseCAddSubExpr(scope)); + node = try ZigTag.shr.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); + }, + else => return node, + } + } +} + +fn parseCAddSubExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { + var node = try mt.parseCMulExpr(scope); + while (true) { + switch (mt.peek()) { + .plus => { + mt.i += 1; + const lhs = try mt.macroIntFromBool(node); + const rhs = try mt.macroIntFromBool(try mt.parseCMulExpr(scope)); + node = try ZigTag.add.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); + }, + .minus => { + mt.i += 1; + const lhs = try mt.macroIntFromBool(node); + const rhs = try mt.macroIntFromBool(try mt.parseCMulExpr(scope)); + node = try ZigTag.sub.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); + }, + else => return node, + } + } +} + +fn parseCMulExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { + var node = try mt.parseCCastExpr(scope); + while (true) { + switch (mt.peek()) { + .asterisk => { + mt.i += 1; + const lhs = try mt.macroIntFromBool(node); + const rhs = try mt.macroIntFromBool(try mt.parseCCastExpr(scope)); + node = try ZigTag.mul.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); + }, + .slash => { + mt.i += 1; + const lhs = try mt.macroIntFromBool(node); + const rhs = try mt.macroIntFromBool(try mt.parseCCastExpr(scope)); + node = try mt.t.createHelperCallNode(.div, &.{ lhs, rhs }); + }, + .percent => { + mt.i += 1; + const lhs = try mt.macroIntFromBool(node); + const rhs = try mt.macroIntFromBool(try mt.parseCCastExpr(scope)); + node = try mt.t.createHelperCallNode(.rem, &.{ lhs, rhs }); + }, + else => return node, + } + } +} + +fn parseCCastExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { + if (mt.eat(.l_paren)) { + if (try mt.parseCTypeName(scope, true)) |type_name| { + while (true) { + const next_tok = mt.peek(); + if (next_tok == .r_paren) { + mt.i += 1; + break; + } + // Skip trailing blank defined before the RParen. + if ((next_tok == .identifier or next_tok == .extended_identifier) and + mt.t.global_scope.blank_macros.contains(mt.tokSlice())) + { + mt.i += 1; + continue; + } + + try mt.fail( + "unable to translate C expr: expected ')' instead got '{s}'", + .{next_tok.symbol()}, + ); + return error.ParseError; + } + if (mt.peek() == .l_brace) { + // initializer list + return mt.parseCPostfixExpr(scope, type_name); + } + const node_to_cast = try mt.parseCCastExpr(scope); + return mt.t.createHelperCallNode(.cast, &.{ type_name, node_to_cast }); + } + mt.i -= 1; // l_paren + } + return mt.parseCUnaryExpr(scope); +} + +// allow_fail is set when unsure if we are parsing a type-name +fn parseCTypeName(mt: *MacroTranslator, scope: *Scope, allow_fail: bool) ParseError!?ZigNode { + if (try mt.parseCSpecifierQualifierList(scope, allow_fail)) |node| { + return try mt.parseCAbstractDeclarator(node); + } + return null; +} + +fn parseCSpecifierQualifierList(mt: *MacroTranslator, scope: *Scope, allow_fail: bool) ParseError!?ZigNode { + const tok = mt.peek(); + switch (tok) { + .macro_param, .macro_param_no_expand => { + const param = mt.macro.params[mt.tokens[mt.i].end]; + + // Assume that this is only a cast if the next token is ')' + // e.g. param)identifier + if (allow_fail and (mt.macro.tokens.len < mt.i + 3 or + mt.macro.tokens[mt.i + 1].id != .r_paren or + mt.macro.tokens[mt.i + 2].id != .identifier)) + return null; + + mt.i += 1; + const mangled_name = scope.getAlias(param) orelse param; + return try ZigTag.identifier.create(mt.t.arena, mangled_name); + }, + .identifier, .extended_identifier => { + const slice = mt.tokSlice(); + const mangled_name = scope.getAlias(slice) orelse slice; + + if (mt.t.global_scope.blank_macros.contains(slice)) { + mt.i += 1; + return try mt.parseCSpecifierQualifierList(scope, allow_fail); + } + + if (!allow_fail or mt.t.typedefs.contains(mangled_name)) { + mt.i += 1; + if (Translator.builtin_typedef_map.get(mangled_name)) |ty| { + return try ZigTag.type.create(mt.t.arena, ty); + } + if (builtins.map.get(mangled_name)) |builtin| { + const builtin_identifier = try ZigTag.identifier.create(mt.t.arena, "__builtin"); + return try ZigTag.field_access.create(mt.t.arena, .{ + .lhs = builtin_identifier, + .field_name = builtin.name, + }); + } + + return try ZigTag.identifier.create(mt.t.arena, mangled_name); + } + }, + .keyword_void => { + mt.i += 1; + return try ZigTag.type.create(mt.t.arena, "anyopaque"); + }, + .keyword_bool => { + mt.i += 1; + return try ZigTag.type.create(mt.t.arena, "bool"); + }, + .keyword_char, + .keyword_int, + .keyword_short, + .keyword_long, + .keyword_float, + .keyword_double, + .keyword_signed, + .keyword_unsigned, + .keyword_complex, + => return try mt.parseCNumericType(), + .keyword_enum, .keyword_struct, .keyword_union => { + const tag_name = mt.tokSlice(); + mt.i += 1; + + // struct Foo will be declared as struct_Foo by transRecordDecl + const identifier = mt.tokSlice(); + try mt.expect(.identifier); + + const name = try std.fmt.allocPrint(mt.t.arena, "{s}_{s}", .{ tag_name, identifier }); + return try ZigTag.identifier.create(mt.t.arena, name); + }, + else => {}, + } + + if (allow_fail) return null; + + try mt.fail("unable to translate C expr: unexpected token '{s}'", .{tok.symbol()}); + return error.ParseError; +} + +fn parseCNumericType(mt: *MacroTranslator) ParseError!ZigNode { + const KwCounter = struct { + double: u8 = 0, + long: u8 = 0, + int: u8 = 0, + float: u8 = 0, + short: u8 = 0, + char: u8 = 0, + unsigned: u8 = 0, + signed: u8 = 0, + complex: u8 = 0, + + fn eql(self: @This(), other: @This()) bool { + return std.meta.eql(self, other); + } + }; + + // Yes, these can be in *any* order + // This still doesn't cover cases where for example volatile is intermixed + + var kw = KwCounter{}; + // prevent overflow + var i: u8 = 0; + while (i < math.maxInt(u8)) : (i += 1) { + switch (mt.peek()) { + .keyword_double => kw.double += 1, + .keyword_long => kw.long += 1, + .keyword_int => kw.int += 1, + .keyword_float => kw.float += 1, + .keyword_short => kw.short += 1, + .keyword_char => kw.char += 1, + .keyword_unsigned => kw.unsigned += 1, + .keyword_signed => kw.signed += 1, + .keyword_complex => kw.complex += 1, + else => break, + } + mt.i += 1; + } + + if (kw.eql(.{ .int = 1 }) or kw.eql(.{ .signed = 1 }) or kw.eql(.{ .signed = 1, .int = 1 })) + return ZigTag.type.create(mt.t.arena, "c_int"); + + if (kw.eql(.{ .unsigned = 1 }) or kw.eql(.{ .unsigned = 1, .int = 1 })) + return ZigTag.type.create(mt.t.arena, "c_uint"); + + if (kw.eql(.{ .long = 1 }) or kw.eql(.{ .signed = 1, .long = 1 }) or kw.eql(.{ .long = 1, .int = 1 }) or kw.eql(.{ .signed = 1, .long = 1, .int = 1 })) + return ZigTag.type.create(mt.t.arena, "c_long"); + + if (kw.eql(.{ .unsigned = 1, .long = 1 }) or kw.eql(.{ .unsigned = 1, .long = 1, .int = 1 })) + return ZigTag.type.create(mt.t.arena, "c_ulong"); + + if (kw.eql(.{ .long = 2 }) or kw.eql(.{ .signed = 1, .long = 2 }) or kw.eql(.{ .long = 2, .int = 1 }) or kw.eql(.{ .signed = 1, .long = 2, .int = 1 })) + return ZigTag.type.create(mt.t.arena, "c_longlong"); + + if (kw.eql(.{ .unsigned = 1, .long = 2 }) or kw.eql(.{ .unsigned = 1, .long = 2, .int = 1 })) + return ZigTag.type.create(mt.t.arena, "c_ulonglong"); + + if (kw.eql(.{ .signed = 1, .char = 1 })) + return ZigTag.type.create(mt.t.arena, "i8"); + + if (kw.eql(.{ .char = 1 }) or kw.eql(.{ .unsigned = 1, .char = 1 })) + return ZigTag.type.create(mt.t.arena, "u8"); + + if (kw.eql(.{ .short = 1 }) or kw.eql(.{ .signed = 1, .short = 1 }) or kw.eql(.{ .short = 1, .int = 1 }) or kw.eql(.{ .signed = 1, .short = 1, .int = 1 })) + return ZigTag.type.create(mt.t.arena, "c_short"); + + if (kw.eql(.{ .unsigned = 1, .short = 1 }) or kw.eql(.{ .unsigned = 1, .short = 1, .int = 1 })) + return ZigTag.type.create(mt.t.arena, "c_ushort"); + + if (kw.eql(.{ .float = 1 })) + return ZigTag.type.create(mt.t.arena, "f32"); + + if (kw.eql(.{ .double = 1 })) + return ZigTag.type.create(mt.t.arena, "f64"); + + if (kw.eql(.{ .long = 1, .double = 1 })) { + try mt.fail("unable to translate: TODO long double", .{}); + return error.ParseError; + } + + if (kw.eql(.{ .float = 1, .complex = 1 })) { + try mt.fail("unable to translate: TODO _Complex", .{}); + return error.ParseError; + } + + if (kw.eql(.{ .double = 1, .complex = 1 })) { + try mt.fail("unable to translate: TODO _Complex", .{}); + return error.ParseError; + } + + if (kw.eql(.{ .long = 1, .double = 1, .complex = 1 })) { + try mt.fail("unable to translate: TODO _Complex", .{}); + return error.ParseError; + } + + try mt.fail("unable to translate: invalid numeric type", .{}); + return error.ParseError; +} + +fn parseCAbstractDeclarator(mt: *MacroTranslator, node: ZigNode) ParseError!ZigNode { + if (mt.eat(.asterisk)) { + if (node.castTag(.type)) |some| { + if (std.mem.eql(u8, some.data, "anyopaque")) { + const ptr = try ZigTag.single_pointer.create(mt.t.arena, .{ + .is_const = false, + .is_volatile = false, + .is_allowzero = false, + .elem_type = node, + }); + return ZigTag.optional_type.create(mt.t.arena, ptr); + } + } + return ZigTag.c_pointer.create(mt.t.arena, .{ + .is_const = false, + .is_volatile = false, + .is_allowzero = false, + .elem_type = node, + }); + } + return node; +} + +fn parseCPostfixExpr(mt: *MacroTranslator, scope: *Scope, type_name: ?ZigNode) ParseError!ZigNode { + var node = try mt.parseCPostfixExprInner(scope, type_name); + // In C the preprocessor would handle concatting strings while expanding macros. + // This should do approximately the same by concatting any strings and identifiers + // after a primary or postfix expression. + while (true) { + switch (mt.peek()) { + .string_literal, + .string_literal_utf_16, + .string_literal_utf_8, + .string_literal_utf_32, + .string_literal_wide, + => {}, + .identifier, .extended_identifier => { + if (mt.t.global_scope.blank_macros.contains(mt.tokSlice())) { + mt.i += 1; + continue; + } + }, + else => break, + } + const rhs = try mt.parseCPostfixExprInner(scope, type_name); + node = try ZigTag.array_cat.create(mt.t.arena, .{ .lhs = node, .rhs = rhs }); + } + return node; +} + +fn parseCPostfixExprInner(mt: *MacroTranslator, scope: *Scope, type_name: ?ZigNode) ParseError!ZigNode { + var node = type_name orelse try mt.parseCPrimaryExpr(scope); + while (true) { + switch (mt.peek()) { + .period => { + mt.i += 1; + const field_name = mt.tokSlice(); + try mt.expect(.identifier); + + node = try ZigTag.field_access.create(mt.t.arena, .{ .lhs = node, .field_name = field_name }); + }, + .arrow => { + mt.i += 1; + const field_name = mt.tokSlice(); + try mt.expect(.identifier); + + const deref = try ZigTag.deref.create(mt.t.arena, node); + node = try ZigTag.field_access.create(mt.t.arena, .{ .lhs = deref, .field_name = field_name }); + }, + .l_bracket => { + mt.i += 1; + + const index_val = try mt.macroIntFromBool(try mt.parseCExpr(scope)); + const index = try ZigTag.as.create(mt.t.arena, .{ + .lhs = try ZigTag.type.create(mt.t.arena, "usize"), + .rhs = try ZigTag.int_cast.create(mt.t.arena, index_val), + }); + node = try ZigTag.array_access.create(mt.t.arena, .{ .lhs = node, .rhs = index }); + try mt.expect(.r_bracket); + }, + .l_paren => { + mt.i += 1; + + if (mt.eat(.r_paren)) { + node = try ZigTag.call.create(mt.t.arena, .{ .lhs = node, .args = &.{} }); + } else { + var args = std.ArrayList(ZigNode).init(mt.t.gpa); + defer args.deinit(); + + while (true) { + const arg = try mt.parseCCondExpr(scope); + try args.append(arg); + + const next_id = mt.peek(); + switch (next_id) { + .comma => { + mt.i += 1; + }, + .r_paren => { + mt.i += 1; + break; + }, + else => { + try mt.fail("unable to translate C expr: expected ',' or ')' instead got '{s}'", .{next_id.symbol()}); + return error.ParseError; + }, + } + } + node = try ZigTag.call.create(mt.t.arena, .{ .lhs = node, .args = try mt.t.arena.dupe(ZigNode, args.items) }); + } + }, + .l_brace => { + mt.i += 1; + + // Check for designated field initializers + if (mt.peek() == .period) { + var init_vals = std.ArrayList(ast.Payload.ContainerInitDot.Initializer).init(mt.t.gpa); + defer init_vals.deinit(); + + while (true) { + try mt.expect(.period); + const name = mt.tokSlice(); + try mt.expect(.identifier); + try mt.expect(.equal); + + const val = try mt.parseCCondExpr(scope); + try init_vals.append(.{ .name = name, .value = val }); + + const next_id = mt.peek(); + switch (next_id) { + .comma => { + mt.i += 1; + }, + .r_brace => { + mt.i += 1; + break; + }, + else => { + try mt.fail("unable to translate C expr: expected ',' or '}}' instead got '{s}'", .{next_id.symbol()}); + return error.ParseError; + }, + } + } + const tuple_node = try ZigTag.container_init_dot.create(mt.t.arena, try mt.t.arena.dupe(ast.Payload.ContainerInitDot.Initializer, init_vals.items)); + node = try ZigTag.std_mem_zeroinit.create(mt.t.arena, .{ .lhs = node, .rhs = tuple_node }); + continue; + } + + var init_vals = std.ArrayList(ZigNode).init(mt.t.gpa); + defer init_vals.deinit(); + + while (true) { + const val = try mt.parseCCondExpr(scope); + try init_vals.append(val); + + const next_id = mt.peek(); + switch (next_id) { + .comma => { + mt.i += 1; + }, + .r_brace => { + mt.i += 1; + break; + }, + else => { + try mt.fail("unable to translate C expr: expected ',' or '}}' instead got '{s}'", .{next_id.symbol()}); + return error.ParseError; + }, + } + } + const tuple_node = try ZigTag.tuple.create(mt.t.arena, try mt.t.arena.dupe(ZigNode, init_vals.items)); + node = try ZigTag.std_mem_zeroinit.create(mt.t.arena, .{ .lhs = node, .rhs = tuple_node }); + }, + .plus_plus, .minus_minus => { + try mt.fail("TODO postfix inc/dec expr", .{}); + return error.ParseError; + }, + else => return node, + } + } +} + +fn parseCUnaryExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { + switch (mt.peek()) { + .bang => { + mt.i += 1; + const operand = try mt.macroIntToBool(try mt.parseCCastExpr(scope)); + return ZigTag.not.create(mt.t.arena, operand); + }, + .minus => { + mt.i += 1; + const operand = try mt.macroIntFromBool(try mt.parseCCastExpr(scope)); + return ZigTag.negate.create(mt.t.arena, operand); + }, + .plus => { + mt.i += 1; + return try mt.parseCCastExpr(scope); + }, + .tilde => { + mt.i += 1; + const operand = try mt.macroIntFromBool(try mt.parseCCastExpr(scope)); + return ZigTag.bit_not.create(mt.t.arena, operand); + }, + .asterisk => { + mt.i += 1; + const operand = try mt.parseCCastExpr(scope); + return ZigTag.deref.create(mt.t.arena, operand); + }, + .ampersand => { + mt.i += 1; + const operand = try mt.parseCCastExpr(scope); + return ZigTag.address_of.create(mt.t.arena, operand); + }, + .keyword_sizeof => { + mt.i += 1; + const operand = if (mt.eat(.l_paren)) blk: { + const inner = (try mt.parseCTypeName(scope, false)).?; + try mt.expect(.r_paren); + break :blk inner; + } else try mt.parseCUnaryExpr(scope); + + return mt.t.createHelperCallNode(.sizeof, &.{operand}); + }, + .keyword_alignof => { + mt.i += 1; + // TODO this won't work if using 's + // #define alignof _Alignof + try mt.expect(.l_paren); + const operand = (try mt.parseCTypeName(scope, false)).?; + try mt.expect(.r_paren); + + return ZigTag.alignof.create(mt.t.arena, operand); + }, + .plus_plus, .minus_minus => { + try mt.fail("TODO unary inc/dec expr", .{}); + return error.ParseError; + }, + else => {}, + } + + return try mt.parseCPostfixExpr(scope, null); +} diff --git a/lib/compiler/translate-c/PatternList.zig b/lib/compiler/translate-c/PatternList.zig new file mode 100644 index 0000000000000000000000000000000000000000..6d8ee4ed9c2140a49b551def73633265931e60e4 --- /dev/null +++ b/lib/compiler/translate-c/PatternList.zig @@ -0,0 +1,288 @@ +const std = @import("std"); +const mem = std.mem; +const assert = std.debug.assert; + +const aro = @import("aro"); +const CToken = aro.Tokenizer.Token; + +const helpers = @import("helpers.zig"); +const Translator = @import("Translator.zig"); +const Error = Translator.Error; +pub const MacroProcessingError = Error || error{UnexpectedMacroToken}; + +const Impl = std.meta.DeclEnum(@import("helpers")); +const Template = struct { []const u8, Impl }; + +/// Templates must be function-like macros +/// first element is macro source, second element is the name of the function +/// in __helpers which implements it +const templates = [_]Template{ + .{ "f_SUFFIX(X) (X ## f)", .F_SUFFIX }, + .{ "F_SUFFIX(X) (X ## F)", .F_SUFFIX }, + + .{ "u_SUFFIX(X) (X ## u)", .U_SUFFIX }, + .{ "U_SUFFIX(X) (X ## U)", .U_SUFFIX }, + + .{ "l_SUFFIX(X) (X ## l)", .L_SUFFIX }, + .{ "L_SUFFIX(X) (X ## L)", .L_SUFFIX }, + + .{ "ul_SUFFIX(X) (X ## ul)", .UL_SUFFIX }, + .{ "uL_SUFFIX(X) (X ## uL)", .UL_SUFFIX }, + .{ "Ul_SUFFIX(X) (X ## Ul)", .UL_SUFFIX }, + .{ "UL_SUFFIX(X) (X ## UL)", .UL_SUFFIX }, + + .{ "ll_SUFFIX(X) (X ## ll)", .LL_SUFFIX }, + .{ "LL_SUFFIX(X) (X ## LL)", .LL_SUFFIX }, + + .{ "ull_SUFFIX(X) (X ## ull)", .ULL_SUFFIX }, + .{ "uLL_SUFFIX(X) (X ## uLL)", .ULL_SUFFIX }, + .{ "Ull_SUFFIX(X) (X ## Ull)", .ULL_SUFFIX }, + .{ "ULL_SUFFIX(X) (X ## ULL)", .ULL_SUFFIX }, + + .{ "f_SUFFIX(X) X ## f", .F_SUFFIX }, + .{ "F_SUFFIX(X) X ## F", .F_SUFFIX }, + + .{ "u_SUFFIX(X) X ## u", .U_SUFFIX }, + .{ "U_SUFFIX(X) X ## U", .U_SUFFIX }, + + .{ "l_SUFFIX(X) X ## l", .L_SUFFIX }, + .{ "L_SUFFIX(X) X ## L", .L_SUFFIX }, + + .{ "ul_SUFFIX(X) X ## ul", .UL_SUFFIX }, + .{ "uL_SUFFIX(X) X ## uL", .UL_SUFFIX }, + .{ "Ul_SUFFIX(X) X ## Ul", .UL_SUFFIX }, + .{ "UL_SUFFIX(X) X ## UL", .UL_SUFFIX }, + + .{ "ll_SUFFIX(X) X ## ll", .LL_SUFFIX }, + .{ "LL_SUFFIX(X) X ## LL", .LL_SUFFIX }, + + .{ "ull_SUFFIX(X) X ## ull", .ULL_SUFFIX }, + .{ "uLL_SUFFIX(X) X ## uLL", .ULL_SUFFIX }, + .{ "Ull_SUFFIX(X) X ## Ull", .ULL_SUFFIX }, + .{ "ULL_SUFFIX(X) X ## ULL", .ULL_SUFFIX }, + + .{ "CAST_OR_CALL(X, Y) (X)(Y)", .CAST_OR_CALL }, + .{ "CAST_OR_CALL(X, Y) ((X)(Y))", .CAST_OR_CALL }, + + .{ + \\wl_container_of(ptr, sample, member) \ + \\(__typeof__(sample))((char *)(ptr) - \ + \\ offsetof(__typeof__(*sample), member)) + , + .WL_CONTAINER_OF, + }, + + .{ "IGNORE_ME(X) ((void)(X))", .DISCARD }, + .{ "IGNORE_ME(X) (void)(X)", .DISCARD }, + .{ "IGNORE_ME(X) ((const void)(X))", .DISCARD }, + .{ "IGNORE_ME(X) (const void)(X)", .DISCARD }, + .{ "IGNORE_ME(X) ((volatile void)(X))", .DISCARD }, + .{ "IGNORE_ME(X) (volatile void)(X)", .DISCARD }, + .{ "IGNORE_ME(X) ((const volatile void)(X))", .DISCARD }, + .{ "IGNORE_ME(X) (const volatile void)(X)", .DISCARD }, + .{ "IGNORE_ME(X) ((volatile const void)(X))", .DISCARD }, + .{ "IGNORE_ME(X) (volatile const void)(X)", .DISCARD }, +}; + +const Pattern = struct { + slicer: MacroSlicer, + impl: Impl, + + fn init(pl: *Pattern, allocator: mem.Allocator, template: Template) Error!void { + const source = template[0]; + const impl = template[1]; + var tok_list = std.ArrayList(CToken).init(allocator); + defer tok_list.deinit(); + + pl.* = .{ + .slicer = try tokenizeMacro(source, &tok_list), + .impl = impl, + }; + } + + fn deinit(pl: *Pattern, allocator: mem.Allocator) void { + allocator.free(pl.slicer.tokens); + pl.* = undefined; + } + + /// This function assumes that `ms` has already been validated to contain a function-like + /// macro, and that the parsed template macro in `pl` also contains a function-like + /// macro. Please review this logic carefully if changing that assumption. Two + /// function-like macros are considered equivalent if and only if they contain the same + /// list of tokens, modulo parameter names. + fn matches(pat: Pattern, ms: MacroSlicer) bool { + if (ms.params != pat.slicer.params) return false; + if (ms.tokens.len != pat.slicer.tokens.len) return false; + + for (ms.tokens, pat.slicer.tokens) |macro_tok, pat_tok| { + if (macro_tok.id != pat_tok.id) return false; + switch (macro_tok.id) { + .macro_param, .macro_param_no_expand => { + // `.end` is the parameter index. + if (macro_tok.end != pat_tok.end) return false; + }, + .identifier, .extended_identifier, .string_literal, .char_literal, .pp_num => { + const macro_bytes = ms.slice(macro_tok); + const pattern_bytes = pat.slicer.slice(pat_tok); + + if (!mem.eql(u8, pattern_bytes, macro_bytes)) return false; + }, + else => { + // other tags correspond to keywords and operators that do not contain a "payload" + // that can vary + }, + } + } + return true; + } +}; + +const PatternList = @This(); + +patterns: []Pattern, + +pub const MacroSlicer = struct { + source: []const u8, + tokens: []const CToken, + params: u32, + + fn slice(pl: MacroSlicer, token: CToken) []const u8 { + return pl.source[token.start..token.end]; + } +}; + +pub fn init(allocator: mem.Allocator) Error!PatternList { + const patterns = try allocator.alloc(Pattern, templates.len); + for (patterns, templates) |*pattern, template| { + try pattern.init(allocator, template); + } + return .{ .patterns = patterns }; +} + +pub fn deinit(pl: *PatternList, allocator: mem.Allocator) void { + for (pl.patterns) |*pattern| pattern.deinit(allocator); + allocator.free(pl.patterns); + pl.* = undefined; +} + +pub fn match(pl: PatternList, ms: MacroSlicer) Error!?Impl { + for (pl.patterns) |pattern| if (pattern.matches(ms)) return pattern.impl; + return null; +} + +fn tokenizeMacro(source: []const u8, tok_list: *std.ArrayList(CToken)) Error!MacroSlicer { + var param_count: u32 = 0; + var param_buf: [8][]const u8 = undefined; + + var tokenizer: aro.Tokenizer = .{ + .buf = source, + .source = .unused, + .langopts = .{}, + }; + { + const name_tok = tokenizer.nextNoWS(); + assert(name_tok.id == .identifier); + const l_paren = tokenizer.nextNoWS(); + assert(l_paren.id == .l_paren); + } + + while (true) { + const param = tokenizer.nextNoWS(); + if (param.id == .r_paren) break; + assert(param.id == .identifier); + const slice = source[param.start..param.end]; + param_buf[param_count] = slice; + param_count += 1; + + const comma = tokenizer.nextNoWS(); + if (comma.id == .r_paren) break; + assert(comma.id == .comma); + } + + outer: while (true) { + const tok = tokenizer.next(); + switch (tok.id) { + .whitespace, .comment => continue, + .identifier => { + const slice = source[tok.start..tok.end]; + for (param_buf[0..param_count], 0..) |param, i| { + if (std.mem.eql(u8, param, slice)) { + try tok_list.append(.{ + .id = .macro_param, + .source = .unused, + .end = @intCast(i), + }); + continue :outer; + } + } + }, + .hash_hash => { + if (tok_list.items[tok_list.items.len - 1].id == .macro_param) { + tok_list.items[tok_list.items.len - 1].id = .macro_param_no_expand; + } + }, + .nl, .eof => break, + else => {}, + } + try tok_list.append(tok); + } + + return .{ + .source = source, + .tokens = try tok_list.toOwnedSlice(), + .params = param_count, + }; +} + +test "Macro matching" { + const testing = std.testing; + const helper = struct { + fn checkMacro( + allocator: mem.Allocator, + pattern_list: PatternList, + source: []const u8, + comptime expected_match: ?Impl, + ) !void { + var tok_list = std.ArrayList(CToken).init(allocator); + defer tok_list.deinit(); + const ms = try tokenizeMacro(source, &tok_list); + defer allocator.free(ms.tokens); + + const matched = try pattern_list.match(ms); + if (expected_match) |expected| { + try testing.expectEqual(expected, matched); + } else { + try testing.expectEqual(@as(@TypeOf(matched), null), matched); + } + } + }; + const allocator = std.testing.allocator; + var pattern_list = try PatternList.init(allocator); + defer pattern_list.deinit(allocator); + + try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## F)", .F_SUFFIX); + try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## U)", .U_SUFFIX); + try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## L)", .L_SUFFIX); + try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## LL)", .LL_SUFFIX); + try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## UL)", .UL_SUFFIX); + try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## ULL)", .ULL_SUFFIX); + try helper.checkMacro(allocator, pattern_list, + \\container_of(a, b, c) \ + \\(__typeof__(b))((char *)(a) - \ + \\ offsetof(__typeof__(*b), c)) + , .WL_CONTAINER_OF); + + try helper.checkMacro(allocator, pattern_list, "NO_MATCH(X, Y) (X + Y)", null); + try helper.checkMacro(allocator, pattern_list, "CAST_OR_CALL(X, Y) (X)(Y)", .CAST_OR_CALL); + try helper.checkMacro(allocator, pattern_list, "CAST_OR_CALL(X, Y) ((X)(Y))", .CAST_OR_CALL); + try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (void)(X)", .DISCARD); + try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((void)(X))", .DISCARD); + try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (const void)(X)", .DISCARD); + try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((const void)(X))", .DISCARD); + try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (volatile void)(X)", .DISCARD); + try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((volatile void)(X))", .DISCARD); + try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (const volatile void)(X)", .DISCARD); + try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((const volatile void)(X))", .DISCARD); + try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (volatile const void)(X)", .DISCARD); + try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((volatile const void)(X))", .DISCARD); +} diff --git a/lib/compiler/translate-c/Scope.zig b/lib/compiler/translate-c/Scope.zig new file mode 100644 index 0000000000000000000000000000000000000000..0317adb7d21f4b9b61685316baaed78beaaa9dff --- /dev/null +++ b/lib/compiler/translate-c/Scope.zig @@ -0,0 +1,399 @@ +const std = @import("std"); + +const aro = @import("aro"); + +const ast = @import("ast.zig"); +const Translator = @import("Translator.zig"); + +const Scope = @This(); + +pub const SymbolTable = std.StringArrayHashMapUnmanaged(ast.Node); +pub const AliasList = std.ArrayListUnmanaged(struct { + alias: []const u8, + name: []const u8, +}); + +/// Associates a container (structure or union) with its relevant member functions. +pub const ContainerMemberFns = struct { + container_decl_ptr: *ast.Node, + member_fns: std.ArrayListUnmanaged(*ast.Payload.Func) = .empty, +}; +pub const ContainerMemberFnsHashMap = std.AutoArrayHashMapUnmanaged(aro.QualType, ContainerMemberFns); + +id: Id, +parent: ?*Scope, + +pub const Id = enum { + block, + root, + condition, + loop, + do_loop, +}; + +/// Used for the scope of condition expressions, for example `if (cond)`. +/// The block is lazily initialized because it is only needed for rare +/// cases of comma operators being used. +pub const Condition = struct { + base: Scope, + block: ?Block = null, + + fn getBlockScope(cond: *Condition, t: *Translator) !*Block { + if (cond.block) |*b| return b; + cond.block = try Block.init(t, &cond.base, true); + return &cond.block.?; + } + + pub fn deinit(cond: *Condition) void { + if (cond.block) |*b| b.deinit(); + } +}; + +/// Represents an in-progress Node.Block. This struct is stack-allocated. +/// When it is deinitialized, it produces an Node.Block which is allocated +/// into the main arena. +pub const Block = struct { + base: Scope, + translator: *Translator, + statements: std.ArrayListUnmanaged(ast.Node), + variables: AliasList, + mangle_count: u32 = 0, + label: ?[]const u8 = null, + + /// By default all variables are discarded, since we do not know in advance if they + /// will be used. This maps the variable's name to the Discard payload, so that if + /// the variable is subsequently referenced we can indicate that the discard should + /// be skipped during the intermediate AST -> Zig AST render step. + variable_discards: std.StringArrayHashMapUnmanaged(*ast.Payload.Discard), + + /// When the block corresponds to a function, keep track of the return type + /// so that the return expression can be cast, if necessary + return_type: ?aro.QualType = null, + + /// C static local variables are wrapped in a block-local struct. The struct + /// is named `mangle(static_local_ + name)` and the Zig variable within the + /// struct keeps the name of the C variable. + pub const static_local_prefix = "static_local"; + + /// C extern local variables are wrapped in a block-local struct. The struct + /// is named `mangle(extern_local + name)` and the Zig variable within the + /// struct keeps the name of the C variable. + pub const extern_local_prefix = "extern_local"; + + pub fn init(t: *Translator, parent: *Scope, labeled: bool) !Block { + var blk: Block = .{ + .base = .{ + .id = .block, + .parent = parent, + }, + .translator = t, + .statements = .empty, + .variables = .empty, + .variable_discards = .empty, + }; + if (labeled) { + blk.label = try blk.makeMangledName("blk"); + } + return blk; + } + + pub fn deinit(block: *Block) void { + block.statements.deinit(block.translator.gpa); + block.variables.deinit(block.translator.gpa); + block.variable_discards.deinit(block.translator.gpa); + block.* = undefined; + } + + pub fn complete(block: *Block) !ast.Node { + const arena = block.translator.arena; + if (block.base.parent.?.id == .do_loop) { + // We reserve 1 extra statement if the parent is a do_loop. This is in case of + // do while, we want to put `if (cond) break;` at the end. + const alloc_len = block.statements.items.len + @intFromBool(block.base.parent.?.id == .do_loop); + var stmts = try arena.alloc(ast.Node, alloc_len); + stmts.len = block.statements.items.len; + @memcpy(stmts[0..block.statements.items.len], block.statements.items); + return ast.Node.Tag.block.create(arena, .{ + .label = block.label, + .stmts = stmts, + }); + } + if (block.statements.items.len == 0) return ast.Node.Tag.empty_block.init(); + return ast.Node.Tag.block.create(arena, .{ + .label = block.label, + .stmts = try arena.dupe(ast.Node, block.statements.items), + }); + } + + /// Given the desired name, return a name that does not shadow anything from outer scopes. + /// Inserts the returned name into the scope. + /// The name will not be visible to callers of getAlias. + pub fn reserveMangledName(block: *Block, name: []const u8) ![]const u8 { + return block.createMangledName(name, true, null); + } + + /// Same as reserveMangledName, but enables the alias immediately. + pub fn makeMangledName(block: *Block, name: []const u8) ![]const u8 { + return block.createMangledName(name, false, null); + } + + pub fn createMangledName(block: *Block, name: []const u8, reservation: bool, prefix_opt: ?[]const u8) ![]const u8 { + const arena = block.translator.arena; + const name_copy = try arena.dupe(u8, name); + const alias_base = if (prefix_opt) |prefix| + try std.fmt.allocPrint(arena, "{s}_{s}", .{ prefix, name }) + else + name; + var proposed_name = alias_base; + while (block.contains(proposed_name)) { + block.mangle_count += 1; + proposed_name = try std.fmt.allocPrint(arena, "{s}_{d}", .{ alias_base, block.mangle_count }); + } + const new_mangle = try block.variables.addOne(block.translator.gpa); + if (reservation) { + new_mangle.* = .{ .name = name_copy, .alias = name_copy }; + } else { + new_mangle.* = .{ .name = name_copy, .alias = proposed_name }; + } + return proposed_name; + } + + fn getAlias(block: *Block, name: []const u8) ?[]const u8 { + for (block.variables.items) |p| { + if (std.mem.eql(u8, p.name, name)) + return p.alias; + } + return block.base.parent.?.getAlias(name); + } + + fn localContains(block: *Block, name: []const u8) bool { + for (block.variables.items) |p| { + if (std.mem.eql(u8, p.alias, name)) + return true; + } + return false; + } + + fn contains(block: *Block, name: []const u8) bool { + if (block.localContains(name)) + return true; + return block.base.parent.?.contains(name); + } + + pub fn discardVariable(block: *Block, name: []const u8) Translator.Error!void { + const gpa = block.translator.gpa; + const arena = block.translator.arena; + const name_node = try ast.Node.Tag.identifier.create(arena, name); + const discard = try ast.Node.Tag.discard.create(arena, .{ .should_skip = false, .value = name_node }); + try block.statements.append(gpa, discard); + try block.variable_discards.putNoClobber(gpa, name, discard.castTag(.discard).?); + } +}; + +pub const Root = struct { + base: Scope, + translator: *Translator, + sym_table: SymbolTable, + blank_macros: std.StringArrayHashMapUnmanaged(void), + nodes: std.ArrayListUnmanaged(ast.Node), + container_member_fns_map: ContainerMemberFnsHashMap, + + pub fn init(t: *Translator) Root { + return .{ + .base = .{ + .id = .root, + .parent = null, + }, + .translator = t, + .sym_table = .empty, + .blank_macros = .empty, + .nodes = .empty, + .container_member_fns_map = .empty, + }; + } + + pub fn deinit(root: *Root) void { + root.sym_table.deinit(root.translator.gpa); + root.blank_macros.deinit(root.translator.gpa); + root.nodes.deinit(root.translator.gpa); + for (root.container_member_fns_map.values()) |*members| { + members.member_fns.deinit(root.translator.gpa); + } + root.container_member_fns_map.deinit(root.translator.gpa); + } + + /// Check if the global scope contains this name, without looking into the "future", e.g. + /// ignore the preprocessed decl and macro names. + pub fn containsNow(root: *Root, name: []const u8) bool { + return root.sym_table.contains(name); + } + + /// Check if the global scope contains the name, includes all decls that haven't been translated yet. + pub fn contains(root: *Root, name: []const u8) bool { + return root.containsNow(name) or root.translator.global_names.contains(name) or root.translator.weak_global_names.contains(name); + } + + pub fn addMemberFunction(root: *Root, func_ty: aro.Type.Func, func: *ast.Payload.Func) !void { + std.debug.assert(func.data.name != null); + if (func_ty.params.len == 0) return; + + const param1_base = func_ty.params[0].qt.base(root.translator.comp); + const container_qt = if (param1_base.type == .pointer) + param1_base.type.pointer.child.base(root.translator.comp).qt + else + param1_base.qt; + + if (root.container_member_fns_map.getPtr(container_qt)) |members| { + try members.member_fns.append(root.translator.gpa, func); + } + } + + pub fn processContainerMemberFns(root: *Root) !void { + const gpa = root.translator.gpa; + const arena = root.translator.arena; + + var member_names: std.StringArrayHashMapUnmanaged(u32) = .empty; + defer member_names.deinit(gpa); + for (root.container_member_fns_map.values()) |members| { + member_names.clearRetainingCapacity(); + const decls_ptr = switch (members.container_decl_ptr.tag()) { + .@"struct", .@"union" => blk_record: { + const payload: *ast.Payload.Container = @alignCast(@fieldParentPtr("base", members.container_decl_ptr.ptr_otherwise)); + // Avoid duplication with field names + for (payload.data.fields) |field| { + try member_names.put(gpa, field.name, 0); + } + break :blk_record &payload.data.decls; + }, + .opaque_literal => blk_opaque: { + const container_decl = try ast.Node.Tag.@"opaque".create(arena, .{ + .layout = .none, + .fields = &.{}, + .decls = &.{}, + }); + members.container_decl_ptr.* = container_decl; + break :blk_opaque &container_decl.castTag(.@"opaque").?.data.decls; + }, + else => return, + }; + + const old_decls = decls_ptr.*; + const new_decls = try arena.alloc(ast.Node, old_decls.len + members.member_fns.items.len); + @memcpy(new_decls[0..old_decls.len], old_decls); + // Assume the allocator of payload.data.decls is arena, + // so don't add arena.free(old_variables). + const func_ref_vars = new_decls[old_decls.len..]; + var count: u32 = 0; + for (members.member_fns.items) |func| { + const func_name = func.data.name.?; + + const last_index = std.mem.lastIndexOf(u8, func_name, "_"); + const last_name = if (last_index) |index| func_name[index + 1 ..] else continue; + var same_count: u32 = 0; + const gop = try member_names.getOrPutValue(gpa, last_name, same_count); + if (gop.found_existing) { + gop.value_ptr.* += 1; + same_count = gop.value_ptr.*; + } + const var_name = if (same_count == 0) + last_name + else + try std.fmt.allocPrint(arena, "{s}{d}", .{ last_name, same_count }); + + func_ref_vars[count] = try ast.Node.Tag.pub_var_simple.create(arena, .{ + .name = var_name, + .init = try ast.Node.Tag.identifier.create(arena, func_name), + }); + count += 1; + } + decls_ptr.* = new_decls[0 .. old_decls.len + count]; + } + } +}; + +pub fn findBlockScope(inner: *Scope, t: *Translator) !*Block { + var scope = inner; + while (true) { + switch (scope.id) { + .root => unreachable, + .block => return @fieldParentPtr("base", scope), + .condition => return @as(*Condition, @fieldParentPtr("base", scope)).getBlockScope(t), + else => scope = scope.parent.?, + } + } +} + +pub fn findBlockReturnType(inner: *Scope) aro.QualType { + var scope = inner; + while (true) { + switch (scope.id) { + .root => unreachable, + .block => { + const block: *Block = @fieldParentPtr("base", scope); + if (block.return_type) |qt| return qt; + scope = scope.parent.?; + }, + else => scope = scope.parent.?, + } + } +} + +pub fn getAlias(scope: *Scope, name: []const u8) ?[]const u8 { + return switch (scope.id) { + .root => null, + .block => @as(*Block, @fieldParentPtr("base", scope)).getAlias(name), + .loop, .do_loop, .condition => scope.parent.?.getAlias(name), + }; +} + +fn contains(scope: *Scope, name: []const u8) bool { + return switch (scope.id) { + .root => @as(*Root, @fieldParentPtr("base", scope)).contains(name), + .block => @as(*Block, @fieldParentPtr("base", scope)).contains(name), + .loop, .do_loop, .condition => scope.parent.?.contains(name), + }; +} + +/// Appends a node to the first block scope if inside a function, or to the root tree if not. +pub fn appendNode(inner: *Scope, node: ast.Node) !void { + var scope = inner; + while (true) { + switch (scope.id) { + .root => { + const root: *Root = @fieldParentPtr("base", scope); + return root.nodes.append(root.translator.gpa, node); + }, + .block => { + const block: *Block = @fieldParentPtr("base", scope); + return block.statements.append(block.translator.gpa, node); + }, + else => scope = scope.parent.?, + } + } +} + +pub fn skipVariableDiscard(inner: *Scope, name: []const u8) void { + if (true) { + // TODO: due to 'local variable is never mutated' errors, we can + // only skip discards if a variable is used as an lvalue, which + // we don't currently have detection for in translate-c. + // Once #17584 is completed, perhaps we can do away with this + // logic entirely, and instead rely on render to fixup code. + return; + } + var scope = inner; + while (true) { + switch (scope.id) { + .root => return, + .block => { + const block: *Block = @fieldParentPtr("base", scope); + if (block.variable_discards.get(name)) |discard| { + discard.data.should_skip = true; + return; + } + }, + else => {}, + } + scope = scope.parent.?; + } +} diff --git a/lib/compiler/translate-c/Translator.zig b/lib/compiler/translate-c/Translator.zig new file mode 100644 index 0000000000000000000000000000000000000000..202ca5b3ca64df3d9ba14c335f02aa85705255b6 --- /dev/null +++ b/lib/compiler/translate-c/Translator.zig @@ -0,0 +1,4183 @@ +const std = @import("std"); +const mem = std.mem; +const assert = std.debug.assert; +const CallingConvention = std.builtin.CallingConvention; + +const aro = @import("aro"); +const CToken = aro.Tokenizer.Token; +const Tree = aro.Tree; +const Node = Tree.Node; +const TokenIndex = Tree.TokenIndex; +const QualType = aro.QualType; + +const ast = @import("ast.zig"); +const ZigNode = ast.Node; +const ZigTag = ZigNode.Tag; +const builtins = @import("builtins.zig"); +const helpers = @import("helpers.zig"); +const MacroTranslator = @import("MacroTranslator.zig"); +const PatternList = @import("PatternList.zig"); +const Scope = @import("Scope.zig"); + +pub const Error = std.mem.Allocator.Error; +pub const MacroProcessingError = Error || error{UnexpectedMacroToken}; +pub const TypeError = Error || error{UnsupportedType}; +pub const TransError = TypeError || error{UnsupportedTranslation}; + +const Translator = @This(); + +/// The C AST to be translated. +tree: *const Tree, +/// The compilation corresponding to the AST. +comp: *aro.Compilation, +/// The Preprocessor that produced the source for `tree`. +pp: *const aro.Preprocessor, + +gpa: mem.Allocator, +arena: mem.Allocator, + +alias_list: Scope.AliasList, +global_scope: *Scope.Root, +/// Running number used for creating new unique identifiers. +mangle_count: u32 = 0, + +/// Table of declarations for enum, struct, union and typedef types. +type_decls: std.AutoArrayHashMapUnmanaged(Node.Index, []const u8) = .empty, +/// Table of record decls that have been demoted to opaques. +opaque_demotes: std.AutoHashMapUnmanaged(QualType, void) = .empty, +/// Table of unnamed enums and records that are child types of typedefs. +unnamed_typedefs: std.AutoHashMapUnmanaged(QualType, []const u8) = .empty, +/// Table of anonymous record to generated field names. +anonymous_record_field_names: std.AutoHashMapUnmanaged(struct { + parent: QualType, + field: QualType, +}, []const u8) = .empty, + +/// This one is different than the root scope's name table. This contains +/// a list of names that we found by visiting all the top level decls without +/// translating them. The other maps are updated as we translate; this one is updated +/// up front in a pre-processing step. +global_names: std.StringArrayHashMapUnmanaged(void) = .empty, + +/// This is similar to `global_names`, but contains names which we would +/// *like* to use, but do not strictly *have* to if they are unavailable. +/// These are relevant to types, which ideally we would name like +/// 'struct_foo' with an alias 'foo', but if either of those names is taken, +/// may be mangled. +/// This is distinct from `global_names` so we can detect at a type +/// declaration whether or not the name is available. +weak_global_names: std.StringArrayHashMapUnmanaged(void) = .empty, + +/// Set of identifiers known to refer to typedef declarations. +/// Used when parsing macros. +typedefs: std.StringArrayHashMapUnmanaged(void) = .empty, + +/// The lhs lval of a compound assignment expression. +compound_assign_dummy: ?ZigNode = null, + +pub fn getMangle(t: *Translator) u32 { + t.mangle_count += 1; + return t.mangle_count; +} + +/// Convert an `aro.Source.Location` to a 'file:line:column' string. +pub fn locStr(t: *Translator, loc: aro.Source.Location) ![]const u8 { + const source = t.comp.getSource(loc.id); + const line_col = source.lineCol(loc); + const filename = source.path; + + const line = source.physicalLine(loc); + const col = line_col.col; + + return std.fmt.allocPrint(t.arena, "{s}:{d}:{d}", .{ filename, line, col }); +} + +fn maybeSuppressResult(t: *Translator, used: ResultUsed, result: ZigNode) TransError!ZigNode { + if (used == .used) return result; + return ZigTag.discard.create(t.arena, .{ .should_skip = false, .value = result }); +} + +pub fn addTopLevelDecl(t: *Translator, name: []const u8, decl_node: ZigNode) !void { + const gop = try t.global_scope.sym_table.getOrPut(t.gpa, name); + if (!gop.found_existing) { + gop.value_ptr.* = decl_node; + try t.global_scope.nodes.append(t.gpa, decl_node); + } +} + +fn fail( + t: *Translator, + err: anytype, + source_loc: TokenIndex, + comptime format: []const u8, + args: anytype, +) (@TypeOf(err) || error{OutOfMemory}) { + try t.warn(&t.global_scope.base, source_loc, format, args); + return err; +} + +pub fn failDecl( + t: *Translator, + scope: *Scope, + tok_idx: TokenIndex, + name: []const u8, + comptime format: []const u8, + args: anytype, +) Error!void { + const loc = t.tree.tokens.items(.loc)[tok_idx]; + return t.failDeclExtra(scope, loc, name, format, args); +} + +pub fn failDeclExtra( + t: *Translator, + scope: *Scope, + loc: aro.Source.Location, + name: []const u8, + comptime format: []const u8, + args: anytype, +) Error!void { + // location + // pub const name = @compileError(msg); + const fail_msg = try std.fmt.allocPrint(t.arena, format, args); + const fail_decl = try ZigTag.fail_decl.create(t.arena, .{ .actual = name, .mangled = fail_msg }); + + const str = try t.locStr(loc); + const location_comment = try std.fmt.allocPrint(t.arena, "// {s}", .{str}); + const loc_node = try ZigTag.warning.create(t.arena, location_comment); + + if (scope.id == .root) { + try t.addTopLevelDecl(name, fail_decl); + try scope.appendNode(loc_node); + } else { + try scope.appendNode(fail_decl); + try scope.appendNode(loc_node); + + const bs = try scope.findBlockScope(t); + try bs.discardVariable(name); + } +} + +fn warn(t: *Translator, scope: *Scope, tok_idx: TokenIndex, comptime format: []const u8, args: anytype) !void { + const loc = t.tree.tokens.items(.loc)[tok_idx]; + const str = try t.locStr(loc); + const value = try std.fmt.allocPrint(t.arena, "// {s}: warning: " ++ format, .{str} ++ args); + try scope.appendNode(try ZigTag.warning.create(t.arena, value)); +} + +pub const Options = struct { + gpa: mem.Allocator, + comp: *aro.Compilation, + pp: *const aro.Preprocessor, + tree: *const aro.Tree, + module_libs: bool, +}; + +pub fn translate(options: Options) ![]u8 { + const gpa = options.gpa; + var arena_allocator = std.heap.ArenaAllocator.init(gpa); + defer arena_allocator.deinit(); + const arena = arena_allocator.allocator(); + + var translator: Translator = .{ + .gpa = gpa, + .arena = arena, + .alias_list = .empty, + .global_scope = try arena.create(Scope.Root), + .comp = options.comp, + .pp = options.pp, + .tree = options.tree, + }; + translator.global_scope.* = Scope.Root.init(&translator); + defer { + translator.type_decls.deinit(gpa); + translator.alias_list.deinit(gpa); + translator.global_names.deinit(gpa); + translator.weak_global_names.deinit(gpa); + translator.opaque_demotes.deinit(gpa); + translator.unnamed_typedefs.deinit(gpa); + translator.anonymous_record_field_names.deinit(gpa); + translator.typedefs.deinit(gpa); + translator.global_scope.deinit(); + } + + try translator.prepopulateGlobalNameTable(); + try translator.transTopLevelDecls(); + + // Insert empty line before macros. + try translator.global_scope.nodes.append(gpa, try ZigTag.warning.create(arena, "\n")); + + try translator.transMacros(); + + for (translator.alias_list.items) |alias| { + if (!translator.global_scope.sym_table.contains(alias.alias)) { + const node = try ZigTag.alias.create(arena, .{ .actual = alias.alias, .mangled = alias.name }); + try translator.addTopLevelDecl(alias.alias, node); + } + } + + try translator.global_scope.processContainerMemberFns(); + + var buf: std.ArrayList(u8) = .init(gpa); + defer buf.deinit(); + + if (options.module_libs) { + try buf.appendSlice( + \\pub const __builtin = @import("c_builtins"); + \\pub const __helpers = @import("helpers"); + \\ + \\ + ); + } else { + try buf.appendSlice( + \\pub const __builtin = @import("c_builtins.zig"); + \\pub const __helpers = @import("helpers.zig"); + \\ + \\ + ); + } + + var zig_ast = try ast.render(gpa, translator.global_scope.nodes.items); + defer { + gpa.free(zig_ast.source); + zig_ast.deinit(gpa); + } + try zig_ast.renderToArrayList(&buf, .{}); + return buf.toOwnedSlice(); +} + +fn prepopulateGlobalNameTable(t: *Translator) !void { + for (t.tree.root_decls.items) |decl| { + switch (decl.get(t.tree)) { + .typedef => |typedef_decl| { + const decl_name = t.tree.tokSlice(typedef_decl.name_tok); + try t.global_names.put(t.gpa, decl_name, {}); + + // Check for typedefs with unnamed enum/record child types. + const base = typedef_decl.qt.base(t.comp); + switch (base.type) { + .@"enum" => |enum_ty| { + if (enum_ty.name.lookup(t.comp)[0] != '(') continue; + }, + .@"struct", .@"union" => |record_ty| { + if (record_ty.name.lookup(t.comp)[0] != '(') continue; + }, + else => continue, + } + + const gop = try t.unnamed_typedefs.getOrPut(t.gpa, base.qt); + if (gop.found_existing) { + // One typedef can declare multiple names. + // TODO Don't put this one in `decl_table` so it's processed later. + continue; + } + gop.value_ptr.* = decl_name; + }, + + .struct_decl, + .union_decl, + .struct_forward_decl, + .union_forward_decl, + .enum_decl, + .enum_forward_decl, + => { + const decl_qt = decl.qt(t.tree); + const prefix, const name = switch (decl_qt.base(t.comp).type) { + .@"struct" => |struct_ty| .{ "struct", struct_ty.name.lookup(t.comp) }, + .@"union" => |union_ty| .{ "union", union_ty.name.lookup(t.comp) }, + .@"enum" => |enum_ty| .{ "enum", enum_ty.name.lookup(t.comp) }, + else => unreachable, + }; + const prefixed_name = try std.fmt.allocPrint(t.arena, "{s}_{s}", .{ prefix, name }); + // `name` and `prefixed_name` are the preferred names for this type. + // However, we can name it anything else if necessary, so these are "weak names". + try t.weak_global_names.ensureUnusedCapacity(t.gpa, 2); + t.weak_global_names.putAssumeCapacity(name, {}); + t.weak_global_names.putAssumeCapacity(prefixed_name, {}); + }, + + .function, .variable => { + const decl_name = t.tree.tokSlice(decl.tok(t.tree)); + try t.global_names.put(t.gpa, decl_name, {}); + }, + .static_assert => {}, + .empty_decl => {}, + .global_asm => {}, + else => unreachable, + } + } + + for (t.pp.defines.keys(), t.pp.defines.values()) |name, macro| { + if (macro.is_builtin) continue; + if (!t.isSelfDefinedMacro(name, macro)) { + try t.global_names.put(t.gpa, name, {}); + } + } +} + +/// Determines whether macro is of the form: `#define FOO FOO` (Possibly with trailing tokens) +/// Macros of this form will not be translated. +fn isSelfDefinedMacro(t: *Translator, name: []const u8, macro: aro.Preprocessor.Macro) bool { + if (macro.is_func) return false; + + if (macro.tokens.len < 1) return false; + const first_tok = macro.tokens[0]; + + const source = t.comp.getSource(macro.loc.id); + const slice = source.buf[first_tok.start..first_tok.end]; + + return std.mem.eql(u8, name, slice); +} + +// ======================= +// Declaration translation +// ======================= + +fn transTopLevelDecls(t: *Translator) !void { + for (t.tree.root_decls.items) |decl| { + try t.transDecl(&t.global_scope.base, decl); + } +} + +fn transDecl(t: *Translator, scope: *Scope, decl: Node.Index) !void { + switch (decl.get(t.tree)) { + .typedef => |typedef_decl| { + // Implicit typedefs are translated only if referenced. + if (typedef_decl.implicit) return; + try t.transTypeDef(scope, decl); + }, + + .struct_decl, .union_decl => |record_decl| { + try t.transRecordDecl(scope, record_decl.container_qt); + }, + + .enum_decl => |enum_decl| { + try t.transEnumDecl(scope, enum_decl.container_qt); + }, + + .enum_field, + .record_field, + .struct_forward_decl, + .union_forward_decl, + .enum_forward_decl, + => return, + + .function => |function| { + if (function.definition) |definition| { + return t.transFnDecl(scope, definition.get(t.tree).function); + } + try t.transFnDecl(scope, function); + }, + + .variable => |variable| { + if (variable.definition != null) return; + try t.transVarDecl(scope, variable); + }, + .static_assert => |static_assert| { + try t.transStaticAssert(&t.global_scope.base, static_assert); + }, + .global_asm => |global_asm| { + try t.transGlobalAsm(&t.global_scope.base, global_asm); + }, + .empty_decl => {}, + else => unreachable, + } +} + +pub const builtin_typedef_map = std.StaticStringMap([]const u8).initComptime(.{ + .{ "uint8_t", "u8" }, + .{ "int8_t", "i8" }, + .{ "uint16_t", "u16" }, + .{ "int16_t", "i16" }, + .{ "uint32_t", "u32" }, + .{ "int32_t", "i32" }, + .{ "uint64_t", "u64" }, + .{ "int64_t", "i64" }, + .{ "intptr_t", "isize" }, + .{ "uintptr_t", "usize" }, + .{ "ssize_t", "isize" }, + .{ "size_t", "usize" }, +}); + +fn transTypeDef(t: *Translator, scope: *Scope, typedef_node: Node.Index) Error!void { + const typedef_decl = typedef_node.get(t.tree).typedef; + if (t.type_decls.get(typedef_node)) |_| + return; // Avoid processing this decl twice + + const toplevel = scope.id == .root; + const bs: *Scope.Block = if (!toplevel) try scope.findBlockScope(t) else undefined; + + var name: []const u8 = t.tree.tokSlice(typedef_decl.name_tok); + try t.typedefs.put(t.gpa, name, {}); + + if (builtin_typedef_map.get(name)) |builtin| { + return t.type_decls.putNoClobber(t.gpa, typedef_node, builtin); + } + if (!toplevel) name = try bs.makeMangledName(name); + try t.type_decls.putNoClobber(t.gpa, typedef_node, name); + + const typedef_loc = typedef_decl.name_tok; + const init_node = t.transType(scope, typedef_decl.qt, typedef_loc) catch |err| switch (err) { + error.UnsupportedType => { + return t.failDecl(scope, typedef_loc, name, "unable to resolve typedef child type", .{}); + }, + error.OutOfMemory => |e| return e, + }; + + const payload = try t.arena.create(ast.Payload.SimpleVarDecl); + payload.* = .{ + .base = .{ .tag = if (toplevel) .pub_var_simple else .var_simple }, + .data = .{ + .name = name, + .init = init_node, + }, + }; + const node = ZigNode.initPayload(&payload.base); + + if (toplevel) { + try t.addTopLevelDecl(name, node); + } else { + try scope.appendNode(node); + try bs.discardVariable(name); + } +} + +fn mangleWeakGlobalName(t: *Translator, want_name: []const u8) Error![]const u8 { + var cur_name = want_name; + + if (!t.weak_global_names.contains(want_name)) { + // This type wasn't noticed by the name detection pass, so nothing has been treating this as + // a weak global name. We must mangle it to avoid conflicts with locals. + cur_name = try std.fmt.allocPrint(t.arena, "{s}_{d}", .{ want_name, t.getMangle() }); + } + + while (t.global_names.contains(cur_name)) { + cur_name = try std.fmt.allocPrint(t.arena, "{s}_{d}", .{ want_name, t.getMangle() }); + } + return cur_name; +} + +fn transRecordDecl(t: *Translator, scope: *Scope, record_qt: QualType) Error!void { + const base = record_qt.base(t.comp); + const record_ty = switch (base.type) { + .@"struct", .@"union" => |record_ty| record_ty, + else => unreachable, + }; + + if (t.type_decls.get(record_ty.decl_node)) |_| + return; // Avoid processing this decl twice + + const toplevel = scope.id == .root; + const bs: *Scope.Block = if (!toplevel) try scope.findBlockScope(t) else undefined; + + const container_kind: ZigTag = if (base.type == .@"union") .@"union" else .@"struct"; + const container_kind_name = @tagName(container_kind); + + var bare_name = record_ty.name.lookup(t.comp); + var is_unnamed = false; + var name = bare_name; + + if (t.unnamed_typedefs.get(base.qt)) |typedef_name| { + bare_name = typedef_name; + name = typedef_name; + } else { + if (record_ty.isAnonymous(t.comp)) { + bare_name = try std.fmt.allocPrint(t.arena, "unnamed_{d}", .{t.getMangle()}); + is_unnamed = true; + } + name = try std.fmt.allocPrint(t.arena, "{s}_{s}", .{ container_kind_name, bare_name }); + if (toplevel and !is_unnamed) { + name = try t.mangleWeakGlobalName(name); + } + } + if (!toplevel) name = try bs.makeMangledName(name); + try t.type_decls.putNoClobber(t.gpa, record_ty.decl_node, name); + + const is_pub = toplevel and !is_unnamed; + const init_node = init: { + if (record_ty.layout == null) { + try t.opaque_demotes.put(t.gpa, base.qt, {}); + break :init ZigTag.opaque_literal.init(); + } + + var fields = try std.ArrayList(ast.Payload.Container.Field).initCapacity(t.gpa, record_ty.fields.len); + defer fields.deinit(); + + var functions = std.ArrayList(ZigNode).init(t.gpa); + defer functions.deinit(); + + var unnamed_field_count: u32 = 0; + + // If a record doesn't have any attributes that would affect the alignment and + // layout, then we can just use a simple `extern` type. If it does have attributes, + // then we need to inspect the layout and assign an `align` value for each field. + const has_alignment_attributes = aligned: { + if (record_qt.hasAttribute(t.comp, .@"packed")) break :aligned true; + if (record_qt.hasAttribute(t.comp, .aligned)) break :aligned true; + for (record_ty.fields) |field| { + const field_attrs = field.attributes(t.comp); + for (field_attrs) |field_attr| { + switch (field_attr.tag) { + .@"packed", .aligned => break :aligned true, + else => {}, + } + } + } + break :aligned false; + }; + const head_field_alignment: ?c_uint = if (has_alignment_attributes) t.headFieldAlignment(record_ty) else null; + + for (record_ty.fields, 0..) |field, field_index| { + const field_loc = field.name_tok; + + // Demote record to opaque if it contains a bitfield + if (field.bit_width != .null) { + try t.opaque_demotes.put(t.gpa, base.qt, {}); + try t.warn(scope, field_loc, "{s} demoted to opaque type - has bitfield", .{container_kind_name}); + break :init ZigTag.opaque_literal.init(); + } + + var field_name = field.name.lookup(t.comp); + if (field.name_tok == 0) { + field_name = try std.fmt.allocPrint(t.arena, "unnamed_{d}", .{unnamed_field_count}); + unnamed_field_count += 1; + try t.anonymous_record_field_names.put(t.gpa, .{ + .parent = base.qt, + .field = field.qt, + }, field_name); + } + + const field_alignment = if (has_alignment_attributes) + t.alignmentForField(record_ty, head_field_alignment, field_index) + else + null; + + const field_type = field_type: { + // Check if this is a flexible array member. + flexible: { + if (field_index != record_ty.fields.len - 1 and container_kind != .@"union") break :flexible; + const array_ty = field.qt.get(t.comp, .array) orelse break :flexible; + if (array_ty.len != .incomplete and (array_ty.len != .fixed or array_ty.len.fixed != 0)) break :flexible; + + const elem_type = t.transType(scope, array_ty.elem, field_loc) catch |err| switch (err) { + error.UnsupportedType => break :flexible, + else => |e| return e, + }; + const zero_array = try ZigTag.array_type.create(t.arena, .{ .len = 0, .elem_type = elem_type }); + + const member_name = field_name; + field_name = try std.fmt.allocPrint(t.arena, "_{s}", .{field_name}); + + const member = try t.createFlexibleMemberFn(member_name, field_name); + try functions.append(member); + + break :field_type zero_array; + } + + break :field_type t.transType(scope, field.qt, field_loc) catch |err| switch (err) { + error.UnsupportedType => { + try t.opaque_demotes.put(t.gpa, base.qt, {}); + try t.warn(scope, field.name_tok, "{s} demoted to opaque type - unable to translate type of field {s}", .{ + container_kind_name, + field_name, + }); + break :init ZigTag.opaque_literal.init(); + }, + else => |e| return e, + }; + }; + + // C99 introduced designated initializers for structs. Omitted fields are implicitly + // initialized to zero. Some C APIs are designed with this in mind. Defaulting to zero + // values for translated struct fields permits Zig code to comfortably use such an API. + const default_value = if (container_kind == .@"struct") + try t.createZeroValueNode(field.qt, field_type, .no_as) + else + null; + + fields.appendAssumeCapacity(.{ + .name = field_name, + .type = field_type, + .alignment = field_alignment, + .default_value = default_value, + }); + } + + // A record is empty if it has no fields or only flexible array fields. + if (record_ty.fields.len == functions.items.len and + t.comp.target.os.tag == .windows and t.comp.target.abi == .msvc) + { + // In MSVC empty records have the same size as their alignment. + const padding_bits = record_ty.layout.?.size_bits; + const alignment_bits = record_ty.layout.?.field_alignment_bits; + + try fields.append(.{ + .name = "_padding", + .type = try ZigTag.type.create(t.arena, try std.fmt.allocPrint(t.arena, "u{d}", .{padding_bits})), + .alignment = @divExact(alignment_bits, 8), + .default_value = if (container_kind == .@"struct") + ZigTag.zero_literal.init() + else + null, + }); + } + + const container_payload = try t.arena.create(ast.Payload.Container); + container_payload.* = .{ + .base = .{ .tag = container_kind }, + .data = .{ + .layout = .@"extern", + .fields = try t.arena.dupe(ast.Payload.Container.Field, fields.items), + .decls = try t.arena.dupe(ZigNode, functions.items), + }, + }; + break :init ZigNode.initPayload(&container_payload.base); + }; + + const payload = try t.arena.create(ast.Payload.SimpleVarDecl); + payload.* = .{ + .base = .{ .tag = if (is_pub) .pub_var_simple else .var_simple }, + .data = .{ + .name = name, + .init = init_node, + }, + }; + const node = ZigNode.initPayload(&payload.base); + if (toplevel) { + try t.addTopLevelDecl(name, node); + // Only add the alias if the name is available *and* it was caught by + // name detection. Don't bother performing a weak mangle, since a + // mangled name is of no real use here. + if (!is_unnamed and !t.global_names.contains(bare_name) and t.weak_global_names.contains(bare_name)) + try t.alias_list.append(t.gpa, .{ .alias = bare_name, .name = name }); + try t.global_scope.container_member_fns_map.put(t.gpa, record_qt, .{ + .container_decl_ptr = &payload.data.init, + }); + } else { + try scope.appendNode(node); + try bs.discardVariable(name); + } +} + +fn transFnDecl(t: *Translator, scope: *Scope, function: Node.Function) Error!void { + const func_ty = function.qt.get(t.comp, .func).?; + + const is_pub = scope.id == .root; + + const fn_name = t.tree.tokSlice(function.name_tok); + if (scope.getAlias(fn_name) != null or t.global_scope.containsNow(fn_name)) + return; // Avoid processing this decl twice + + const fn_decl_loc = function.name_tok; + const has_body = function.body != null and func_ty.kind != .variadic; + if (function.body != null and func_ty.kind == .variadic) { + try t.warn(scope, function.name_tok, "TODO unable to translate variadic function, demoted to extern", .{}); + } + + const is_always_inline = has_body and function.qt.getAttribute(t.comp, .always_inline) != null; + const proto_ctx: FnProtoContext = .{ + .fn_name = fn_name, + .is_always_inline = is_always_inline, + .is_extern = !has_body, + .is_export = !function.static and has_body and !is_always_inline and !function.@"inline", + .is_pub = is_pub, + .has_body = has_body, + .cc = if (function.qt.getAttribute(t.comp, .calling_convention)) |some| switch (some.cc) { + .c => .c, + .stdcall => .x86_stdcall, + .thiscall => .x86_thiscall, + .fastcall => .x86_fastcall, + .regcall => .x86_regcall, + .riscv_vector => .riscv_vector, + .aarch64_sve_pcs => .aarch64_sve_pcs, + .aarch64_vector_pcs => .aarch64_vfabi, + .arm_aapcs => .arm_aapcs, + .arm_aapcs_vfp => .arm_aapcs_vfp, + .vectorcall => switch (t.comp.target.cpu.arch) { + .x86 => .x86_vectorcall, + .aarch64, .aarch64_be => .aarch64_vfabi, + else => .c, + }, + .x86_64_sysv => .x86_64_sysv, + .x86_64_win => .x86_64_win, + } else .c, + }; + + const proto_node = t.transFnType(&t.global_scope.base, function.qt, func_ty, fn_decl_loc, proto_ctx) catch |err| switch (err) { + error.UnsupportedType => { + return t.failDecl(scope, fn_decl_loc, fn_name, "unable to resolve prototype of function", .{}); + }, + error.OutOfMemory => |e| return e, + }; + + const proto_payload = proto_node.castTag(.func).?; + if (!has_body) { + if (scope.id != .root) { + const bs: *Scope.Block = try scope.findBlockScope(t); + const mangled_name = try bs.createMangledName(fn_name, false, Scope.Block.extern_local_prefix); + const wrapped = try ZigTag.wrapped_local.create(t.arena, .{ .name = mangled_name, .init = proto_node }); + try scope.appendNode(wrapped); + try bs.discardVariable(mangled_name); + return; + } + try t.global_scope.addMemberFunction(func_ty, proto_payload); + return t.addTopLevelDecl(fn_name, proto_node); + } + + // actual function definition with body + const body_stmt = function.body.?.get(t.tree).compound_stmt; + var block_scope = try Scope.Block.init(t, &t.global_scope.base, false); + block_scope.return_type = func_ty.return_type; + defer block_scope.deinit(); + + var param_id: c_uint = 0; + for (proto_payload.data.params, func_ty.params) |*param, param_info| { + const param_name = param.name orelse { + proto_payload.data.is_extern = true; + proto_payload.data.is_export = false; + proto_payload.data.is_inline = false; + try t.warn(&t.global_scope.base, fn_decl_loc, "function {s} parameter has no name, demoted to extern", .{fn_name}); + return t.addTopLevelDecl(fn_name, proto_node); + }; + + const is_const = param_info.qt.@"const"; + + const mangled_param_name = try block_scope.makeMangledName(param_name); + param.name = mangled_param_name; + + if (!is_const) { + const bare_arg_name = try std.fmt.allocPrint(t.arena, "arg_{s}", .{mangled_param_name}); + const arg_name = try block_scope.makeMangledName(bare_arg_name); + param.name = arg_name; + + const redecl_node = try ZigTag.arg_redecl.create(t.arena, .{ .actual = mangled_param_name, .mangled = arg_name }); + try block_scope.statements.append(t.gpa, redecl_node); + } + try block_scope.discardVariable(mangled_param_name); + + param_id += 1; + } + + t.transCompoundStmtInline(body_stmt, &block_scope) catch |err| switch (err) { + error.OutOfMemory => |e| return e, + error.UnsupportedTranslation, + error.UnsupportedType, + => { + proto_payload.data.is_extern = true; + proto_payload.data.is_export = false; + proto_payload.data.is_inline = false; + try t.warn(&t.global_scope.base, fn_decl_loc, "unable to translate function, demoted to extern", .{}); + return t.addTopLevelDecl(fn_name, proto_node); + }, + }; + + try t.global_scope.addMemberFunction(func_ty, proto_payload); + proto_payload.data.body = try block_scope.complete(); + return t.addTopLevelDecl(fn_name, proto_node); +} + +fn transVarDecl(t: *Translator, scope: *Scope, variable: Node.Variable) Error!void { + const base_name = t.tree.tokSlice(variable.name_tok); + const toplevel = scope.id == .root; + const bs: *Scope.Block = if (!toplevel) try scope.findBlockScope(t) else undefined; + const name, const use_base_name = blk: { + if (toplevel) break :blk .{ base_name, false }; + + // Local extern and static variables are wrapped in a struct. + const prefix: ?[]const u8 = switch (variable.storage_class) { + .@"extern" => Scope.Block.extern_local_prefix, + .static => Scope.Block.static_local_prefix, + else => null, + }; + break :blk .{ try bs.createMangledName(base_name, false, prefix), prefix != null }; + }; + + if (t.typeWasDemotedToOpaque(variable.qt)) { + if (variable.storage_class != .@"extern" and scope.id == .root) { + return t.failDecl(scope, variable.name_tok, name, "non-extern variable has opaque type", .{}); + } else { + return t.failDecl(scope, variable.name_tok, name, "local variable has opaque type", .{}); + } + } + + const type_node = (if (variable.initializer) |init| + t.transTypeInit(scope, variable.qt, init, variable.name_tok) + else + t.transType(scope, variable.qt, variable.name_tok)) catch |err| switch (err) { + error.UnsupportedType => { + return t.failDecl(scope, variable.name_tok, name, "unable to translate variable declaration type", .{}); + }, + else => |e| return e, + }; + + const array_ty = variable.qt.get(t.comp, .array); + var is_const = variable.qt.@"const" or (array_ty != null and array_ty.?.elem.@"const"); + var is_extern = variable.storage_class == .@"extern"; + + const init_node = init: { + if (variable.initializer) |init| { + const maybe_literal = init.get(t.tree); + const init_node = (if (maybe_literal == .string_literal_expr) + t.transStringLiteralInitializer(init, maybe_literal.string_literal_expr, type_node) + else + t.transExprCoercing(scope, init, .used)) catch |err| switch (err) { + error.UnsupportedTranslation, error.UnsupportedType => { + return t.failDecl(scope, variable.name_tok, name, "unable to resolve var init expr", .{}); + }, + else => |e| return e, + }; + + if (!variable.qt.is(t.comp, .bool) and init_node.isBoolRes()) { + break :init try ZigTag.int_from_bool.create(t.arena, init_node); + } else { + break :init init_node; + } + } + if (variable.storage_class == .@"extern") { + if (array_ty != null and array_ty.?.len == .incomplete) { + // Oh no, an extern array of unknown size! These are really fun because there's no + // direct equivalent in Zig. To translate correctly, we'll have to create a C-pointer + // to the data initialized via @extern. + + // Since this is really a pointer to the underlying data, we tweak a few properties. + is_extern = false; + is_const = true; + + const name_str = try std.fmt.allocPrint(t.arena, "\"{s}\"", .{base_name}); + break :init try ZigTag.builtin_extern.create(t.arena, .{ + .type = type_node, + .name = try ZigTag.string_literal.create(t.arena, name_str), + }); + } + break :init null; + } + if (toplevel or variable.storage_class == .static or variable.thread_local) { + // The C language specification states that variables with static or threadlocal + // storage without an initializer are initialized to a zero value. + break :init try t.createZeroValueNode(variable.qt, type_node, .no_as); + } + break :init ZigTag.undefined_literal.init(); + }; + + const linksection_string = blk: { + if (variable.qt.getAttribute(t.comp, .section)) |section| { + break :blk t.comp.interner.get(section.name.ref()).bytes; + } + break :blk null; + }; + + const alignment: ?c_uint = variable.qt.requestedAlignment(t.comp) orelse null; + var node = try ZigTag.var_decl.create(t.arena, .{ + .is_pub = toplevel, + .is_const = is_const, + .is_extern = is_extern, + .is_export = toplevel and variable.storage_class == .auto, + .is_threadlocal = variable.thread_local, + .linksection_string = linksection_string, + .alignment = alignment, + .name = if (use_base_name) base_name else name, + .type = type_node, + .init = init_node, + }); + + if (toplevel) { + try t.addTopLevelDecl(name, node); + } else { + if (use_base_name) { + node = try ZigTag.wrapped_local.create(t.arena, .{ .name = name, .init = node }); + } + try scope.appendNode(node); + try bs.discardVariable(name); + + if (variable.qt.getAttribute(t.comp, .cleanup)) |cleanup_attr| { + const cleanup_fn_name = t.tree.tokSlice(cleanup_attr.function.tok); + const mangled_fn_name = scope.getAlias(cleanup_fn_name) orelse cleanup_fn_name; + const fn_id = try ZigTag.identifier.create(t.arena, mangled_fn_name); + + const varname = try ZigTag.identifier.create(t.arena, name); + const args = try t.arena.alloc(ZigNode, 1); + args[0] = try ZigTag.address_of.create(t.arena, varname); + + const cleanup_call = try ZigTag.call.create(t.arena, .{ .lhs = fn_id, .args = args }); + const discard = try ZigTag.discard.create(t.arena, .{ .should_skip = false, .value = cleanup_call }); + const deferred_cleanup = try ZigTag.@"defer".create(t.arena, discard); + + try bs.statements.append(t.gpa, deferred_cleanup); + } + } +} + +fn transEnumDecl(t: *Translator, scope: *Scope, enum_qt: QualType) Error!void { + const base = enum_qt.base(t.comp); + const enum_ty = base.type.@"enum"; + + if (t.type_decls.get(enum_ty.decl_node)) |_| + return; // Avoid processing this decl twice + + const toplevel = scope.id == .root; + const bs: *Scope.Block = if (!toplevel) try scope.findBlockScope(t) else undefined; + + var bare_name = enum_ty.name.lookup(t.comp); + var is_unnamed = false; + var name = bare_name; + if (t.unnamed_typedefs.get(base.qt)) |typedef_name| { + bare_name = typedef_name; + name = typedef_name; + } else { + if (enum_ty.isAnonymous(t.comp)) { + bare_name = try std.fmt.allocPrint(t.arena, "unnamed_{d}", .{t.getMangle()}); + is_unnamed = true; + } + name = try std.fmt.allocPrint(t.arena, "enum_{s}", .{bare_name}); + } + if (!toplevel) name = try bs.makeMangledName(name); + try t.type_decls.putNoClobber(t.gpa, enum_ty.decl_node, name); + + const enum_type_node = if (!base.qt.hasIncompleteSize(t.comp)) blk: { + const enum_decl = enum_ty.decl_node.get(t.tree).enum_decl; + for (enum_ty.fields, enum_decl.fields) |field, field_node| { + var enum_val_name = field.name.lookup(t.comp); + if (!toplevel) { + enum_val_name = try bs.makeMangledName(enum_val_name); + } + + const enum_const_type_node: ?ZigNode = t.transType(scope, field.qt, field.name_tok) catch |err| switch (err) { + error.UnsupportedType => null, + else => |e| return e, + }; + + const val = t.tree.value_map.get(field_node).?; + const enum_const_def = try ZigTag.enum_constant.create(t.arena, .{ + .name = enum_val_name, + .is_public = toplevel, + .type = enum_const_type_node, + .value = try t.createIntNode(val), + }); + if (toplevel) + try t.addTopLevelDecl(enum_val_name, enum_const_def) + else { + try scope.appendNode(enum_const_def); + try bs.discardVariable(enum_val_name); + } + } + + break :blk t.transType(scope, enum_ty.tag.?, enum_decl.name_or_kind_tok) catch |err| switch (err) { + error.UnsupportedType => { + return t.failDecl(scope, enum_decl.name_or_kind_tok, name, "unable to translate enum integer type", .{}); + }, + else => |e| return e, + }; + } else blk: { + try t.opaque_demotes.put(t.gpa, base.qt, {}); + break :blk ZigTag.opaque_literal.init(); + }; + + const is_pub = toplevel and !is_unnamed; + const payload = try t.arena.create(ast.Payload.SimpleVarDecl); + payload.* = .{ + .base = .{ .tag = if (is_pub) .pub_var_simple else .var_simple }, + .data = .{ + .init = enum_type_node, + .name = name, + }, + }; + const node = ZigNode.initPayload(&payload.base); + if (toplevel) { + try t.addTopLevelDecl(name, node); + if (!is_unnamed) + try t.alias_list.append(t.gpa, .{ .alias = bare_name, .name = name }); + } else { + try scope.appendNode(node); + try bs.discardVariable(name); + } +} + +fn transStaticAssert(t: *Translator, scope: *Scope, static_assert: Node.StaticAssert) Error!void { + const condition = t.transExpr(scope, static_assert.cond, .used) catch |err| switch (err) { + error.UnsupportedTranslation, error.UnsupportedType => { + return try t.warn(&t.global_scope.base, static_assert.cond.tok(t.tree), "unable to translate _Static_assert condition", .{}); + }, + error.OutOfMemory => |e| return e, + }; + + // generate @compileError message that matches C compiler output + const diagnostic = if (static_assert.message) |message| str: { + // Aro guarantees this to be a string literal. + const str_val = t.tree.value_map.get(message).?; + const str_qt = message.qt(t.tree); + + const bytes = t.comp.interner.get(str_val.ref()).bytes; + var allocating: std.Io.Writer.Allocating = .init(t.gpa); + defer allocating.deinit(); + + allocating.writer.writeAll("\"static assertion failed \\") catch return error.OutOfMemory; + + aro.Value.printString(bytes, str_qt, t.comp, &allocating.writer) catch return error.OutOfMemory; + allocating.writer.end -= 1; // printString adds a terminating " so we need to remove it + allocating.writer.writeAll("\\\"\"") catch return error.OutOfMemory; + + break :str try ZigTag.string_literal.create(t.arena, try t.arena.dupe(u8, allocating.getWritten())); + } else try ZigTag.string_literal.create(t.arena, "\"static assertion failed\""); + + const assert_node = try ZigTag.static_assert.create(t.arena, .{ .lhs = condition, .rhs = diagnostic }); + try scope.appendNode(assert_node); +} + +fn transGlobalAsm(t: *Translator, scope: *Scope, global_asm: Node.SimpleAsm) Error!void { + const asm_string = t.tree.value_map.get(global_asm.asm_str).?; + const bytes = t.comp.interner.get(asm_string.ref()).bytes; + + var allocating: std.Io.Writer.Allocating = try .initCapacity(t.gpa, bytes.len); + defer allocating.deinit(); + aro.Value.printString(bytes, global_asm.asm_str.qt(t.tree), t.comp, &allocating.writer) catch return error.OutOfMemory; + + const str_node = try ZigTag.string_literal.create(t.arena, try t.arena.dupe(u8, allocating.getWritten())); + + const asm_node = try ZigTag.asm_simple.create(t.arena, str_node); + const block = try ZigTag.block_single.create(t.arena, asm_node); + const comptime_node = try ZigTag.@"comptime".create(t.arena, block); + + try scope.appendNode(comptime_node); +} + +// ================ +// Type translation +// ================ + +fn getTypeStr(t: *Translator, qt: QualType) ![]const u8 { + var allocating: std.Io.Writer.Allocating = .init(t.gpa); + defer allocating.deinit(); + qt.print(t.comp, &allocating.writer) catch return error.OutOfMemory; + return t.arena.dupe(u8, allocating.getWritten()); +} + +fn transType(t: *Translator, scope: *Scope, qt: QualType, source_loc: TokenIndex) TypeError!ZigNode { + loop: switch (qt.type(t.comp)) { + .atomic => { + const type_name = try t.getTypeStr(qt); + return t.fail(error.UnsupportedType, source_loc, "TODO support atomic type: '{s}'", .{type_name}); + }, + .void => return ZigTag.type.create(t.arena, "anyopaque"), + .bool => return ZigTag.type.create(t.arena, "bool"), + .int => |int_ty| switch (int_ty) { + //.char => return ZigTag.type.create(t.arena, "c_char"), // TODO: this is the preferred translation + .char => return ZigTag.type.create(t.arena, "u8"), + .schar => return ZigTag.type.create(t.arena, "i8"), + .uchar => return ZigTag.type.create(t.arena, "u8"), + .short => return ZigTag.type.create(t.arena, "c_short"), + .ushort => return ZigTag.type.create(t.arena, "c_ushort"), + .int => return ZigTag.type.create(t.arena, "c_int"), + .uint => return ZigTag.type.create(t.arena, "c_uint"), + .long => return ZigTag.type.create(t.arena, "c_long"), + .ulong => return ZigTag.type.create(t.arena, "c_ulong"), + .long_long => return ZigTag.type.create(t.arena, "c_longlong"), + .ulong_long => return ZigTag.type.create(t.arena, "c_ulonglong"), + .int128 => return ZigTag.type.create(t.arena, "i128"), + .uint128 => return ZigTag.type.create(t.arena, "u128"), + }, + .float => |float_ty| switch (float_ty) { + .fp16, .float16 => return ZigTag.type.create(t.arena, "f16"), + .float => return ZigTag.type.create(t.arena, "f32"), + .double => return ZigTag.type.create(t.arena, "f64"), + .long_double => return ZigTag.type.create(t.arena, "c_longdouble"), + .float128 => return ZigTag.type.create(t.arena, "f128"), + }, + .pointer => |pointer_ty| { + const child_qt = pointer_ty.child; + + const is_fn_proto = child_qt.is(t.comp, .func); + const is_const = is_fn_proto or child_qt.@"const"; + const is_volatile = child_qt.@"volatile"; + const elem_type = try t.transType(scope, child_qt, source_loc); + const ptr_info: @FieldType(ast.Payload.Pointer, "data") = .{ + .is_const = is_const, + .is_volatile = is_volatile, + .elem_type = elem_type, + .is_allowzero = false, + }; + if (is_fn_proto or + t.typeIsOpaque(child_qt) or + t.typeWasDemotedToOpaque(child_qt)) + { + const ptr = try ZigTag.single_pointer.create(t.arena, ptr_info); + return ZigTag.optional_type.create(t.arena, ptr); + } + + return ZigTag.c_pointer.create(t.arena, ptr_info); + }, + .array => |array_ty| { + const elem_qt = array_ty.elem; + switch (array_ty.len) { + .incomplete, .unspecified_variable => { + const elem_type = try t.transType(scope, elem_qt, source_loc); + return ZigTag.c_pointer.create(t.arena, .{ + .is_const = elem_qt.@"const", + .is_volatile = elem_qt.@"volatile", + .is_allowzero = false, + .elem_type = elem_type, + }); + }, + .fixed, .static => |len| { + const elem_type = try t.transType(scope, elem_qt, source_loc); + return ZigTag.array_type.create(t.arena, .{ .len = len, .elem_type = elem_type }); + }, + .variable => return t.fail(error.UnsupportedType, source_loc, "VLA unsupported '{s}'", .{try t.getTypeStr(qt)}), + } + }, + .func => |func_ty| return t.transFnType(scope, qt, func_ty, source_loc, .{}), + .@"struct", .@"union" => |record_ty| { + var trans_scope = scope; + if (!record_ty.isAnonymous(t.comp)) { + if (t.weak_global_names.contains(record_ty.name.lookup(t.comp))) trans_scope = &t.global_scope.base; + } + try t.transRecordDecl(trans_scope, qt); + const name = t.type_decls.get(record_ty.decl_node).?; + return ZigTag.identifier.create(t.arena, name); + }, + .@"enum" => |enum_ty| { + var trans_scope = scope; + const is_anonymous = enum_ty.isAnonymous(t.comp); + if (!is_anonymous) { + if (t.weak_global_names.contains(enum_ty.name.lookup(t.comp))) trans_scope = &t.global_scope.base; + } + try t.transEnumDecl(trans_scope, qt); + const name = t.type_decls.get(enum_ty.decl_node).?; + return ZigTag.identifier.create(t.arena, name); + }, + .typedef => |typedef_ty| { + var trans_scope = scope; + const typedef_name = typedef_ty.name.lookup(t.comp); + if (builtin_typedef_map.get(typedef_name)) |builtin| return ZigTag.type.create(t.arena, builtin); + if (t.global_names.contains(typedef_name)) trans_scope = &t.global_scope.base; + + try t.transTypeDef(trans_scope, typedef_ty.decl_node); + const name = t.type_decls.get(typedef_ty.decl_node).?; + return ZigTag.identifier.create(t.arena, name); + }, + .attributed => |attributed_ty| continue :loop attributed_ty.base.type(t.comp), + .typeof => |typeof_ty| continue :loop typeof_ty.base.type(t.comp), + .vector => |vector_ty| { + const len = try t.createNumberNode(vector_ty.len, .int); + const elem_type = try t.transType(scope, vector_ty.elem, source_loc); + return ZigTag.vector.create(t.arena, .{ .lhs = len, .rhs = elem_type }); + }, + else => return t.fail(error.UnsupportedType, source_loc, "unsupported type: '{s}'", .{try t.getTypeStr(qt)}), + } +} + +/// Look ahead through the fields of the record to determine what the alignment of the record +/// would be without any align/packed/etc. attributes. This helps us determine whether or not +/// the fields with 0 offset need an `align` qualifier. Strictly speaking, we could just +/// pedantically assign those fields the same alignment as the parent's pointer alignment, +/// but this helps the generated code to be a little less verbose. +fn headFieldAlignment(t: *Translator, record_decl: aro.Type.Record) ?c_uint { + const bits_per_byte = 8; + const parent_ptr_alignment_bits = record_decl.layout.?.pointer_alignment_bits; + const parent_ptr_alignment = parent_ptr_alignment_bits / bits_per_byte; + var max_field_alignment_bits: u64 = 0; + for (record_decl.fields) |field| { + if (field.qt.getRecord(t.comp)) |field_record_decl| { + const child_record_alignment = field_record_decl.layout.?.field_alignment_bits; + if (child_record_alignment > max_field_alignment_bits) + max_field_alignment_bits = child_record_alignment; + } else { + const field_size = field.layout.size_bits; + if (field_size > max_field_alignment_bits) + max_field_alignment_bits = field_size; + } + } + if (max_field_alignment_bits != parent_ptr_alignment_bits) { + return parent_ptr_alignment; + } else { + return null; + } +} + +/// This function inspects the generated layout of a record to determine the alignment for a +/// particular field. This approach is necessary because unlike Zig, a C compiler is not +/// required to fulfill the requested alignment, which means we'd risk generating different code +/// if we only look at the user-requested alignment. +/// +/// Returns a ?c_uint to match Clang's behavior of using c_uint. The return type can be changed +/// after the Clang frontend for translate-c is removed. A null value indicates that a field is +/// 'naturally aligned'. +fn alignmentForField( + t: *Translator, + record_decl: aro.Type.Record, + head_field_alignment: ?c_uint, + field_index: usize, +) ?c_uint { + const fields = record_decl.fields; + assert(fields.len != 0); + const field = fields[field_index]; + + const bits_per_byte = 8; + const parent_ptr_alignment_bits = record_decl.layout.?.pointer_alignment_bits; + const parent_ptr_alignment = parent_ptr_alignment_bits / bits_per_byte; + + // bitfields aren't supported yet. Until support is added, records with bitfields + // should be demoted to opaque, and this function shouldn't be called for them. + if (field.bit_width != .null) { + @panic("TODO: add bitfield support for records"); + } + + const field_offset_bits: u64 = field.layout.offset_bits; + const field_size_bits: u64 = field.layout.size_bits; + + // Fields with zero width always have an alignment of 1 + if (field_size_bits == 0) { + return 1; + } + + // Fields with 0 offset inherit the parent's pointer alignment. + if (field_offset_bits == 0) { + return head_field_alignment; + } + + // Records have a natural alignment when used as a field, and their size is + // a multiple of this alignment value. For all other types, the natural alignment + // is their size. + const field_natural_alignment_bits: u64 = if (field.qt.getRecord(t.comp)) |record| + record.layout.?.field_alignment_bits + else + field_size_bits; + const rem_bits = field_offset_bits % field_natural_alignment_bits; + + // If there's a remainder, then the alignment is smaller than the field's + // natural alignment + if (rem_bits > 0) { + const rem_alignment = rem_bits / bits_per_byte; + if (rem_alignment > 0 and std.math.isPowerOfTwo(rem_alignment)) { + const actual_alignment = @min(rem_alignment, parent_ptr_alignment); + return @as(c_uint, @truncate(actual_alignment)); + } else { + return 1; + } + } + + // A field may have an offset which positions it to be naturally aligned, but the + // parent's pointer alignment determines if this is actually true, so we take the minimum + // value. + // For example, a float field (4 bytes wide) with a 4 byte offset is positioned to have natural + // alignment, but if the parent pointer alignment is 2, then the actual alignment of the + // float is 2. + const field_natural_alignment: u64 = field_natural_alignment_bits / bits_per_byte; + const offset_alignment = field_offset_bits / bits_per_byte; + const possible_alignment = @min(parent_ptr_alignment, offset_alignment); + if (possible_alignment == field_natural_alignment) { + return null; + } else if (possible_alignment < field_natural_alignment) { + if (std.math.isPowerOfTwo(possible_alignment)) { + return possible_alignment; + } else { + return 1; + } + } else { // possible_alignment > field_natural_alignment + // Here, the field is positioned be at a higher alignment than it's natural alignment. This means we + // need to determine whether it's a specified alignment. We can determine that from the padding preceding + // the field. + const padding_from_prev_field: u64 = blk: { + if (field_offset_bits != 0) { + const previous_field = fields[field_index - 1]; + break :blk (field_offset_bits - previous_field.layout.offset_bits) - previous_field.layout.size_bits; + } else { + break :blk 0; + } + }; + if (padding_from_prev_field < field_natural_alignment_bits) { + return null; + } else { + return possible_alignment; + } + } +} + +const FnProtoContext = struct { + is_pub: bool = false, + is_export: bool = false, + is_extern: bool = false, + is_always_inline: bool = false, + fn_name: ?[]const u8 = null, + has_body: bool = false, + cc: ast.Payload.Func.CallingConvention = .c, +}; + +fn transFnType( + t: *Translator, + scope: *Scope, + func_qt: QualType, + func_ty: aro.Type.Func, + source_loc: TokenIndex, + ctx: FnProtoContext, +) !ZigNode { + const param_count: usize = func_ty.params.len; + const fn_params = try t.arena.alloc(ast.Payload.Param, param_count); + + for (func_ty.params, fn_params) |param_info, *param_node| { + const param_qt = param_info.qt; + const is_noalias = param_qt.restrict; + + const param_name: ?[]const u8 = if (param_info.name == .empty) + null + else + param_info.name.lookup(t.comp); + + const type_node = try t.transType(scope, param_qt, param_info.name_tok); + param_node.* = .{ + .is_noalias = is_noalias, + .name = param_name, + .type = type_node, + }; + } + + const linksection_string = blk: { + if (func_qt.getAttribute(t.comp, .section)) |section| { + break :blk t.comp.interner.get(section.name.ref()).bytes; + } + break :blk null; + }; + + const alignment: ?c_uint = func_qt.requestedAlignment(t.comp) orelse null; + + const explicit_callconv = if ((ctx.is_always_inline or ctx.is_export or ctx.is_extern) and ctx.cc == .c) null else ctx.cc; + + const return_type_node = blk: { + if (func_qt.getAttribute(t.comp, .noreturn) != null) { + break :blk ZigTag.noreturn_type.init(); + } else { + const return_qt = func_ty.return_type; + if (return_qt.is(t.comp, .void)) { + // convert primitive anyopaque to actual void (only for return type) + break :blk ZigTag.void_type.init(); + } else { + break :blk t.transType(scope, return_qt, source_loc) catch |err| switch (err) { + error.UnsupportedType => { + try t.warn(scope, source_loc, "unsupported function proto return type", .{}); + return err; + }, + error.OutOfMemory => |e| return e, + }; + } + } + }; + + const payload = try t.arena.create(ast.Payload.Func); + payload.* = .{ + .base = .{ .tag = .func }, + .data = .{ + .is_pub = ctx.is_pub, + .is_extern = ctx.is_extern, + .is_export = ctx.is_export, + .is_inline = ctx.is_always_inline, + .is_var_args = switch (func_ty.kind) { + .normal => false, + .variadic => true, + .old_style => !ctx.is_export and !ctx.is_always_inline and !ctx.has_body, + }, + .name = ctx.fn_name, + .linksection_string = linksection_string, + .explicit_callconv = explicit_callconv, + .params = fn_params, + .return_type = return_type_node, + .body = null, + .alignment = alignment, + }, + }; + return ZigNode.initPayload(&payload.base); +} + +/// Produces a Zig AST node by translating a Type, respecting the width, but modifying the signed-ness. +/// Asserts the type is an integer. +fn transTypeIntWidthOf(t: *Translator, qt: QualType, is_signed: bool) TypeError!ZigNode { + return ZigTag.type.create(t.arena, loop: switch (qt.base(t.comp).type) { + .int => |int_ty| switch (int_ty) { + .char, .schar, .uchar => if (is_signed) "i8" else "u8", + .short, .ushort => if (is_signed) "c_short" else "c_ushort", + .int, .uint => if (is_signed) "c_int" else "c_uint", + .long, .ulong => if (is_signed) "c_long" else "c_ulong", + .long_long, .ulong_long => if (is_signed) "c_longlong" else "c_ulonglong", + .int128, .uint128 => if (is_signed) "i128" else "u128", + }, + .bit_int => |bit_int_ty| try std.fmt.allocPrint(t.arena, "{s}{d}", .{ + if (is_signed) "i" else "u", + bit_int_ty.bits, + }), + .@"enum" => |enum_ty| blk: { + const tag_ty = enum_ty.tag orelse + break :blk if (is_signed) "c_int" else "c_uint"; + + continue :loop tag_ty.base(t.comp).type; + }, + else => unreachable, // only call this function when it has already been determined the type is int + }); +} + +fn transTypeInit( + t: *Translator, + scope: *Scope, + qt: QualType, + init: Node.Index, + source_loc: TokenIndex, +) TypeError!ZigNode { + switch (init.get(t.tree)) { + .string_literal_expr => |literal| { + const elem_ty = try t.transType(scope, qt.childType(t.comp), source_loc); + + const string_lit_size = literal.qt.arrayLen(t.comp).?; + const array_size = qt.arrayLen(t.comp).?; + + if (array_size == string_lit_size) { + return ZigTag.null_sentinel_array_type.create(t.arena, .{ .len = array_size - 1, .elem_type = elem_ty }); + } else { + return ZigTag.array_type.create(t.arena, .{ .len = array_size, .elem_type = elem_ty }); + } + }, + else => {}, + } + return t.transType(scope, qt, source_loc); +} + +// ============ +// Type helpers +// ============ + +fn typeIsOpaque(t: *Translator, qt: QualType) bool { + return switch (qt.base(t.comp).type) { + .void => true, + .@"struct", .@"union" => |record_ty| { + if (record_ty.layout == null) return true; + for (record_ty.fields) |field| { + if (field.bit_width != .null) return true; + } + return false; + }, + else => false, + }; +} + +fn typeWasDemotedToOpaque(t: *Translator, qt: QualType) bool { + const base = qt.base(t.comp); + switch (base.type) { + .@"struct", .@"union" => |record_ty| { + if (t.opaque_demotes.contains(base.qt)) return true; + for (record_ty.fields) |field| { + if (t.typeWasDemotedToOpaque(field.qt)) return true; + } + return false; + }, + .@"enum" => return t.opaque_demotes.contains(base.qt), + else => return false, + } +} + +fn typeHasWrappingOverflow(t: *Translator, qt: QualType) bool { + if (t.signedness(qt) == .unsigned) { + // unsigned integer overflow wraps around. + return true; + } else { + // float, signed integer, and pointer overflow is undefined behavior. + return false; + } +} + +/// Signedness of type when translated to Zig. +/// Different from `QualType.signedness()` for `char` and enums. +/// Returns null for non-int types. +fn signedness(t: *Translator, qt: QualType) ?std.builtin.Signedness { + return loop: switch (qt.base(t.comp).type) { + .bool => .unsigned, + .bit_int => |bit_int| bit_int.signedness, + .int => |int_ty| switch (int_ty) { + .char => .unsigned, // Always translated as u8 + .schar, .short, .int, .long, .long_long, .int128 => .signed, + .uchar, .ushort, .uint, .ulong, .ulong_long, .uint128 => .unsigned, + }, + .@"enum" => |enum_ty| { + const tag_qt = enum_ty.tag orelse return .signed; + continue :loop tag_qt.base(t.comp).type; + }, + else => return null, + }; +} + +// ===================== +// Statement translation +// ===================== + +fn transStmt(t: *Translator, scope: *Scope, stmt: Node.Index) TransError!ZigNode { + switch (stmt.get(t.tree)) { + .compound_stmt => |compound| { + return t.transCompoundStmt(scope, compound); + }, + .static_assert => |static_assert| { + try t.transStaticAssert(scope, static_assert); + return ZigTag.declaration.init(); + }, + .return_stmt => |return_stmt| return t.transReturnStmt(scope, return_stmt), + .null_stmt => return ZigTag.empty_block.init(), + .if_stmt => |if_stmt| return t.transIfStmt(scope, if_stmt), + .while_stmt => |while_stmt| return t.transWhileStmt(scope, while_stmt), + .do_while_stmt => |do_while_stmt| return t.transDoWhileStmt(scope, do_while_stmt), + .for_stmt => |for_stmt| return t.transForStmt(scope, for_stmt), + .continue_stmt => return ZigTag.@"continue".init(), + .break_stmt => return ZigTag.@"break".init(), + .typedef => |typedef_decl| { + assert(!typedef_decl.implicit); + try t.transTypeDef(scope, stmt); + return ZigTag.declaration.init(); + }, + .struct_decl, .union_decl => |record_decl| { + try t.transRecordDecl(scope, record_decl.container_qt); + return ZigTag.declaration.init(); + }, + .enum_decl => |enum_decl| { + try t.transEnumDecl(scope, enum_decl.container_qt); + return ZigTag.declaration.init(); + }, + .function => |function| { + try t.transFnDecl(scope, function); + return ZigTag.declaration.init(); + }, + .variable => |variable| { + try t.transVarDecl(scope, variable); + return ZigTag.declaration.init(); + }, + .switch_stmt => |switch_stmt| return t.transSwitch(scope, switch_stmt), + .case_stmt, .default_stmt => { + return t.fail(error.UnsupportedTranslation, stmt.tok(t.tree), "TODO complex switch", .{}); + }, + .goto_stmt, .computed_goto_stmt, .labeled_stmt => { + return t.fail(error.UnsupportedTranslation, stmt.tok(t.tree), "TODO goto", .{}); + }, + else => return t.transExprCoercing(scope, stmt, .unused), + } +} + +fn transCompoundStmtInline(t: *Translator, compound: Node.CompoundStmt, block: *Scope.Block) TransError!void { + for (compound.body) |stmt| { + const result = try t.transStmt(&block.base, stmt); + switch (result.tag()) { + .declaration, .empty_block => {}, + else => try block.statements.append(t.gpa, result), + } + } +} + +fn transCompoundStmt(t: *Translator, scope: *Scope, compound: Node.CompoundStmt) TransError!ZigNode { + var block_scope = try Scope.Block.init(t, scope, false); + defer block_scope.deinit(); + try t.transCompoundStmtInline(compound, &block_scope); + return try block_scope.complete(); +} + +fn transReturnStmt(t: *Translator, scope: *Scope, return_stmt: Node.ReturnStmt) TransError!ZigNode { + switch (return_stmt.operand) { + .none => return ZigTag.return_void.init(), + .expr => |operand| { + var rhs = try t.transExprCoercing(scope, operand, .used); + const return_qt = scope.findBlockReturnType(); + if (rhs.isBoolRes() and !return_qt.is(t.comp, .bool)) { + rhs = try ZigTag.int_from_bool.create(t.arena, rhs); + } + return ZigTag.@"return".create(t.arena, rhs); + }, + .implicit => |zero| { + if (zero) return ZigTag.@"return".create(t.arena, ZigTag.zero_literal.init()); + + const return_qt = scope.findBlockReturnType(); + if (return_qt.is(t.comp, .void)) return ZigTag.empty_block.init(); + + return ZigTag.@"return".create(t.arena, ZigTag.undefined_literal.init()); + }, + } +} + +/// If a statement can possibly translate to a Zig assignment (either directly because it's +/// an assignment in C or indirectly via result assignment to `_`) AND it's the sole statement +/// in the body of an if statement or loop, then we need to put the statement into its own block. +/// The `else` case here corresponds to statements that could result in an assignment. If a statement +/// class never needs a block, add its enum to the top prong. +fn maybeBlockify(t: *Translator, scope: *Scope, stmt: Node.Index) TransError!ZigNode { + switch (stmt.get(t.tree)) { + .break_stmt, + .continue_stmt, + .compound_stmt, + .decl_ref_expr, + .enumeration_ref, + .do_while_stmt, + .for_stmt, + .if_stmt, + .return_stmt, + .null_stmt, + .while_stmt, + => return t.transStmt(scope, stmt), + else => return t.blockify(scope, stmt), + } +} + +/// Translate statement and place it in its own block. +fn blockify(t: *Translator, scope: *Scope, stmt: Node.Index) TransError!ZigNode { + var block_scope = try Scope.Block.init(t, scope, false); + defer block_scope.deinit(); + const result = try t.transStmt(&block_scope.base, stmt); + try block_scope.statements.append(t.gpa, result); + return block_scope.complete(); +} + +fn transIfStmt(t: *Translator, scope: *Scope, if_stmt: Node.IfStmt) TransError!ZigNode { + var cond_scope: Scope.Condition = .{ + .base = .{ + .parent = scope, + .id = .condition, + }, + }; + defer cond_scope.deinit(); + const cond = try t.transBoolExpr(&cond_scope.base, if_stmt.cond); + + // block needed to keep else statement from attaching to inner while + const must_blockify = (if_stmt.else_body != null) and switch (if_stmt.then_body.get(t.tree)) { + .while_stmt, .do_while_stmt, .for_stmt => true, + else => false, + }; + + const then_node = if (must_blockify) + try t.blockify(scope, if_stmt.then_body) + else + try t.maybeBlockify(scope, if_stmt.then_body); + + const else_node = if (if_stmt.else_body) |stmt| + try t.maybeBlockify(scope, stmt) + else + null; + return ZigTag.@"if".create(t.arena, .{ .cond = cond, .then = then_node, .@"else" = else_node }); +} + +fn transWhileStmt(t: *Translator, scope: *Scope, while_stmt: Node.WhileStmt) TransError!ZigNode { + var cond_scope: Scope.Condition = .{ + .base = .{ + .parent = scope, + .id = .condition, + }, + }; + defer cond_scope.deinit(); + const cond = try t.transBoolExpr(&cond_scope.base, while_stmt.cond); + + var loop_scope: Scope = .{ + .parent = scope, + .id = .loop, + }; + const body = try t.maybeBlockify(&loop_scope, while_stmt.body); + return ZigTag.@"while".create(t.arena, .{ .cond = cond, .body = body, .cont_expr = null }); +} + +fn transDoWhileStmt(t: *Translator, scope: *Scope, do_stmt: Node.DoWhileStmt) TransError!ZigNode { + var loop_scope: Scope = .{ + .parent = scope, + .id = .do_loop, + }; + + // if (!cond) break; + var cond_scope: Scope.Condition = .{ + .base = .{ + .parent = scope, + .id = .condition, + }, + }; + defer cond_scope.deinit(); + const cond = try t.transBoolExpr(&cond_scope.base, do_stmt.cond); + const if_not_break = switch (cond.tag()) { + .true_literal => { + const body_node = try t.maybeBlockify(scope, do_stmt.body); + return ZigTag.while_true.create(t.arena, body_node); + }, + else => try ZigTag.if_not_break.create(t.arena, cond), + }; + + var body_node = try t.transStmt(&loop_scope, do_stmt.body); + if (body_node.isNoreturn(true)) { + // The body node ends in a noreturn statement. Simply put it in a while (true) + // in case it contains breaks or continues. + } else if (do_stmt.body.get(t.tree) == .compound_stmt) { + // there's already a block in C, so we'll append our condition to it. + // c: do { + // c: a; + // c: b; + // c: } while(c); + // zig: while (true) { + // zig: a; + // zig: b; + // zig: if (!cond) break; + // zig: } + const block = body_node.castTag(.block).?; + block.data.stmts.len += 1; // This is safe since we reserve one extra space in Scope.Block.complete. + block.data.stmts[block.data.stmts.len - 1] = if_not_break; + } else { + // the C statement is without a block, so we need to create a block to contain it. + // c: do + // c: a; + // c: while(c); + // zig: while (true) { + // zig: a; + // zig: if (!cond) break; + // zig: } + const statements = try t.arena.alloc(ZigNode, 2); + statements[0] = body_node; + statements[1] = if_not_break; + body_node = try ZigTag.block.create(t.arena, .{ .label = null, .stmts = statements }); + } + return ZigTag.while_true.create(t.arena, body_node); +} + +fn transForStmt(t: *Translator, scope: *Scope, for_stmt: Node.ForStmt) TransError!ZigNode { + var loop_scope: Scope = .{ + .parent = scope, + .id = .loop, + }; + + var block_scope: ?Scope.Block = null; + defer if (block_scope) |*bs| bs.deinit(); + + switch (for_stmt.init) { + .decls => |decls| { + block_scope = try Scope.Block.init(t, scope, false); + loop_scope.parent = &block_scope.?.base; + for (decls) |decl| { + try t.transDecl(&block_scope.?.base, decl); + } + }, + .expr => |maybe_init| if (maybe_init) |init| { + block_scope = try Scope.Block.init(t, scope, false); + loop_scope.parent = &block_scope.?.base; + const init_node = try t.transStmt(&block_scope.?.base, init); + try loop_scope.appendNode(init_node); + }, + } + var cond_scope: Scope.Condition = .{ + .base = .{ + .parent = &loop_scope, + .id = .condition, + }, + }; + defer cond_scope.deinit(); + + const cond = if (for_stmt.cond) |cond| + try t.transBoolExpr(&cond_scope.base, cond) + else + ZigTag.true_literal.init(); + + const cont_expr = if (for_stmt.incr) |incr| + try t.transExpr(&cond_scope.base, incr, .unused) + else + null; + + const body = try t.maybeBlockify(&loop_scope, for_stmt.body); + const while_node = try ZigTag.@"while".create(t.arena, .{ .cond = cond, .body = body, .cont_expr = cont_expr }); + if (block_scope) |*bs| { + try bs.statements.append(t.gpa, while_node); + return try bs.complete(); + } else { + return while_node; + } +} + +fn transSwitch(t: *Translator, scope: *Scope, switch_stmt: Node.SwitchStmt) TransError!ZigNode { + var loop_scope: Scope = .{ + .parent = scope, + .id = .loop, + }; + + var block_scope = try Scope.Block.init(t, &loop_scope, false); + defer block_scope.deinit(); + + const base_scope = &block_scope.base; + + var cond_scope: Scope.Condition = .{ + .base = .{ + .parent = base_scope, + .id = .condition, + }, + }; + defer cond_scope.deinit(); + const switch_expr = try t.transExpr(&cond_scope.base, switch_stmt.cond, .used); + + var cases = std.ArrayList(ZigNode).init(t.gpa); + defer cases.deinit(); + var has_default = false; + + const body_node = switch_stmt.body.get(t.tree); + if (body_node != .compound_stmt) { + return t.fail(error.UnsupportedTranslation, switch_stmt.switch_tok, "TODO complex switch", .{}); + } + const body = body_node.compound_stmt.body; + // Iterate over switch body and collect all cases. + // Fallthrough is handled by duplicating statements. + for (body, 0..) |stmt, i| { + switch (stmt.get(t.tree)) { + .case_stmt => { + var items = std.ArrayList(ZigNode).init(t.gpa); + defer items.deinit(); + const sub = try t.transCaseStmt(base_scope, stmt, &items); + const res = try t.transSwitchProngStmt(base_scope, sub, body[i..]); + + if (items.items.len == 0) { + has_default = true; + const switch_else = try ZigTag.switch_else.create(t.arena, res); + try cases.append(switch_else); + } else { + const switch_prong = try ZigTag.switch_prong.create(t.arena, .{ + .cases = try t.arena.dupe(ZigNode, items.items), + .cond = res, + }); + try cases.append(switch_prong); + } + }, + .default_stmt => |default_stmt| { + has_default = true; + + var sub = default_stmt.body; + while (true) switch (sub.get(t.tree)) { + .case_stmt => |sub_case| sub = sub_case.body, + .default_stmt => |sub_default| sub = sub_default.body, + else => break, + }; + + const res = try t.transSwitchProngStmt(base_scope, sub, body[i..]); + + const switch_else = try ZigTag.switch_else.create(t.arena, res); + try cases.append(switch_else); + }, + else => {}, // collected in transSwitchProngStmt + } + } + + if (!has_default) { + const else_prong = try ZigTag.switch_else.create(t.arena, ZigTag.empty_block.init()); + try cases.append(else_prong); + } + + const switch_node = try ZigTag.@"switch".create(t.arena, .{ + .cond = switch_expr, + .cases = try t.arena.dupe(ZigNode, cases.items), + }); + try block_scope.statements.append(t.gpa, switch_node); + try block_scope.statements.append(t.gpa, ZigTag.@"break".init()); + const while_body = try block_scope.complete(); + + return ZigTag.while_true.create(t.arena, while_body); +} + +/// Collects all items for this case, returns the first statement after the labels. +/// If items ends up empty, the prong should be translated as an else. +fn transCaseStmt( + t: *Translator, + scope: *Scope, + stmt: Node.Index, + items: *std.ArrayList(ZigNode), +) TransError!Node.Index { + var sub = stmt; + var seen_default = false; + while (true) { + switch (sub.get(t.tree)) { + .default_stmt => |default_stmt| { + seen_default = true; + items.items.len = 0; + sub = default_stmt.body; + }, + .case_stmt => |case_stmt| { + if (seen_default) { + items.items.len = 0; + sub = case_stmt.body; + continue; + } + + const expr = if (case_stmt.end) |end| blk: { + const start_node = try t.transExpr(scope, case_stmt.start, .used); + const end_node = try t.transExpr(scope, end, .used); + + break :blk try ZigTag.ellipsis3.create(t.arena, .{ .lhs = start_node, .rhs = end_node }); + } else try t.transExpr(scope, case_stmt.start, .used); + + try items.append(expr); + sub = case_stmt.body; + }, + else => return sub, + } + } +} + +/// Collects all statements seen by this case into a block. +/// Avoids creating a block if the first statement is a break or return. +fn transSwitchProngStmt( + t: *Translator, + scope: *Scope, + stmt: Node.Index, + body: []const Node.Index, +) TransError!ZigNode { + switch (stmt.get(t.tree)) { + .break_stmt => return ZigTag.@"break".init(), + .return_stmt => return t.transStmt(scope, stmt), + .case_stmt, .default_stmt => unreachable, + else => { + var block_scope = try Scope.Block.init(t, scope, false); + defer block_scope.deinit(); + + // we do not need to translate `stmt` since it is the first stmt of `body` + try t.transSwitchProngStmtInline(&block_scope, body); + return try block_scope.complete(); + }, + } +} + +/// Collects all statements seen by this case into a block. +fn transSwitchProngStmtInline( + t: *Translator, + block: *Scope.Block, + body: []const Node.Index, +) TransError!void { + for (body) |stmt| { + switch (stmt.get(t.tree)) { + .return_stmt => { + const result = try t.transStmt(&block.base, stmt); + try block.statements.append(t.gpa, result); + return; + }, + .break_stmt => { + try block.statements.append(t.gpa, ZigTag.@"break".init()); + return; + }, + .case_stmt => |case_stmt| { + var sub = case_stmt.body; + while (true) switch (sub.get(t.tree)) { + .case_stmt => |sub_case| sub = sub_case.body, + .default_stmt => |sub_default| sub = sub_default.body, + else => break, + }; + const result = try t.transStmt(&block.base, sub); + assert(result.tag() != .declaration); + try block.statements.append(t.gpa, result); + if (result.isNoreturn(true)) return; + }, + .default_stmt => |default_stmt| { + var sub = default_stmt.body; + while (true) switch (sub.get(t.tree)) { + .case_stmt => |sub_case| sub = sub_case.body, + .default_stmt => |sub_default| sub = sub_default.body, + else => break, + }; + const result = try t.transStmt(&block.base, sub); + assert(result.tag() != .declaration); + try block.statements.append(t.gpa, result); + if (result.isNoreturn(true)) return; + }, + .compound_stmt => |compound_stmt| { + const result = try t.transCompoundStmt(&block.base, compound_stmt); + try block.statements.append(t.gpa, result); + if (result.isNoreturn(true)) return; + }, + else => { + const result = try t.transStmt(&block.base, stmt); + switch (result.tag()) { + .declaration, .empty_block => {}, + else => try block.statements.append(t.gpa, result), + } + }, + } + } +} + +// ====================== +// Expression translation +// ====================== + +const ResultUsed = enum { used, unused }; + +fn transExpr(t: *Translator, scope: *Scope, expr: Node.Index, used: ResultUsed) TransError!ZigNode { + const qt = expr.qt(t.tree); + return t.maybeSuppressResult(used, switch (expr.get(t.tree)) { + .paren_expr => |paren_expr| { + return t.transExpr(scope, paren_expr.operand, used); + }, + .cast => |cast| return t.transCastExpr(scope, cast, cast.qt, used, .with_as), + .decl_ref_expr => |decl_ref| try t.transDeclRefExpr(scope, decl_ref), + .enumeration_ref => |enum_ref| try t.transDeclRefExpr(scope, enum_ref), + .addr_of_expr => |addr_of_expr| try ZigTag.address_of.create(t.arena, try t.transExpr(scope, addr_of_expr.operand, .used)), + .deref_expr => |deref_expr| res: { + if (t.typeWasDemotedToOpaque(qt)) + return t.fail(error.UnsupportedTranslation, deref_expr.op_tok, "cannot dereference opaque type", .{}); + + // Dereferencing a function pointer is a no-op. + if (qt.is(t.comp, .func)) return t.transExpr(scope, deref_expr.operand, used); + + break :res try ZigTag.deref.create(t.arena, try t.transExpr(scope, deref_expr.operand, .used)); + }, + .bool_not_expr => |bool_not_expr| try ZigTag.not.create(t.arena, try t.transBoolExpr(scope, bool_not_expr.operand)), + .bit_not_expr => |bit_not_expr| try ZigTag.bit_not.create(t.arena, try t.transExpr(scope, bit_not_expr.operand, .used)), + .plus_expr => |plus_expr| return t.transExpr(scope, plus_expr.operand, used), + .negate_expr => |negate_expr| res: { + const operand_qt = negate_expr.operand.qt(t.tree); + if (!t.typeHasWrappingOverflow(operand_qt)) { + const sub_expr_node = try t.transExpr(scope, negate_expr.operand, .used); + const to_negate = if (sub_expr_node.isBoolRes()) blk: { + const ty_node = try ZigTag.type.create(t.arena, "c_int"); + const int_node = try ZigTag.int_from_bool.create(t.arena, sub_expr_node); + break :blk try ZigTag.as.create(t.arena, .{ .lhs = ty_node, .rhs = int_node }); + } else sub_expr_node; + + break :res try ZigTag.negate.create(t.arena, to_negate); + } else if (t.signedness(operand_qt) == .unsigned) { + // use -% x for unsigned integers + break :res try ZigTag.negate_wrap.create(t.arena, try t.transExpr(scope, negate_expr.operand, .used)); + } else return t.fail(error.UnsupportedTranslation, negate_expr.op_tok, "C negation with non float non integer", .{}); + }, + .div_expr => |div_expr| res: { + if (qt.isInt(t.comp) and t.signedness(qt) == .signed) { + // signed integer division uses @divTrunc + const lhs = try t.transExpr(scope, div_expr.lhs, .used); + const rhs = try t.transExpr(scope, div_expr.rhs, .used); + break :res try ZigTag.div_trunc.create(t.arena, .{ .lhs = lhs, .rhs = rhs }); + } + // unsigned/float division uses the operator + break :res try t.transBinExpr(scope, div_expr, .div); + }, + .mod_expr => |mod_expr| res: { + if (qt.isInt(t.comp) and t.signedness(qt) == .signed) { + // signed integer remainder uses __helpers.signedRemainder + const lhs = try t.transExpr(scope, mod_expr.lhs, .used); + const rhs = try t.transExpr(scope, mod_expr.rhs, .used); + break :res try t.createHelperCallNode(.signedRemainder, &.{ lhs, rhs }); + } + // unsigned/float division uses the operator + break :res try t.transBinExpr(scope, mod_expr, .mod); + }, + .add_expr => |add_expr| res: { + // `ptr + idx` and `idx + ptr` -> ptr + @as(usize, @bitCast(@as(isize, @intCast(idx)))) + const lhs_qt = add_expr.lhs.qt(t.tree); + const rhs_qt = add_expr.rhs.qt(t.tree); + if (qt.isPointer(t.comp) and (t.signedness(lhs_qt) == .signed or + t.signedness(rhs_qt) == .signed)) + { + break :res try t.transPointerArithmeticSignedOp(scope, add_expr, .add); + } + + if (t.signedness(qt) == .unsigned) { + break :res try t.transBinExpr(scope, add_expr, .add_wrap); + } else { + break :res try t.transBinExpr(scope, add_expr, .add); + } + }, + .sub_expr => |sub_expr| res: { + // `ptr - idx` -> ptr - @as(usize, @bitCast(@as(isize, @intCast(idx)))) + const lhs_qt = sub_expr.lhs.qt(t.tree); + const rhs_qt = sub_expr.rhs.qt(t.tree); + if (qt.isPointer(t.comp) and (t.signedness(lhs_qt) == .signed or + t.signedness(rhs_qt) == .signed)) + { + break :res try t.transPointerArithmeticSignedOp(scope, sub_expr, .sub); + } + + if (sub_expr.lhs.qt(t.tree).isPointer(t.comp) and sub_expr.rhs.qt(t.tree).isPointer(t.comp)) { + break :res try t.transPtrDiffExpr(scope, sub_expr); + } else if (t.signedness(qt) == .unsigned) { + break :res try t.transBinExpr(scope, sub_expr, .sub_wrap); + } else { + break :res try t.transBinExpr(scope, sub_expr, .sub); + } + }, + .mul_expr => |mul_expr| if (t.signedness(qt) == .unsigned) + try t.transBinExpr(scope, mul_expr, .mul_wrap) + else + try t.transBinExpr(scope, mul_expr, .mul), + + .less_than_expr => |lt| try t.transBinExpr(scope, lt, .less_than), + .greater_than_expr => |gt| try t.transBinExpr(scope, gt, .greater_than), + .less_than_equal_expr => |lte| try t.transBinExpr(scope, lte, .less_than_equal), + .greater_than_equal_expr => |gte| try t.transBinExpr(scope, gte, .greater_than_equal), + .equal_expr => |equal_expr| try t.transBinExpr(scope, equal_expr, .equal), + .not_equal_expr => |not_equal_expr| try t.transBinExpr(scope, not_equal_expr, .not_equal), + + .bool_and_expr => |bool_and_expr| try t.transBoolBinExpr(scope, bool_and_expr, .@"and"), + .bool_or_expr => |bool_or_expr| try t.transBoolBinExpr(scope, bool_or_expr, .@"or"), + + .bit_and_expr => |bit_and_expr| try t.transBinExpr(scope, bit_and_expr, .bit_and), + .bit_or_expr => |bit_or_expr| try t.transBinExpr(scope, bit_or_expr, .bit_or), + .bit_xor_expr => |bit_xor_expr| try t.transBinExpr(scope, bit_xor_expr, .bit_xor), + + .shl_expr => |shl_expr| try t.transShiftExpr(scope, shl_expr, .shl), + .shr_expr => |shr_expr| try t.transShiftExpr(scope, shr_expr, .shr), + + .member_access_expr => |member_access| try t.transMemberAccess(scope, .normal, member_access, null), + .member_access_ptr_expr => |member_access| try t.transMemberAccess(scope, .ptr, member_access, null), + .array_access_expr => |array_access| try t.transArrayAccess(scope, array_access, null), + + .builtin_ref => unreachable, + .builtin_call_expr => |call| return t.transBuiltinCall(scope, call, used), + .call_expr => |call| return t.transCall(scope, call, used), + + .builtin_types_compatible_p => |compatible| blk: { + const lhs = try t.transType(scope, compatible.lhs, compatible.builtin_tok); + const rhs = try t.transType(scope, compatible.rhs, compatible.builtin_tok); + + break :blk try ZigTag.equal.create(t.arena, .{ + .lhs = lhs, + .rhs = rhs, + }); + }, + .builtin_choose_expr => |choose| return t.transCondExpr(scope, choose, used), + .cond_expr => |cond_expr| return t.transCondExpr(scope, cond_expr, used), + .binary_cond_expr => |conditional| return t.transBinaryCondExpr(scope, conditional, used), + .cond_dummy_expr => unreachable, + + .assign_expr => |assign| return t.transAssignExpr(scope, assign, used), + .add_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), + .sub_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), + .mul_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), + .div_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), + .mod_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), + .shl_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), + .shr_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), + .bit_and_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), + .bit_xor_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), + .bit_or_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), + .compound_assign_dummy_expr => { + assert(used == .used); + return t.compound_assign_dummy.?; + }, + + .comma_expr => |comma_expr| return t.transCommaExpr(scope, comma_expr, used), + .pre_inc_expr => |un| return t.transIncDecExpr(scope, un, .pre, .inc, used), + .pre_dec_expr => |un| return t.transIncDecExpr(scope, un, .pre, .dec, used), + .post_inc_expr => |un| return t.transIncDecExpr(scope, un, .post, .inc, used), + .post_dec_expr => |un| return t.transIncDecExpr(scope, un, .post, .dec, used), + + .int_literal => return t.transIntLiteral(scope, expr, used, .with_as), + .char_literal => return t.transCharLiteral(scope, expr, used, .with_as), + .float_literal => return t.transFloatLiteral(scope, expr, used, .with_as), + .string_literal_expr => |literal| try t.transStringLiteral(scope, expr, literal), + .bool_literal => res: { + const val = t.tree.value_map.get(expr).?; + break :res if (val.toBool(t.comp)) + ZigTag.true_literal.init() + else + ZigTag.false_literal.init(); + }, + .nullptr_literal => ZigTag.null_literal.init(), + .imaginary_literal => |literal| { + return t.fail(error.UnsupportedTranslation, literal.op_tok, "TODO complex numbers", .{}); + }, + .compound_literal_expr => |literal| return t.transCompoundLiteral(scope, literal, used), + + .default_init_expr => |default_init| return t.transDefaultInit(scope, default_init, used, .with_as), + .array_init_expr => |array_init| return t.transArrayInit(scope, array_init, used), + .union_init_expr => |union_init| return t.transUnionInit(scope, union_init, used), + .struct_init_expr => |struct_init| return t.transStructInit(scope, struct_init, used), + .array_filler_expr => unreachable, + + .sizeof_expr => |sizeof| try t.transTypeInfo(scope, .sizeof, sizeof), + .alignof_expr => |alignof| try t.transTypeInfo(scope, .alignof, alignof), + + .imag_expr, .real_expr => |un| { + return t.fail(error.UnsupportedTranslation, un.op_tok, "TODO complex numbers", .{}); + }, + .addr_of_label => |addr_of_label| { + return t.fail(error.UnsupportedTranslation, addr_of_label.label_tok, "TODO computed goto", .{}); + }, + + .generic_expr => |generic| return t.transExpr(scope, generic.chosen, used), + .generic_association_expr => |generic| return t.transExpr(scope, generic.expr, used), + .generic_default_expr => |generic| return t.transExpr(scope, generic.expr, used), + + .stmt_expr => |stmt_expr| return t.transStmtExpr(scope, stmt_expr, used), + + .builtin_convertvector => |convertvector| try t.transConvertvectorExpr(scope, convertvector), + .builtin_shufflevector => |shufflevector| try t.transShufflevectorExpr(scope, shufflevector), + + .compound_stmt, + .static_assert, + .return_stmt, + .null_stmt, + .if_stmt, + .while_stmt, + .do_while_stmt, + .for_stmt, + .continue_stmt, + .break_stmt, + .labeled_stmt, + .switch_stmt, + .case_stmt, + .default_stmt, + .goto_stmt, + .computed_goto_stmt, + .gnu_asm_simple, + .global_asm, + .typedef, + .struct_decl, + .union_decl, + .enum_decl, + .function, + .param, + .variable, + .enum_field, + .record_field, + .struct_forward_decl, + .union_forward_decl, + .enum_forward_decl, + .empty_decl, + => unreachable, // not an expression + }); +} + +/// Same as `transExpr` but with the knowledge that the operand will be type coerced, and therefore +/// an `@as` would be redundant. This is used to prevent redundant `@as` in integer literals. +fn transExprCoercing(t: *Translator, scope: *Scope, expr: Node.Index, used: ResultUsed) TransError!ZigNode { + switch (expr.get(t.tree)) { + .int_literal => return t.transIntLiteral(scope, expr, used, .no_as), + .char_literal => return t.transCharLiteral(scope, expr, used, .no_as), + .float_literal => return t.transFloatLiteral(scope, expr, used, .no_as), + .cast => |cast| switch (cast.kind) { + .no_op => { + const operand = cast.operand.get(t.tree); + if (operand == .cast) { + return t.transCastExpr(scope, operand.cast, cast.qt, used, .no_as); + } + return t.transExprCoercing(scope, cast.operand, used); + }, + .lval_to_rval => return t.transExprCoercing(scope, cast.operand, used), + else => return t.transCastExpr(scope, cast, cast.qt, used, .no_as), + }, + .default_init_expr => |default_init| return try t.transDefaultInit(scope, default_init, used, .no_as), + .compound_literal_expr => |literal| { + if (!literal.thread_local and literal.storage_class != .static) { + return t.transExprCoercing(scope, literal.initializer, used); + } + }, + else => {}, + } + + return t.transExpr(scope, expr, used); +} + +fn transBoolExpr(t: *Translator, scope: *Scope, expr: Node.Index) TransError!ZigNode { + switch (expr.get(t.tree)) { + .int_literal => { + const int_val = t.tree.value_map.get(expr).?; + return if (int_val.isZero(t.comp)) + ZigTag.false_literal.init() + else + ZigTag.true_literal.init(); + }, + .cast => |cast| switch (cast.kind) { + .bool_to_int => return t.transExpr(scope, cast.operand, .used), + .array_to_pointer => { + const operand = cast.operand.get(t.tree); + if (operand == .string_literal_expr) { + // @intFromPtr("foo") != 0, always true + const str = try t.transStringLiteral(scope, cast.operand, operand.string_literal_expr); + const int_from_ptr = try ZigTag.int_from_ptr.create(t.arena, str); + return ZigTag.not_equal.create(t.arena, .{ .lhs = int_from_ptr, .rhs = ZigTag.zero_literal.init() }); + } + }, + else => {}, + }, + else => {}, + } + + const maybe_bool_res = try t.transExpr(scope, expr, .used); + if (maybe_bool_res.isBoolRes()) { + return maybe_bool_res; + } + + return t.finishBoolExpr(expr.qt(t.tree), maybe_bool_res); +} + +fn finishBoolExpr(t: *Translator, qt: QualType, node: ZigNode) TransError!ZigNode { + const sk = qt.scalarKind(t.comp); + if (sk == .bool) return node; + if (sk == .nullptr_t) { + // node == null, always true + return ZigTag.equal.create(t.arena, .{ .lhs = node, .rhs = ZigTag.null_literal.init() }); + } + if (sk.isPointer()) { + // node != null + return ZigTag.not_equal.create(t.arena, .{ .lhs = node, .rhs = ZigTag.null_literal.init() }); + } + if (sk != .none) { + // node != 0 + return ZigTag.not_equal.create(t.arena, .{ .lhs = node, .rhs = ZigTag.zero_literal.init() }); + } + unreachable; // Unexpected bool expression type +} + +fn transCastExpr( + t: *Translator, + scope: *Scope, + cast: Node.Cast, + dest_qt: QualType, + used: ResultUsed, + suppress_as: SuppressCast, +) TransError!ZigNode { + const operand = switch (cast.kind) { + .no_op => { + const operand = cast.operand.get(t.tree); + if (operand == .cast) { + return t.transCastExpr(scope, operand.cast, cast.qt, used, suppress_as); + } + return t.transExpr(scope, cast.operand, used); + }, + .lval_to_rval, .function_to_pointer => { + return t.transExpr(scope, cast.operand, used); + }, + .int_cast => int_cast: { + const src_qt = cast.operand.qt(t.tree); + + if (cast.implicit) { + if (t.tree.value_map.get(cast.operand)) |val| { + const max_int = try aro.Value.maxInt(dest_qt, t.comp); + const min_int = try aro.Value.minInt(dest_qt, t.comp); + + if (val.compare(.lte, max_int, t.comp) and val.compare(.gte, min_int, t.comp)) { + break :int_cast try t.transExprCoercing(scope, cast.operand, .used); + } + } + } + const operand = try t.transExpr(scope, cast.operand, .used); + break :int_cast try t.transIntCast(operand, src_qt, dest_qt); + }, + .to_void => { + assert(used == .unused); + return try t.transExpr(scope, cast.operand, .unused); + }, + .null_to_pointer => ZigTag.null_literal.init(), + .array_to_pointer => array_to_pointer: { + const child_qt = dest_qt.childType(t.comp); + + loop: switch (cast.operand.get(t.tree)) { + .string_literal_expr => |literal| { + const sub_expr_node = try t.transExpr(scope, cast.operand, .used); + + const ref = if (literal.kind == .utf8 or literal.kind == .ascii) + sub_expr_node + else + try ZigTag.address_of.create(t.arena, sub_expr_node); + + const casted = if (child_qt.@"const") + ref + else + try ZigTag.const_cast.create(t.arena, sub_expr_node); + + return t.maybeSuppressResult(used, casted); + }, + .paren_expr => |paren_expr| { + continue :loop paren_expr.operand.get(t.tree); + }, + .generic_expr => |generic| { + continue :loop generic.chosen.get(t.tree); + }, + .generic_association_expr => |generic| { + continue :loop generic.expr.get(t.tree); + }, + .generic_default_expr => |generic| { + continue :loop generic.expr.get(t.tree); + }, + else => {}, + } + + if (cast.operand.qt(t.tree).arrayLen(t.comp) == null) { + return try t.transExpr(scope, cast.operand, used); + } + + const sub_expr_node = try t.transExpr(scope, cast.operand, .used); + const ref = try ZigTag.address_of.create(t.arena, sub_expr_node); + const align_cast = try ZigTag.align_cast.create(t.arena, ref); + break :array_to_pointer try ZigTag.ptr_cast.create(t.arena, align_cast); + }, + .int_to_pointer => int_to_pointer: { + var sub_expr_node = try t.transExpr(scope, cast.operand, .used); + const operand_qt = cast.operand.qt(t.tree); + if (t.signedness(operand_qt) == .signed or operand_qt.bitSizeof(t.comp) > t.comp.target.ptrBitWidth()) { + sub_expr_node = try ZigTag.as.create(t.arena, .{ + .lhs = try ZigTag.type.create(t.arena, "usize"), + .rhs = try ZigTag.int_cast.create(t.arena, sub_expr_node), + }); + } + break :int_to_pointer try ZigTag.ptr_from_int.create(t.arena, sub_expr_node); + }, + .int_to_bool => { + const sub_expr_node = try t.transExpr(scope, cast.operand, .used); + if (sub_expr_node.isBoolRes()) return sub_expr_node; + if (cast.operand.qt(t.tree).is(t.comp, .bool)) return sub_expr_node; + const cmp_node = try ZigTag.not_equal.create(t.arena, .{ .lhs = sub_expr_node, .rhs = ZigTag.zero_literal.init() }); + return t.maybeSuppressResult(used, cmp_node); + }, + .float_to_bool => { + const sub_expr_node = try t.transExpr(scope, cast.operand, .used); + const cmp_node = try ZigTag.not_equal.create(t.arena, .{ .lhs = sub_expr_node, .rhs = ZigTag.zero_literal.init() }); + return t.maybeSuppressResult(used, cmp_node); + }, + .pointer_to_bool => { + const sub_expr_node = try t.transExpr(scope, cast.operand, .used); + + // Special case function pointers as @intFromPtr(expr) != 0 + if (cast.operand.qt(t.tree).get(t.comp, .pointer)) |ptr_ty| if (ptr_ty.child.is(t.comp, .func)) { + const ptr_node = if (sub_expr_node.tag() == .identifier) + try ZigTag.address_of.create(t.arena, sub_expr_node) + else + sub_expr_node; + const int_from_ptr = try ZigTag.int_from_ptr.create(t.arena, ptr_node); + const cmp_node = try ZigTag.not_equal.create(t.arena, .{ .lhs = int_from_ptr, .rhs = ZigTag.zero_literal.init() }); + return t.maybeSuppressResult(used, cmp_node); + }; + + const cmp_node = try ZigTag.not_equal.create(t.arena, .{ .lhs = sub_expr_node, .rhs = ZigTag.null_literal.init() }); + return t.maybeSuppressResult(used, cmp_node); + }, + .bool_to_int => bool_to_int: { + const sub_expr_node = try t.transExpr(scope, cast.operand, .used); + break :bool_to_int try ZigTag.int_from_bool.create(t.arena, sub_expr_node); + }, + .bool_to_float => bool_to_float: { + const sub_expr_node = try t.transExpr(scope, cast.operand, .used); + const int_from_bool = try ZigTag.int_from_bool.create(t.arena, sub_expr_node); + break :bool_to_float try ZigTag.float_from_int.create(t.arena, int_from_bool); + }, + .bool_to_pointer => bool_to_pointer: { + const sub_expr_node = try t.transExpr(scope, cast.operand, .used); + const int_from_bool = try ZigTag.int_from_bool.create(t.arena, sub_expr_node); + break :bool_to_pointer try ZigTag.ptr_from_int.create(t.arena, int_from_bool); + }, + .float_cast => float_cast: { + const sub_expr_node = try t.transExpr(scope, cast.operand, .used); + break :float_cast try ZigTag.float_cast.create(t.arena, sub_expr_node); + }, + .int_to_float => int_to_float: { + const sub_expr_node = try t.transExpr(scope, cast.operand, used); + const int_node = if (sub_expr_node.isBoolRes()) + try ZigTag.int_from_bool.create(t.arena, sub_expr_node) + else + sub_expr_node; + break :int_to_float try ZigTag.float_from_int.create(t.arena, int_node); + }, + .float_to_int => float_to_int: { + const sub_expr_node = try t.transExpr(scope, cast.operand, .used); + break :float_to_int try ZigTag.int_from_float.create(t.arena, sub_expr_node); + }, + .pointer_to_int => pointer_to_int: { + const sub_expr_node = try t.transPointerCastExpr(scope, cast.operand); + const ptr_node = try ZigTag.int_from_ptr.create(t.arena, sub_expr_node); + break :pointer_to_int try ZigTag.int_cast.create(t.arena, ptr_node); + }, + .bitcast => bitcast: { + const sub_expr_node = try t.transPointerCastExpr(scope, cast.operand); + const operand_qt = cast.operand.qt(t.tree); + if (dest_qt.isPointer(t.comp) and operand_qt.isPointer(t.comp)) { + var casted = try ZigTag.align_cast.create(t.arena, sub_expr_node); + casted = try ZigTag.ptr_cast.create(t.arena, casted); + + const src_elem = operand_qt.childType(t.comp); + const dest_elem = dest_qt.childType(t.comp); + if ((src_elem.@"const" or src_elem.is(t.comp, .func)) and !dest_elem.@"const") { + casted = try ZigTag.const_cast.create(t.arena, casted); + } + if (src_elem.@"volatile" and !dest_elem.@"volatile") { + casted = try ZigTag.volatile_cast.create(t.arena, casted); + } + break :bitcast casted; + } + + break :bitcast try ZigTag.bit_cast.create(t.arena, sub_expr_node); + }, + .union_cast => union_cast: { + const union_type = try t.transType(scope, dest_qt, cast.l_paren); + + const operand_qt = cast.operand.qt(t.tree); + const union_base = dest_qt.base(t.comp); + const field = for (union_base.type.@"union".fields) |field| { + if (field.qt.eql(operand_qt, t.comp)) break field; + } else unreachable; + const field_name = if (field.name_tok == 0) t.anonymous_record_field_names.get(.{ + .parent = union_base.qt, + .field = field.qt, + }).? else field.name.lookup(t.comp); + + const field_init = try t.arena.create(ast.Payload.ContainerInit.Initializer); + field_init.* = .{ + .name = field_name, + .value = try t.transExpr(scope, cast.operand, .used), + }; + break :union_cast try ZigTag.container_init.create(t.arena, .{ + .lhs = union_type, + .inits = field_init[0..1], + }); + }, + else => return t.fail(error.UnsupportedTranslation, cast.l_paren, "TODO translate {s} cast", .{@tagName(cast.kind)}), + }; + if (suppress_as == .no_as) return t.maybeSuppressResult(used, operand); + if (used == .unused) return t.maybeSuppressResult(used, operand); + const as = try ZigTag.as.create(t.arena, .{ + .lhs = try t.transType(scope, dest_qt, cast.l_paren), + .rhs = operand, + }); + return as; +} + +fn transIntCast(t: *Translator, operand: ZigNode, src_qt: QualType, dest_qt: QualType) !ZigNode { + const src_dest_order = src_qt.intRankOrder(dest_qt, t.comp); + const different_sign = t.signedness(src_qt) != t.signedness(dest_qt); + const needs_bitcast = different_sign and !(t.signedness(src_qt) == .unsigned and src_dest_order == .lt); + + var casted = operand; + if (casted.isBoolRes()) { + casted = try ZigTag.int_from_bool.create(t.arena, casted); + } else if (src_dest_order == .gt) { + // No C type is smaller than the 1 bit from @intFromBool + casted = try ZigTag.truncate.create(t.arena, casted); + } + if (needs_bitcast) { + if (src_dest_order != .eq) { + casted = try ZigTag.as.create(t.arena, .{ + .lhs = try t.transTypeIntWidthOf(dest_qt, t.signedness(src_qt) == .signed), + .rhs = casted, + }); + } + return ZigTag.bit_cast.create(t.arena, casted); + } + return casted; +} + +/// Same as `transExpr` but adds a `&` if the expression is an identifier referencing a function type. +fn transPointerCastExpr(t: *Translator, scope: *Scope, expr: Node.Index) TransError!ZigNode { + const sub_expr_node = try t.transExpr(scope, expr, .used); + switch (expr.get(t.tree)) { + .cast => |cast| if (cast.kind == .function_to_pointer and sub_expr_node.tag() == .identifier) { + return ZigTag.address_of.create(t.arena, sub_expr_node); + }, + else => {}, + } + return sub_expr_node; +} + +fn transDeclRefExpr(t: *Translator, scope: *Scope, decl_ref: Node.DeclRef) TransError!ZigNode { + const name = t.tree.tokSlice(decl_ref.name_tok); + const maybe_alias = scope.getAlias(name); + const mangled_name = maybe_alias orelse name; + + switch (decl_ref.decl.get(t.tree)) { + .function => |function| if (function.definition == null and function.body == null) { + // Try translating the decl again in case of out of scope declaration. + try t.transFnDecl(scope, function); + }, + else => {}, + } + + const decl = decl_ref.decl.get(t.tree); + const ref_expr = blk: { + const identifier = try ZigTag.identifier.create(t.arena, mangled_name); + if (decl_ref.qt.is(t.comp, .func) and maybe_alias != null) { + break :blk try ZigTag.field_access.create(t.arena, .{ + .lhs = identifier, + .field_name = name, + }); + } + if (decl == .variable and maybe_alias != null) { + switch (decl.variable.storage_class) { + .@"extern", .static => { + break :blk try ZigTag.field_access.create(t.arena, .{ + .lhs = identifier, + .field_name = name, + }); + }, + else => {}, + } + } + break :blk identifier; + }; + + scope.skipVariableDiscard(mangled_name); + return ref_expr; +} + +fn transBinExpr(t: *Translator, scope: *Scope, bin: Node.Binary, op_id: ZigTag) TransError!ZigNode { + const lhs_uncasted = try t.transExpr(scope, bin.lhs, .used); + const rhs_uncasted = try t.transExpr(scope, bin.rhs, .used); + + const lhs = if (lhs_uncasted.isBoolRes()) + try ZigTag.int_from_bool.create(t.arena, lhs_uncasted) + else + lhs_uncasted; + + const rhs = if (rhs_uncasted.isBoolRes()) + try ZigTag.int_from_bool.create(t.arena, rhs_uncasted) + else + rhs_uncasted; + + return t.createBinOpNode(op_id, lhs, rhs); +} + +fn transBoolBinExpr(t: *Translator, scope: *Scope, bin: Node.Binary, op: ZigTag) !ZigNode { + std.debug.assert(op == .@"and" or op == .@"or"); + + const lhs = try t.transBoolExpr(scope, bin.lhs); + const rhs = try t.transBoolExpr(scope, bin.rhs); + + return t.createBinOpNode(op, lhs, rhs); +} + +fn transShiftExpr(t: *Translator, scope: *Scope, bin: Node.Binary, op_id: ZigTag) !ZigNode { + std.debug.assert(op_id == .shl or op_id == .shr); + + // lhs >> @intCast(rh) + const lhs = try t.transExpr(scope, bin.lhs, .used); + + const rhs = try t.transExprCoercing(scope, bin.rhs, .used); + const rhs_casted = try ZigTag.int_cast.create(t.arena, rhs); + + return t.createBinOpNode(op_id, lhs, rhs_casted); +} + +fn transCondExpr( + t: *Translator, + scope: *Scope, + conditional: Node.Conditional, + used: ResultUsed, +) TransError!ZigNode { + var cond_scope: Scope.Condition = .{ + .base = .{ + .parent = scope, + .id = .condition, + }, + }; + defer cond_scope.deinit(); + + const res_is_bool = conditional.qt.is(t.comp, .bool); + const cond = try t.transBoolExpr(&cond_scope.base, conditional.cond); + + var then_body = try t.transExpr(scope, conditional.then_expr, used); + if (!res_is_bool and then_body.isBoolRes()) { + then_body = try ZigTag.int_from_bool.create(t.arena, then_body); + } + + var else_body = try t.transExpr(scope, conditional.else_expr, used); + if (!res_is_bool and else_body.isBoolRes()) { + else_body = try ZigTag.int_from_bool.create(t.arena, else_body); + } + + // The `ResultUsed` is forwarded to both branches so no need to suppress the result here. + return ZigTag.@"if".create(t.arena, .{ .cond = cond, .then = then_body, .@"else" = else_body }); +} + +fn transBinaryCondExpr( + t: *Translator, + scope: *Scope, + conditional: Node.Conditional, + used: ResultUsed, +) TransError!ZigNode { + // GNU extension of the ternary operator where the middle expression is + // omitted, the condition itself is returned if it evaluates to true. + + if (used == .unused) { + // Result unused so this can be translated as + // if (condition) else_expr; + var cond_scope: Scope.Condition = .{ + .base = .{ + .parent = scope, + .id = .condition, + }, + }; + defer cond_scope.deinit(); + + return ZigTag.@"if".create(t.arena, .{ + .cond = try t.transBoolExpr(&cond_scope.base, conditional.cond), + .then = try t.transExpr(scope, conditional.else_expr, .unused), + .@"else" = null, + }); + } + + const res_is_bool = conditional.qt.is(t.comp, .bool); + // c: (condition)?:(else_expr) + // zig: (blk: { + // const _cond_temp = (condition); + // break :blk if (_cond_temp) _cond_temp else (else_expr); + // }) + var block_scope = try Scope.Block.init(t, scope, true); + defer block_scope.deinit(); + + const cond_temp = try block_scope.reserveMangledName("cond_temp"); + const init_node = try t.transExpr(&block_scope.base, conditional.cond, .used); + const temp_decl = try ZigTag.var_simple.create(t.arena, .{ .name = cond_temp, .init = init_node }); + try block_scope.statements.append(t.gpa, temp_decl); + + var cond_scope: Scope.Condition = .{ + .base = .{ + .parent = &block_scope.base, + .id = .condition, + }, + }; + defer cond_scope.deinit(); + + const cond_ident = try ZigTag.identifier.create(t.arena, cond_temp); + const cond_node = try t.finishBoolExpr(conditional.cond.qt(t.tree), cond_ident); + var then_body = cond_ident; + if (!res_is_bool and init_node.isBoolRes()) { + then_body = try ZigTag.int_from_bool.create(t.arena, then_body); + } + + var else_body = try t.transExpr(&block_scope.base, conditional.else_expr, .used); + if (!res_is_bool and else_body.isBoolRes()) { + else_body = try ZigTag.int_from_bool.create(t.arena, else_body); + } + const if_node = try ZigTag.@"if".create(t.arena, .{ + .cond = cond_node, + .then = then_body, + .@"else" = else_body, + }); + const break_node = try ZigTag.break_val.create(t.arena, .{ + .label = block_scope.label, + .val = if_node, + }); + try block_scope.statements.append(t.gpa, break_node); + return block_scope.complete(); +} + +fn transCommaExpr(t: *Translator, scope: *Scope, bin: Node.Binary, used: ResultUsed) TransError!ZigNode { + if (used == .unused) { + const lhs = try t.transExprCoercing(scope, bin.lhs, .unused); + try scope.appendNode(lhs); + const rhs = try t.transExprCoercing(scope, bin.rhs, .unused); + return rhs; + } + + var block_scope = try Scope.Block.init(t, scope, true); + defer block_scope.deinit(); + + const lhs = try t.transExprCoercing(&block_scope.base, bin.lhs, .unused); + try block_scope.statements.append(t.gpa, lhs); + + const rhs = try t.transExprCoercing(&block_scope.base, bin.rhs, .used); + const break_node = try ZigTag.break_val.create(t.arena, .{ + .label = block_scope.label, + .val = rhs, + }); + try block_scope.statements.append(t.gpa, break_node); + + return try block_scope.complete(); +} + +fn transAssignExpr(t: *Translator, scope: *Scope, bin: Node.Binary, used: ResultUsed) !ZigNode { + if (used == .unused) { + const lhs = try t.transExpr(scope, bin.lhs, .used); + var rhs = try t.transExprCoercing(scope, bin.rhs, .used); + + const lhs_qt = bin.lhs.qt(t.tree); + if (rhs.isBoolRes() and !lhs_qt.is(t.comp, .bool)) { + rhs = try ZigTag.int_from_bool.create(t.arena, rhs); + } + + return t.createBinOpNode(.assign, lhs, rhs); + } + + var block_scope = try Scope.Block.init(t, scope, true); + defer block_scope.deinit(); + + const tmp = try block_scope.reserveMangledName("tmp"); + + var rhs = try t.transExpr(&block_scope.base, bin.rhs, .used); + const lhs_qt = bin.lhs.qt(t.tree); + if (rhs.isBoolRes() and !lhs_qt.is(t.comp, .bool)) { + rhs = try ZigTag.int_from_bool.create(t.arena, rhs); + } + + const tmp_decl = try ZigTag.var_simple.create(t.arena, .{ .name = tmp, .init = rhs }); + try block_scope.statements.append(t.gpa, tmp_decl); + + const lhs = try t.transExprCoercing(&block_scope.base, bin.lhs, .used); + const tmp_ident = try ZigTag.identifier.create(t.arena, tmp); + + const assign = try t.createBinOpNode(.assign, lhs, tmp_ident); + try block_scope.statements.append(t.gpa, assign); + + const break_node = try ZigTag.break_val.create(t.arena, .{ + .label = block_scope.label, + .val = tmp_ident, + }); + try block_scope.statements.append(t.gpa, break_node); + + return try block_scope.complete(); +} + +fn transCompoundAssign( + t: *Translator, + scope: *Scope, + assign: Node.Binary, + used: ResultUsed, +) !ZigNode { + // If the result is unused we can try using the equivalent Zig operator + // without a block + if (used == .unused) { + if (try t.transCompoundAssignSimple(scope, null, assign)) |some| { + return some; + } + } + + // Otherwise we need to wrap the the compound assignment in a block. + var block_scope = try Scope.Block.init(t, scope, used == .used); + defer block_scope.deinit(); + const ref = try block_scope.reserveMangledName("ref"); + + const lhs_expr = try t.transExpr(&block_scope.base, assign.lhs, .used); + const addr_of = try ZigTag.address_of.create(t.arena, lhs_expr); + const ref_decl = try ZigTag.var_simple.create(t.arena, .{ .name = ref, .init = addr_of }); + try block_scope.statements.append(t.gpa, ref_decl); + + const lhs_node = try ZigTag.identifier.create(t.arena, ref); + const ref_node = try ZigTag.deref.create(t.arena, lhs_node); + + // Use the equivalent Zig operator if possible. + if (try t.transCompoundAssignSimple(scope, ref_node, assign)) |some| { + try block_scope.statements.append(t.gpa, some); + } else { + const old_dummy = t.compound_assign_dummy; + defer t.compound_assign_dummy = old_dummy; + t.compound_assign_dummy = ref_node; + + // Otherwise do the operation and assignment separately. + const rhs_node = try t.transExprCoercing(&block_scope.base, assign.rhs, .used); + const assign_node = try t.createBinOpNode(.assign, ref_node, rhs_node); + try block_scope.statements.append(t.gpa, assign_node); + } + + if (used == .used) { + const break_node = try ZigTag.break_val.create(t.arena, .{ + .label = block_scope.label, + .val = ref_node, + }); + try block_scope.statements.append(t.gpa, break_node); + } + return block_scope.complete(); +} + +/// Translates compound assignment using the equivalent Zig operator if possible. +fn transCompoundAssignSimple(t: *Translator, scope: *Scope, lhs_dummy_opt: ?ZigNode, assign: Node.Binary) TransError!?ZigNode { + const assign_rhs = assign.rhs.get(t.tree); + if (assign_rhs == .cast) return null; + + const is_signed = t.signedness(assign.qt) == .signed; + switch (assign_rhs) { + .div_expr, .mod_expr => if (is_signed) return null, + else => {}, + } + const lhs_ptr = assign.qt.isPointer(t.comp); + + const bin, const op: ZigTag, const cast: enum { none, shift, usize } = switch (assign_rhs) { + .add_expr => |bin| .{ + bin, + if (t.typeHasWrappingOverflow(bin.qt)) .add_wrap_assign else .add_assign, + if (lhs_ptr and t.signedness(bin.rhs.qt(t.tree)) == .signed) .usize else .none, + }, + .sub_expr => |bin| .{ + bin, + if (t.typeHasWrappingOverflow(bin.qt)) .sub_wrap_assign else .sub_assign, + if (lhs_ptr and t.signedness(bin.rhs.qt(t.tree)) == .signed) .usize else .none, + }, + .mul_expr => |bin| .{ + bin, + if (t.typeHasWrappingOverflow(bin.qt)) .mul_wrap_assign else .mul_assign, + .none, + }, + .mod_expr => |bin| .{ bin, .mod_assign, .none }, + .div_expr => |bin| .{ bin, .div_assign, .none }, + .shl_expr => |bin| .{ bin, .shl_assign, .shift }, + .shr_expr => |bin| .{ bin, .shr_assign, .shift }, + .bit_and_expr => |bin| .{ bin, .bit_and_assign, .none }, + .bit_xor_expr => |bin| .{ bin, .bit_xor_assign, .none }, + .bit_or_expr => |bin| .{ bin, .bit_or_assign, .none }, + else => unreachable, + }; + + const lhs_node = blk: { + const old_dummy = t.compound_assign_dummy; + defer t.compound_assign_dummy = old_dummy; + t.compound_assign_dummy = lhs_dummy_opt orelse try t.transExpr(scope, assign.lhs, .used); + + break :blk try t.transExpr(scope, bin.lhs, .used); + }; + + const rhs_node = try t.transExprCoercing(scope, bin.rhs, .used); + const casted_rhs = switch (cast) { + .none => rhs_node, + .shift => try ZigTag.int_cast.create(t.arena, rhs_node), + .usize => try t.usizeCastForWrappingPtrArithmetic(rhs_node), + }; + return try t.createBinOpNode(op, lhs_node, casted_rhs); +} + +fn transIncDecExpr( + t: *Translator, + scope: *Scope, + un: Node.Unary, + position: enum { pre, post }, + kind: enum { inc, dec }, + used: ResultUsed, +) !ZigNode { + const is_wrapping = t.typeHasWrappingOverflow(un.qt); + const op: ZigTag = switch (kind) { + .inc => if (is_wrapping) .add_wrap_assign else .add_assign, + .dec => if (is_wrapping) .sub_wrap_assign else .sub_assign, + }; + + const one_literal = ZigTag.one_literal.init(); + if (used == .unused) { + const operand = try t.transExpr(scope, un.operand, .used); + return try t.createBinOpNode(op, operand, one_literal); + } + + var block_scope = try Scope.Block.init(t, scope, true); + defer block_scope.deinit(); + + const ref = try block_scope.reserveMangledName("ref"); + const operand = try t.transExprCoercing(&block_scope.base, un.operand, .used); + const operand_ref = try ZigTag.address_of.create(t.arena, operand); + const ref_decl = try ZigTag.var_simple.create(t.arena, .{ .name = ref, .init = operand_ref }); + try block_scope.statements.append(t.gpa, ref_decl); + + const ref_ident = try ZigTag.identifier.create(t.arena, ref); + const ref_deref = try ZigTag.deref.create(t.arena, ref_ident); + const effect = try t.createBinOpNode(op, ref_deref, one_literal); + + switch (position) { + .pre => { + try block_scope.statements.append(t.gpa, effect); + + const break_node = try ZigTag.break_val.create(t.arena, .{ + .label = block_scope.label, + .val = ref_deref, + }); + try block_scope.statements.append(t.gpa, break_node); + }, + .post => { + const tmp = try block_scope.reserveMangledName("tmp"); + const tmp_decl = try ZigTag.var_simple.create(t.arena, .{ .name = tmp, .init = ref_deref }); + try block_scope.statements.append(t.gpa, tmp_decl); + + try block_scope.statements.append(t.gpa, effect); + + const tmp_ident = try ZigTag.identifier.create(t.arena, tmp); + const break_node = try ZigTag.break_val.create(t.arena, .{ + .label = block_scope.label, + .val = tmp_ident, + }); + try block_scope.statements.append(t.gpa, break_node); + }, + } + + return try block_scope.complete(); +} + +fn transPtrDiffExpr(t: *Translator, scope: *Scope, bin: Node.Binary) TransError!ZigNode { + const lhs_uncasted = try t.transExpr(scope, bin.lhs, .used); + const rhs_uncasted = try t.transExpr(scope, bin.rhs, .used); + + const lhs = try ZigTag.int_from_ptr.create(t.arena, lhs_uncasted); + const rhs = try ZigTag.int_from_ptr.create(t.arena, rhs_uncasted); + + const sub_res = try t.createBinOpNode(.sub_wrap, lhs, rhs); + + // @divExact(@as(, @bitCast(@intFromPtr(lhs)) -% @intFromPtr(rhs)), @sizeOf()) + const ptrdiff_type = try t.transTypeIntWidthOf(bin.qt, true); + + const bitcast = try ZigTag.as.create(t.arena, .{ + .lhs = ptrdiff_type, + .rhs = try ZigTag.bit_cast.create(t.arena, sub_res), + }); + + // C standard requires that pointer subtraction operands are of the same type, + // otherwise it is undefined behavior. So we can assume the left and right + // sides are the same Type and arbitrarily choose left. + const lhs_ty = try t.transType(scope, bin.lhs.qt(t.tree), bin.lhs.tok(t.tree)); + const c_pointer = t.getContainer(lhs_ty).?; + + if (c_pointer.castTag(.c_pointer)) |c_pointer_payload| { + const sizeof = try ZigTag.sizeof.create(t.arena, c_pointer_payload.data.elem_type); + return ZigTag.div_exact.create(t.arena, .{ + .lhs = bitcast, + .rhs = sizeof, + }); + } else { + // This is an opaque/incomplete type. This subtraction exhibits Undefined Behavior by the C99 spec. + // However, allowing subtraction on `void *` and function pointers is a commonly used extension. + // So, just return the value in byte units, mirroring the behavior of this language extension as implemented by GCC and Clang. + return bitcast; + } +} + +/// Translate an arithmetic expression with a pointer operand and a signed-integer operand. +/// Zig requires a usize argument for pointer arithmetic, so we intCast to isize and then +/// bitcast to usize; pointer wraparound makes the math work. +/// Zig pointer addition is not commutative (unlike C); the pointer operand needs to be on the left. +/// The + operator in C is not a sequence point so it should be safe to switch the order if necessary. +fn transPointerArithmeticSignedOp(t: *Translator, scope: *Scope, bin: Node.Binary, op_id: ZigTag) TransError!ZigNode { + std.debug.assert(op_id == .add or op_id == .sub); + + const lhs_qt = bin.lhs.qt(t.tree); + const swap_operands = op_id == .add and t.signedness(lhs_qt) == .signed; + + const swizzled_lhs = if (swap_operands) bin.rhs else bin.lhs; + const swizzled_rhs = if (swap_operands) bin.lhs else bin.rhs; + + const lhs_node = try t.transExpr(scope, swizzled_lhs, .used); + const rhs_node = try t.transExpr(scope, swizzled_rhs, .used); + + const bitcast_node = try t.usizeCastForWrappingPtrArithmetic(rhs_node); + + return t.createBinOpNode(op_id, lhs_node, bitcast_node); +} + +fn transMemberAccess( + t: *Translator, + scope: *Scope, + kind: enum { normal, ptr }, + member_access: Node.MemberAccess, + opt_base: ?ZigNode, +) TransError!ZigNode { + const base_info = switch (kind) { + .normal => member_access.base.qt(t.tree), + .ptr => member_access.base.qt(t.tree).childType(t.comp), + }; + const record = base_info.getRecord(t.comp).?; + const field = record.fields[member_access.member_index]; + const field_name = if (field.name_tok == 0) t.anonymous_record_field_names.get(.{ + .parent = base_info.base(t.comp).qt, + .field = field.qt, + }).? else field.name.lookup(t.comp); + const base_node = opt_base orelse try t.transExpr(scope, member_access.base, .used); + const lhs = switch (kind) { + .normal => base_node, + .ptr => try ZigTag.deref.create(t.arena, base_node), + }; + const field_access = try ZigTag.field_access.create(t.arena, .{ + .lhs = lhs, + .field_name = field_name, + }); + + // Flexible array members are translated as member functions. + if (member_access.member_index == record.fields.len - 1 or base_info.base(t.comp).type == .@"union") { + if (field.qt.get(t.comp, .array)) |array_ty| { + if (array_ty.len == .incomplete or (array_ty.len == .fixed and array_ty.len.fixed == 0)) { + return ZigTag.call.create(t.arena, .{ .lhs = field_access, .args = &.{} }); + } + } + } + + return field_access; +} + +fn transArrayAccess(t: *Translator, scope: *Scope, array_access: Node.ArrayAccess, opt_base: ?ZigNode) TransError!ZigNode { + // Unwrap the base statement if it's an array decayed to a bare pointer type + // so that we index the array itself + const base = base: { + const base = array_access.base.get(t.tree); + if (base != .cast) break :base array_access.base; + if (base.cast.kind != .array_to_pointer) break :base array_access.base; + break :base base.cast.operand; + }; + + const base_node = opt_base orelse try t.transExpr(scope, base, .used); + const index = index: { + const index = try t.transExpr(scope, array_access.index, .used); + const index_qt = array_access.index.qt(t.tree); + const maybe_bigger_than_usize = switch (index_qt.base(t.comp).type) { + .bool => { + break :index try ZigTag.int_from_bool.create(t.arena, index); + }, + .int => |int| switch (int) { + .long_long, .ulong_long, .int128, .uint128 => true, + else => false, + }, + .bit_int => |bit_int| bit_int.bits > t.comp.target.ptrBitWidth(), + else => unreachable, + }; + + const is_nonnegative_int_literal = if (t.tree.value_map.get(array_access.index)) |val| + val.compare(.gte, .zero, t.comp) + else + false; + const is_signed = t.signedness(index_qt) == .signed; + + if (is_signed and !is_nonnegative_int_literal) { + // First cast to `isize` to get proper sign extension and + // then @bitCast to `usize` to satisfy the compiler. + const index_isize = try ZigTag.as.create(t.arena, .{ + .lhs = try ZigTag.type.create(t.arena, "isize"), + .rhs = try ZigTag.int_cast.create(t.arena, index), + }); + break :index try ZigTag.bit_cast.create(t.arena, index_isize); + } + + if (maybe_bigger_than_usize) { + break :index try ZigTag.int_cast.create(t.arena, index); + } + break :index index; + }; + + return ZigTag.array_access.create(t.arena, .{ + .lhs = base_node, + .rhs = index, + }); +} + +fn transOffsetof(t: *Translator, scope: *Scope, arg: Node.Index) TransError!ZigNode { + // Translate __builtin_offsetof(T, designator) as + // @intFromPtr(&(@as(*allowzero T, @ptrFromInt(0)).designator)) + const member = try t.transMemberDesignator(scope, arg); + const address = try ZigTag.address_of.create(t.arena, member); + return ZigTag.int_from_ptr.create(t.arena, address); +} + +fn transMemberDesignator(t: *Translator, scope: *Scope, arg: Node.Index) TransError!ZigNode { + switch (arg.get(t.tree)) { + .default_init_expr => |default| { + const elem_node = try t.transType(scope, default.qt, default.last_tok); + const ptr_ty = try ZigTag.single_pointer.create(t.arena, .{ + .elem_type = elem_node, + .is_allowzero = true, + .is_const = false, + .is_volatile = false, + }); + const zero = try ZigTag.ptr_from_int.create(t.arena, ZigTag.zero_literal.init()); + return ZigTag.as.create(t.arena, .{ .lhs = ptr_ty, .rhs = zero }); + }, + .array_access_expr => |access| { + const base = try t.transMemberDesignator(scope, access.base); + return t.transArrayAccess(scope, access, base); + }, + .member_access_expr => |access| { + const base = try t.transMemberDesignator(scope, access.base); + return t.transMemberAccess(scope, .normal, access, base); + }, + .cast => |cast| { + assert(cast.kind == .array_to_pointer); + return t.transMemberDesignator(scope, cast.operand); + }, + else => unreachable, + } +} + +fn transBuiltinCall( + t: *Translator, + scope: *Scope, + call: Node.BuiltinCall, + used: ResultUsed, +) TransError!ZigNode { + const builtin_name = t.tree.tokSlice(call.builtin_tok); + if (std.mem.eql(u8, builtin_name, "__builtin_offsetof")) { + const res = try t.transOffsetof(scope, call.args[0]); + return t.maybeSuppressResult(used, res); + } + + const builtin = builtins.map.get(builtin_name) orelse + return t.fail(error.UnsupportedTranslation, call.builtin_tok, "TODO implement function '{s}' in std.zig.c_builtins", .{builtin_name}); + + if (builtin.tag) |tag| switch (tag) { + .byte_swap, .ceil, .cos, .sin, .exp, .exp2, .exp10, .abs, .log, .log2, .log10, .round, .sqrt, .trunc, .floor => { + assert(call.args.len == 1); + const arg = try t.transExprCoercing(scope, call.args[0], .used); + const arg_ty = try t.transType(scope, call.args[0].qt(t.tree), call.args[0].tok(t.tree)); + const coerced = try ZigTag.as.create(t.arena, .{ .lhs = arg_ty, .rhs = arg }); + + const ptr = try t.arena.create(ast.Payload.UnOp); + ptr.* = .{ .base = .{ .tag = tag }, .data = coerced }; + return t.maybeSuppressResult(used, ZigNode.initPayload(&ptr.base)); + }, + .@"unreachable" => return ZigTag.@"unreachable".init(), + else => unreachable, + }; + + const arg_nodes = try t.arena.alloc(ZigNode, call.args.len); + for (call.args, arg_nodes) |c_arg, *zig_arg| { + zig_arg.* = try t.transExprCoercing(scope, c_arg, .used); + } + + const builtin_identifier = try ZigTag.identifier.create(t.arena, "__builtin"); + const field_access = try ZigTag.field_access.create(t.arena, .{ + .lhs = builtin_identifier, + .field_name = builtin.name, + }); + + const res = try ZigTag.call.create(t.arena, .{ + .lhs = field_access, + .args = arg_nodes, + }); + if (call.qt.is(t.comp, .void)) return res; + return t.maybeSuppressResult(used, res); +} + +fn transCall( + t: *Translator, + scope: *Scope, + call: Node.Call, + used: ResultUsed, +) TransError!ZigNode { + const raw_fn_expr = try t.transExpr(scope, call.callee, .used); + const fn_expr = blk: { + loop: switch (call.callee.get(t.tree)) { + .paren_expr => |paren_expr| { + continue :loop paren_expr.operand.get(t.tree); + }, + .decl_ref_expr => |decl_ref| { + if (decl_ref.qt.is(t.comp, .func)) break :blk raw_fn_expr; + }, + .cast => |cast| { + if (cast.kind == .function_to_pointer) { + continue :loop cast.operand.get(t.tree); + } + }, + .deref_expr, .addr_of_expr => |un| { + continue :loop un.operand.get(t.tree); + }, + .generic_expr => |generic| { + continue :loop generic.chosen.get(t.tree); + }, + .generic_association_expr => |generic| { + continue :loop generic.expr.get(t.tree); + }, + .generic_default_expr => |generic| { + continue :loop generic.expr.get(t.tree); + }, + else => {}, + } + break :blk try ZigTag.unwrap.create(t.arena, raw_fn_expr); + }; + + const callee_qt = call.callee.qt(t.tree); + const maybe_ptr_ty = callee_qt.get(t.comp, .pointer); + const func_qt = if (maybe_ptr_ty) |ptr| ptr.child else callee_qt; + const func_ty = func_qt.get(t.comp, .func).?; + + const arg_nodes = try t.arena.alloc(ZigNode, call.args.len); + for (call.args, arg_nodes, 0..) |c_arg, *zig_arg, i| { + if (i < func_ty.params.len) { + zig_arg.* = try t.transExprCoercing(scope, c_arg, .used); + + if (zig_arg.isBoolRes() and !func_ty.params[i].qt.is(t.comp, .bool)) { + // In C the result type of a boolean expression is int. If this result is passed as + // an argument to a function whose parameter is also int, there is no cast. Therefore + // in Zig we'll need to cast it from bool to u1 (which will safely coerce to c_int). + zig_arg.* = try ZigTag.int_from_bool.create(t.arena, zig_arg.*); + } + } else { + zig_arg.* = try t.transExpr(scope, c_arg, .used); + + if (zig_arg.isBoolRes()) { + // Same as above but now we don't have a result type. + const u1_node = try ZigTag.int_from_bool.create(t.arena, zig_arg.*); + const c_int_node = try ZigTag.type.create(t.arena, "c_int"); + zig_arg.* = try ZigTag.as.create(t.arena, .{ .lhs = c_int_node, .rhs = u1_node }); + } + } + } + + const res = try ZigTag.call.create(t.arena, .{ + .lhs = fn_expr, + .args = arg_nodes, + }); + if (call.qt.is(t.comp, .void)) return res; + return t.maybeSuppressResult(used, res); +} + +const SuppressCast = enum { with_as, no_as }; + +fn transIntLiteral( + t: *Translator, + scope: *Scope, + literal_index: Node.Index, + used: ResultUsed, + suppress_as: SuppressCast, +) TransError!ZigNode { + const val = t.tree.value_map.get(literal_index).?; + const int_lit_node = try t.createIntNode(val); + if (suppress_as == .no_as) { + return t.maybeSuppressResult(used, int_lit_node); + } + + // Integer literals in C have types, and this can matter for several reasons. + // For example, this is valid C: + // unsigned char y = 256; + // How this gets evaluated is the 256 is an integer, which gets truncated to signed char, then bit-casted + // to unsigned char, resulting in 0. In order for this to work, we have to emit this zig code: + // var y = @as(u8, @bitCast(@as(i8, @truncate(@as(c_int, 256))))); + + // @as(T, x) + const ty_node = try t.transType(scope, literal_index.qt(t.tree), literal_index.tok(t.tree)); + const as = try ZigTag.as.create(t.arena, .{ .lhs = ty_node, .rhs = int_lit_node }); + return t.maybeSuppressResult(used, as); +} + +fn transCharLiteral( + t: *Translator, + scope: *Scope, + literal_index: Node.Index, + used: ResultUsed, + suppress_as: SuppressCast, +) TransError!ZigNode { + const val = t.tree.value_map.get(literal_index).?; + const char_literal = literal_index.get(t.tree).char_literal; + const narrow = char_literal.kind == .ascii or char_literal.kind == .utf8; + + // C has a somewhat obscure feature called multi-character character constant + // e.g. 'abcd' + const int_value = val.toInt(u32, t.comp).?; + const int_lit_node = if (char_literal.kind == .ascii and int_value > 255) + try t.createNumberNode(int_value, .int) + else + try t.createCharLiteralNode(narrow, int_value); + + if (suppress_as == .no_as) { + return t.maybeSuppressResult(used, int_lit_node); + } + + // See comment in `transIntLiteral` for why this code is here. + // @as(T, x) + const as_node = try ZigTag.as.create(t.arena, .{ + .lhs = try t.transType(scope, char_literal.qt, char_literal.literal_tok), + .rhs = int_lit_node, + }); + return t.maybeSuppressResult(used, as_node); +} + +fn transFloatLiteral( + t: *Translator, + scope: *Scope, + literal_index: Node.Index, + used: ResultUsed, + suppress_as: SuppressCast, +) TransError!ZigNode { + const val = t.tree.value_map.get(literal_index).?; + const float_literal = literal_index.get(t.tree).float_literal; + + var allocating: std.Io.Writer.Allocating = .init(t.gpa); + defer allocating.deinit(); + _ = val.print(float_literal.qt, t.comp, &allocating.writer) catch return error.OutOfMemory; + + const float_lit_node = try ZigTag.float_literal.create(t.arena, try t.arena.dupe(u8, allocating.getWritten())); + if (suppress_as == .no_as) { + return t.maybeSuppressResult(used, float_lit_node); + } + + const as_node = try ZigTag.as.create(t.arena, .{ + .lhs = try t.transType(scope, float_literal.qt, float_literal.literal_tok), + .rhs = float_lit_node, + }); + return t.maybeSuppressResult(used, as_node); +} + +fn transStringLiteral( + t: *Translator, + scope: *Scope, + expr: Node.Index, + literal: Node.CharLiteral, +) TransError!ZigNode { + switch (literal.kind) { + .ascii, .utf8 => return t.transNarrowStringLiteral(expr, literal), + .utf16, .utf32, .wide => { + const name = try std.fmt.allocPrint(t.arena, "{s}_string_{d}", .{ @tagName(literal.kind), t.getMangle() }); + + const array_type = try t.transTypeInit(scope, literal.qt, expr, literal.literal_tok); + const lit_array = try t.transStringLiteralInitializer(expr, literal, array_type); + const decl = try ZigTag.var_simple.create(t.arena, .{ .name = name, .init = lit_array }); + try scope.appendNode(decl); + return ZigTag.identifier.create(t.arena, name); + }, + } +} + +fn transNarrowStringLiteral( + t: *Translator, + expr: Node.Index, + literal: Node.CharLiteral, +) TransError!ZigNode { + const val = t.tree.value_map.get(expr).?; + + const bytes = t.comp.interner.get(val.ref()).bytes; + var allocating: std.Io.Writer.Allocating = try .initCapacity(t.gpa, bytes.len); + defer allocating.deinit(); + + aro.Value.printString(bytes, literal.qt, t.comp, &allocating.writer) catch return error.OutOfMemory; + + return ZigTag.string_literal.create(t.arena, try t.arena.dupe(u8, allocating.getWritten())); +} + +/// Translate a string literal that is initializing an array. In general narrow string +/// literals become `"".*` or `""[0..].*` if they need truncation. +/// Wide string literals become an array of integers. zero-fillers pad out the array to +/// the appropriate length, if necessary. +fn transStringLiteralInitializer( + t: *Translator, + expr: Node.Index, + literal: Node.CharLiteral, + array_type: ZigNode, +) TransError!ZigNode { + assert(array_type.tag() == .array_type or array_type.tag() == .null_sentinel_array_type); + + const is_narrow = literal.kind == .ascii or literal.kind == .utf8; + + // The length of the string literal excluding the sentinel. + const str_length = literal.qt.arrayLen(t.comp).? - 1; + + const payload = (array_type.castTag(.array_type) orelse array_type.castTag(.null_sentinel_array_type).?).data; + const array_size = payload.len; + const elem_type = payload.elem_type; + + if (array_size == 0) return ZigTag.empty_array.create(t.arena, array_type); + + const num_inits = @min(str_length, array_size); + if (num_inits == 0) { + return ZigTag.array_filler.create(t.arena, .{ + .type = elem_type, + .filler = ZigTag.zero_literal.init(), + .count = array_size, + }); + } + + const init_node = if (is_narrow) blk: { + // "string literal".* or string literal"[0..num_inits].* + var str = try t.transNarrowStringLiteral(expr, literal); + if (str_length != array_size) str = try ZigTag.string_slice.create(t.arena, .{ .string = str, .end = num_inits }); + break :blk try ZigTag.deref.create(t.arena, str); + } else blk: { + const size = literal.qt.childType(t.comp).sizeof(t.comp); + + const val = t.tree.value_map.get(expr).?; + const bytes = t.comp.interner.get(val.ref()).bytes; + + const init_list = try t.arena.alloc(ZigNode, @intCast(num_inits)); + for (init_list, 0..) |*item, i| { + const codepoint = switch (size) { + 2 => @as(*const u16, @alignCast(@ptrCast(bytes.ptr + i * 2))).*, + 4 => @as(*const u32, @alignCast(@ptrCast(bytes.ptr + i * 4))).*, + else => unreachable, + }; + item.* = try t.createCharLiteralNode(false, codepoint); + } + const init_args: ast.Payload.Array.ArrayTypeInfo = .{ .len = num_inits, .elem_type = elem_type }; + const init_array_type = if (array_type.tag() == .array_type) + try ZigTag.array_type.create(t.arena, init_args) + else + try ZigTag.null_sentinel_array_type.create(t.arena, init_args); + break :blk try ZigTag.array_init.create(t.arena, .{ + .cond = init_array_type, + .cases = init_list, + }); + }; + + if (num_inits == array_size) return init_node; + assert(array_size > str_length); // If array_size <= str_length, `num_inits == array_size` and we've already returned. + + const filler_node = try ZigTag.array_filler.create(t.arena, .{ + .type = elem_type, + .filler = ZigTag.zero_literal.init(), + .count = array_size - str_length, + }); + return ZigTag.array_cat.create(t.arena, .{ .lhs = init_node, .rhs = filler_node }); +} + +fn transCompoundLiteral( + t: *Translator, + scope: *Scope, + literal: Node.CompoundLiteral, + used: ResultUsed, +) TransError!ZigNode { + if (used == .unused) { + return t.transExpr(scope, literal.initializer, .unused); + } + + // TODO taking a reference to a compound literal should result in a mutable + // pointer (unless the literal is const). + + const initializer = try t.transExprCoercing(scope, literal.initializer, .used); + const ty = try t.transType(scope, literal.qt, literal.l_paren_tok); + if (!literal.thread_local and literal.storage_class != .static) { + // In the simple case a compound literal can be translated + // simply as `@as(type, initializer)`. + return ZigTag.as.create(t.arena, .{ .lhs = ty, .rhs = initializer }); + } + + // Otherwise static or thread local compound literals are translated as + // a reference to a variable wrapped in a struct. + + var block_scope = try Scope.Block.init(t, scope, true); + defer block_scope.deinit(); + + const tmp = try block_scope.reserveMangledName("tmp"); + const wrapped_name = "compound_literal"; + + // const tmp = struct { var compound_literal = initializer }; + const temp_decl = try ZigTag.var_decl.create(t.arena, .{ + .is_pub = false, + .is_const = literal.qt.@"const", + .is_extern = false, + .is_export = false, + .is_threadlocal = literal.thread_local, + .linksection_string = null, + .alignment = null, + .name = wrapped_name, + .type = ty, + .init = initializer, + }); + const wrapped = try ZigTag.wrapped_local.create(t.arena, .{ .name = tmp, .init = temp_decl }); + try block_scope.statements.append(t.gpa, wrapped); + + // break :blk tmp.compound_literal + const static_tmp_ident = try ZigTag.identifier.create(t.arena, tmp); + const field_access = try ZigTag.field_access.create(t.arena, .{ + .lhs = static_tmp_ident, + .field_name = wrapped_name, + }); + const break_node = try ZigTag.break_val.create(t.arena, .{ + .label = block_scope.label, + .val = field_access, + }); + try block_scope.statements.append(t.gpa, break_node); + + return block_scope.complete(); +} + +fn transDefaultInit( + t: *Translator, + scope: *Scope, + default_init: Node.DefaultInit, + used: ResultUsed, + suppress_as: SuppressCast, +) TransError!ZigNode { + assert(used == .used); + const type_node = try t.transType(scope, default_init.qt, default_init.last_tok); + return try t.createZeroValueNode(default_init.qt, type_node, suppress_as); +} + +fn transArrayInit( + t: *Translator, + scope: *Scope, + array_init: Node.ContainerInit, + used: ResultUsed, +) TransError!ZigNode { + assert(used == .used); + const array_item_qt = array_init.container_qt.childType(t.comp); + const array_item_type = try t.transType(scope, array_item_qt, array_init.l_brace_tok); + var maybe_lhs: ?ZigNode = null; + var val_list: std.ArrayListUnmanaged(ZigNode) = .empty; + defer val_list.deinit(t.gpa); + var i: usize = 0; + while (i < array_init.items.len) { + const rhs = switch (array_init.items[i].get(t.tree)) { + .array_filler_expr => |array_filler| blk: { + const node = try ZigTag.array_filler.create(t.arena, .{ + .type = array_item_type, + .filler = try t.createZeroValueNode(array_item_qt, array_item_type, .no_as), + .count = @intCast(array_filler.count), + }); + i += 1; + break :blk node; + }, + else => blk: { + defer val_list.clearRetainingCapacity(); + while (i < array_init.items.len) : (i += 1) { + if (array_init.items[i].get(t.tree) == .array_filler_expr) break; + const expr = try t.transExprCoercing(scope, array_init.items[i], .used); + try val_list.append(t.gpa, expr); + } + const array_type = try ZigTag.array_type.create(t.arena, .{ + .elem_type = array_item_type, + .len = val_list.items.len, + }); + const array_init_node = try ZigTag.array_init.create(t.arena, .{ + .cond = array_type, + .cases = try t.arena.dupe(ZigNode, val_list.items), + }); + break :blk array_init_node; + }, + }; + maybe_lhs = if (maybe_lhs) |lhs| blk: { + const cat = try ZigTag.array_cat.create(t.arena, .{ + .lhs = lhs, + .rhs = rhs, + }); + break :blk cat; + } else rhs; + } + return maybe_lhs orelse try ZigTag.container_init_dot.create(t.arena, &.{}); +} + +fn transUnionInit( + t: *Translator, + scope: *Scope, + union_init: Node.UnionInit, + used: ResultUsed, +) TransError!ZigNode { + assert(used == .used); + const init_expr = union_init.initializer orelse + return ZigTag.undefined_literal.init(); + + if (init_expr.get(t.tree) == .default_init_expr) { + return try t.transExpr(scope, init_expr, used); + } + + const union_type = try t.transType(scope, union_init.union_qt, union_init.l_brace_tok); + + const union_base = union_init.union_qt.base(t.comp); + const field = union_base.type.@"union".fields[union_init.field_index]; + const field_name = if (field.name_tok == 0) t.anonymous_record_field_names.get(.{ + .parent = union_base.qt, + .field = field.qt, + }).? else field.name.lookup(t.comp); + + const field_init = try t.arena.create(ast.Payload.ContainerInit.Initializer); + field_init.* = .{ + .name = field_name, + .value = try t.transExprCoercing(scope, init_expr, .used), + }; + const container_init = try ZigTag.container_init.create(t.arena, .{ + .lhs = union_type, + .inits = field_init[0..1], + }); + return container_init; +} + +fn transStructInit( + t: *Translator, + scope: *Scope, + struct_init: Node.ContainerInit, + used: ResultUsed, +) TransError!ZigNode { + assert(used == .used); + const struct_type = try t.transType(scope, struct_init.container_qt, struct_init.l_brace_tok); + const field_inits = try t.arena.alloc(ast.Payload.ContainerInit.Initializer, struct_init.items.len); + + const struct_base = struct_init.container_qt.base(t.comp); + for ( + field_inits, + struct_init.items, + struct_base.type.@"struct".fields, + ) |*init, field_expr, field| { + const field_name = if (field.name_tok == 0) t.anonymous_record_field_names.get(.{ + .parent = struct_base.qt, + .field = field.qt, + }).? else field.name.lookup(t.comp); + init.* = .{ + .name = field_name, + .value = try t.transExprCoercing(scope, field_expr, .used), + }; + } + + const container_init = try ZigTag.container_init.create(t.arena, .{ + .lhs = struct_type, + .inits = field_inits, + }); + return container_init; +} + +fn transTypeInfo( + t: *Translator, + scope: *Scope, + op: ZigTag, + typeinfo: Node.TypeInfo, +) TransError!ZigNode { + const operand = operand: { + if (typeinfo.expr) |expr| { + const operand = try t.transExpr(scope, expr, .used); + break :operand try ZigTag.typeof.create(t.arena, operand); + } + break :operand try t.transType(scope, typeinfo.operand_qt, typeinfo.op_tok); + }; + + const payload = try t.arena.create(ast.Payload.UnOp); + payload.* = .{ + .base = .{ .tag = op }, + .data = operand, + }; + return ZigNode.initPayload(&payload.base); +} + +fn transStmtExpr( + t: *Translator, + scope: *Scope, + stmt_expr: Node.Unary, + used: ResultUsed, +) TransError!ZigNode { + const compound_stmt = stmt_expr.operand.get(t.tree).compound_stmt; + if (used == .unused) { + return t.transCompoundStmt(scope, compound_stmt); + } + var block_scope = try Scope.Block.init(t, scope, true); + defer block_scope.deinit(); + + for (compound_stmt.body[0 .. compound_stmt.body.len - 1]) |stmt| { + const result = try t.transStmt(&block_scope.base, stmt); + switch (result.tag()) { + .declaration, .empty_block => {}, + else => try block_scope.statements.append(t.gpa, result), + } + } + + const last_result = try t.transExpr(&block_scope.base, compound_stmt.body[compound_stmt.body.len - 1], .used); + switch (last_result.tag()) { + .declaration, .empty_block => {}, + else => { + const break_node = try ZigTag.break_val.create(t.arena, .{ + .label = block_scope.label, + .val = last_result, + }); + try block_scope.statements.append(t.gpa, break_node); + }, + } + return block_scope.complete(); +} + +fn transConvertvectorExpr( + t: *Translator, + scope: *Scope, + convertvector: Node.Convertvector, +) TransError!ZigNode { + var block_scope = try Scope.Block.init(t, scope, true); + defer block_scope.deinit(); + + const src_expr_node = try t.transExpr(&block_scope.base, convertvector.operand, .used); + const tmp = try block_scope.reserveMangledName("tmp"); + const tmp_decl = try ZigTag.var_simple.create(t.arena, .{ .name = tmp, .init = src_expr_node }); + try block_scope.statements.append(t.gpa, tmp_decl); + const tmp_ident = try ZigTag.identifier.create(t.arena, tmp); + + const dest_type_node = try t.transType(&block_scope.base, convertvector.dest_qt, convertvector.builtin_tok); + const dest_vec_ty = convertvector.dest_qt.get(t.comp, .vector).?; + const src_vec_ty = convertvector.operand.qt(t.tree).get(t.comp, .vector).?; + + const src_elem_sk = src_vec_ty.elem.scalarKind(t.comp); + const dest_elem_sk = convertvector.dest_qt.childType(t.comp).scalarKind(t.comp); + + const items = try t.arena.alloc(ZigNode, dest_vec_ty.len); + for (items, 0..dest_vec_ty.len) |*item, i| { + const value = try ZigTag.array_access.create(t.arena, .{ + .lhs = tmp_ident, + .rhs = try t.createNumberNode(i, .int), + }); + + if (src_elem_sk == .float and dest_elem_sk == .float) { + item.* = try ZigTag.float_cast.create(t.arena, value); + } else if (src_elem_sk == .float) { + item.* = try ZigTag.int_from_float.create(t.arena, value); + } else if (dest_elem_sk == .float) { + item.* = try ZigTag.float_from_int.create(t.arena, value); + } else { + item.* = try t.transIntCast(value, src_vec_ty.elem, dest_vec_ty.elem); + } + } + + const vec_init = try ZigTag.array_init.create(t.arena, .{ + .cond = dest_type_node, + .cases = items, + }); + const break_node = try ZigTag.break_val.create(t.arena, .{ + .label = block_scope.label, + .val = vec_init, + }); + try block_scope.statements.append(t.gpa, break_node); + + return block_scope.complete(); +} + +fn transShufflevectorExpr( + t: *Translator, + scope: *Scope, + shufflevector: Node.Shufflevector, +) TransError!ZigNode { + if (shufflevector.indexes.len == 0) { + return t.fail(error.UnsupportedTranslation, shufflevector.builtin_tok, "@shuffle needs at least 1 index", .{}); + } + + const a = try t.transExpr(scope, shufflevector.lhs, .used); + const b = try t.transExpr(scope, shufflevector.rhs, .used); + + // First two arguments to __builtin_shufflevector must be the same type + const vector_child_type = try t.vectorTypeInfo(a, "child"); + const vector_len = try t.vectorTypeInfo(a, "len"); + const shuffle_mask = blk: { + const mask_len = shufflevector.indexes.len; + + const mask_type = try ZigTag.vector.create(t.arena, .{ + .lhs = try t.createNumberNode(mask_len, .int), + .rhs = try ZigTag.type.create(t.arena, "i32"), + }); + + const init_list = try t.arena.alloc(ZigNode, mask_len); + for (init_list, shufflevector.indexes) |*init, index| { + const index_expr = try t.transExprCoercing(scope, index, .used); + const converted_index = try t.createHelperCallNode(.shuffleVectorIndex, &.{ index_expr, vector_len }); + init.* = converted_index; + } + + break :blk try ZigTag.array_init.create(t.arena, .{ + .cond = mask_type, + .cases = init_list, + }); + }; + + return ZigTag.shuffle.create(t.arena, .{ + .element_type = vector_child_type, + .a = a, + .b = b, + .mask_vector = shuffle_mask, + }); +} + +// ===================== +// Node creation helpers +// ===================== + +fn createZeroValueNode( + t: *Translator, + qt: QualType, + type_node: ZigNode, + suppress_as: SuppressCast, +) !ZigNode { + switch (qt.base(t.comp).type) { + .bool => return ZigTag.false_literal.init(), + .int, .bit_int, .float => { + const zero_literal = ZigTag.zero_literal.init(); + return switch (suppress_as) { + .with_as => try t.createBinOpNode(.as, type_node, zero_literal), + .no_as => zero_literal, + }; + }, + .pointer => { + const null_literal = ZigTag.null_literal.init(); + return switch (suppress_as) { + .with_as => try t.createBinOpNode(.as, type_node, null_literal), + .no_as => null_literal, + }; + }, + else => {}, + } + return try ZigTag.std_mem_zeroes.create(t.arena, type_node); +} + +fn createIntNode(t: *Translator, int: aro.Value) !ZigNode { + var space: aro.Interner.Tag.Int.BigIntSpace = undefined; + var big = t.comp.interner.get(int.ref()).toBigInt(&space); + const is_negative = !big.positive; + big.positive = true; + + const str = big.toStringAlloc(t.arena, 10, .lower) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + }; + const res = try ZigTag.integer_literal.create(t.arena, str); + if (is_negative) return ZigTag.negate.create(t.arena, res); + return res; +} + +fn createNumberNode(t: *Translator, num: anytype, num_kind: enum { int, float }) !ZigNode { + const fmt_s = switch (@typeInfo(@TypeOf(num))) { + .int, .comptime_int => "{d}", + else => "{s}", + }; + const str = try std.fmt.allocPrint(t.arena, fmt_s, .{num}); + if (num_kind == .float) + return ZigTag.float_literal.create(t.arena, str) + else + return ZigTag.integer_literal.create(t.arena, str); +} + +fn createCharLiteralNode(t: *Translator, narrow: bool, val: u32) TransError!ZigNode { + return ZigTag.char_literal.create(t.arena, if (narrow) + try std.fmt.allocPrint(t.arena, "'{f}'", .{std.zig.fmtChar(&.{@as(u8, @intCast(val))})}) + else + try std.fmt.allocPrint(t.arena, "'\\u{{{x}}}'", .{val})); +} + +fn createBinOpNode( + t: *Translator, + op: ZigTag, + lhs: ZigNode, + rhs: ZigNode, +) !ZigNode { + const payload = try t.arena.create(ast.Payload.BinOp); + payload.* = .{ + .base = .{ .tag = op }, + .data = .{ + .lhs = lhs, + .rhs = rhs, + }, + }; + return ZigNode.initPayload(&payload.base); +} + +pub fn createHelperCallNode(t: *Translator, name: std.meta.DeclEnum(@import("helpers")), args_opt: ?[]const ZigNode) !ZigNode { + if (args_opt) |args| { + return ZigTag.helper_call.create(t.arena, .{ + .name = @tagName(name), + .args = try t.arena.dupe(ZigNode, args), + }); + } else { + return ZigTag.helper_ref.create(t.arena, @tagName(name)); + } +} + +/// Cast a signed integer node to a usize, for use in pointer arithmetic. Negative numbers +/// will become very large positive numbers but that is ok since we only use this in +/// pointer arithmetic expressions, where wraparound will ensure we get the correct value. +/// node -> @as(usize, @bitCast(@as(isize, @intCast(node)))) +fn usizeCastForWrappingPtrArithmetic(t: *Translator, node: ZigNode) TransError!ZigNode { + const intcast_node = try ZigTag.as.create(t.arena, .{ + .lhs = try ZigTag.type.create(t.arena, "isize"), + .rhs = try ZigTag.int_cast.create(t.arena, node), + }); + + return ZigTag.as.create(t.arena, .{ + .lhs = try ZigTag.type.create(t.arena, "usize"), + .rhs = try ZigTag.bit_cast.create(t.arena, intcast_node), + }); +} + +/// @typeInfo(@TypeOf(vec_node)).vector. +fn vectorTypeInfo(t: *Translator, vec_node: ZigNode, field: []const u8) TransError!ZigNode { + const typeof_call = try ZigTag.typeof.create(t.arena, vec_node); + const typeinfo_call = try ZigTag.typeinfo.create(t.arena, typeof_call); + const vector_type_info = try ZigTag.field_access.create(t.arena, .{ .lhs = typeinfo_call, .field_name = "vector" }); + return ZigTag.field_access.create(t.arena, .{ .lhs = vector_type_info, .field_name = field }); +} + +/// Build a getter function for a flexible array field in a C record +/// e.g. `T items[]` or `T items[0]`. The generated function returns a [*c] pointer +/// to the flexible array with the correct const and volatile qualifiers +fn createFlexibleMemberFn( + t: *Translator, + member_name: []const u8, + field_name: []const u8, +) Error!ZigNode { + const self_param_name = "self"; + const self_param = try ZigTag.identifier.create(t.arena, self_param_name); + const self_type = try ZigTag.typeof.create(t.arena, self_param); + + const fn_params = try t.arena.alloc(ast.Payload.Param, 1); + fn_params[0] = .{ + .name = self_param_name, + .type = ZigTag.@"anytype".init(), + .is_noalias = false, + }; + + // @typeInfo(@TypeOf(self.*.)).pointer.child + const dereffed = try ZigTag.deref.create(t.arena, self_param); + const field_access = try ZigTag.field_access.create(t.arena, .{ .lhs = dereffed, .field_name = field_name }); + const type_of = try ZigTag.typeof.create(t.arena, field_access); + const type_info = try ZigTag.typeinfo.create(t.arena, type_of); + const array_info = try ZigTag.field_access.create(t.arena, .{ .lhs = type_info, .field_name = "array" }); + const child_info = try ZigTag.field_access.create(t.arena, .{ .lhs = array_info, .field_name = "child" }); + + const return_type = try t.createHelperCallNode(.FlexibleArrayType, &.{ self_type, child_info }); + + // return @ptrCast(&self.*.); + const address_of = try ZigTag.address_of.create(t.arena, field_access); + const casted = try ZigTag.ptr_cast.create(t.arena, address_of); + const return_stmt = try ZigTag.@"return".create(t.arena, casted); + const body = try ZigTag.block_single.create(t.arena, return_stmt); + + return ZigTag.func.create(t.arena, .{ + .is_pub = true, + .is_extern = false, + .is_export = false, + .is_inline = false, + .is_var_args = false, + .name = member_name, + .linksection_string = null, + .explicit_callconv = null, + .params = fn_params, + .return_type = return_type, + .body = body, + .alignment = null, + }); +} + +// ================= +// Macro translation +// ================= + +fn transMacros(t: *Translator) !void { + var tok_list = std.ArrayList(CToken).init(t.gpa); + defer tok_list.deinit(); + + var pattern_list = try PatternList.init(t.gpa); + defer pattern_list.deinit(t.gpa); + + for (t.pp.defines.keys(), t.pp.defines.values()) |name, macro| { + if (macro.is_builtin) continue; + if (t.global_scope.containsNow(name)) { + continue; + } + + tok_list.items.len = 0; + try tok_list.ensureUnusedCapacity(macro.tokens.len); + for (macro.tokens) |tok| { + switch (tok.id) { + .invalid => continue, + .whitespace => continue, + .comment => continue, + .macro_ws => continue, + else => {}, + } + tok_list.appendAssumeCapacity(tok); + } + + if (macro.is_func) { + const ms: PatternList.MacroSlicer = .{ + .tokens = tok_list.items, + .source = t.comp.getSource(macro.loc.id).buf, + .params = @intCast(macro.params.len), + }; + if (try pattern_list.match(ms)) |impl| { + const decl = try ZigTag.pub_var_simple.create(t.arena, .{ + .name = name, + .init = try t.createHelperCallNode(impl, null), + }); + try t.addTopLevelDecl(name, decl); + continue; + } + } + + if (t.checkTranslatableMacro(tok_list.items, macro.params)) |err| { + switch (err) { + .undefined_identifier => |ident| try t.failDeclExtra(&t.global_scope.base, macro.loc, name, "unable to translate macro: undefined identifier `{s}`", .{ident}), + .invalid_arg_usage => |ident| try t.failDeclExtra(&t.global_scope.base, macro.loc, name, "unable to translate macro: untranslatable usage of arg `{s}`", .{ident}), + } + continue; + } + + var macro_translator: MacroTranslator = .{ + .t = t, + .tokens = tok_list.items, + .source = t.comp.getSource(macro.loc.id).buf, + .name = name, + .macro = macro, + }; + + const res = if (macro.is_func) + macro_translator.transFnMacro() + else + macro_translator.transMacro(); + res catch |err| switch (err) { + error.ParseError => continue, + error.OutOfMemory => |e| return e, + }; + } +} + +const MacroTranslateError = union(enum) { + undefined_identifier: []const u8, + invalid_arg_usage: []const u8, +}; + +fn checkTranslatableMacro(t: *Translator, tokens: []const CToken, params: []const []const u8) ?MacroTranslateError { + var last_is_type_kw = false; + var i: usize = 0; + while (i < tokens.len) : (i += 1) { + const token = tokens[i]; + switch (token.id) { + .period, .arrow => i += 1, // skip next token since field identifiers can be unknown + .keyword_struct, .keyword_union, .keyword_enum => if (!last_is_type_kw) { + last_is_type_kw = true; + continue; + }, + .macro_param, .macro_param_no_expand => { + if (last_is_type_kw) { + return .{ .invalid_arg_usage = params[token.end] }; + } + }, + .identifier, .extended_identifier => { + const identifier = t.pp.tokSlice(token); + if (!t.global_scope.contains(identifier) and !builtins.map.has(identifier)) { + return .{ .undefined_identifier = identifier }; + } + }, + else => {}, + } + last_is_type_kw = false; + } + return null; +} + +fn getContainer(t: *Translator, node: ZigNode) ?ZigNode { + switch (node.tag()) { + .@"union", + .@"struct", + .address_of, + .bit_not, + .not, + .optional_type, + .negate, + .negate_wrap, + .array_type, + .c_pointer, + .single_pointer, + => return node, + + .identifier => { + const ident = node.castTag(.identifier).?; + if (t.global_scope.sym_table.get(ident.data)) |value| { + if (value.castTag(.var_decl)) |var_decl| + return t.getContainer(var_decl.data.init.?); + if (value.castTag(.var_simple) orelse value.castTag(.pub_var_simple)) |var_decl| + return t.getContainer(var_decl.data.init); + } + }, + + .field_access => { + const field_access = node.castTag(.field_access).?; + + if (t.getContainerTypeOf(field_access.data.lhs)) |ty_node| { + if (ty_node.castTag(.@"struct") orelse ty_node.castTag(.@"union")) |container| { + for (container.data.fields) |field| { + if (mem.eql(u8, field.name, field_access.data.field_name)) { + return t.getContainer(field.type); + } + } + } + } + }, + + else => {}, + } + return null; +} + +fn getContainerTypeOf(t: *Translator, ref: ZigNode) ?ZigNode { + if (ref.castTag(.identifier)) |ident| { + if (t.global_scope.sym_table.get(ident.data)) |value| { + if (value.castTag(.var_decl)) |var_decl| { + return t.getContainer(var_decl.data.type); + } + } + } else if (ref.castTag(.field_access)) |field_access| { + if (t.getContainerTypeOf(field_access.data.lhs)) |ty_node| { + if (ty_node.castTag(.@"struct") orelse ty_node.castTag(.@"union")) |container| { + for (container.data.fields) |field| { + if (mem.eql(u8, field.name, field_access.data.field_name)) { + return t.getContainer(field.type); + } + } + } else return ty_node; + } + } + return null; +} + +pub fn getFnProto(t: *Translator, ref: ZigNode) ?*ast.Payload.Func { + const init = if (ref.castTag(.var_decl)) |v| + v.data.init orelse return null + else if (ref.castTag(.var_simple) orelse ref.castTag(.pub_var_simple)) |v| + v.data.init + else + return null; + if (t.getContainerTypeOf(init)) |ty_node| { + if (ty_node.castTag(.optional_type)) |prefix| { + if (prefix.data.castTag(.single_pointer)) |sp| { + if (sp.data.elem_type.castTag(.func)) |fn_proto| { + return fn_proto; + } + } + } + } + return null; +} diff --git a/lib/compiler/translate-c/ast.zig b/lib/compiler/translate-c/ast.zig new file mode 100644 index 0000000000000000000000000000000000000000..264a23906f5d2a2cd2814e9a945e369dd954c0c5 --- /dev/null +++ b/lib/compiler/translate-c/ast.zig @@ -0,0 +1,3063 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; + +pub const Node = extern union { + /// If the tag value is less than Tag.no_payload_count, then no pointer + /// dereference is needed. + tag_if_small_enough: usize, + ptr_otherwise: *Payload, + + pub const Tag = enum { + /// Declarations add themselves to the correct scopes and should not be emitted as this tag. + declaration, + null_literal, + undefined_literal, + /// opaque {} + opaque_literal, + true_literal, + false_literal, + empty_block, + return_void, + zero_literal, + one_literal, + @"unreachable", + void_type, + noreturn_type, + @"anytype", + @"continue", + @"break", + // After this, the tag requires a payload. + + integer_literal, + float_literal, + string_literal, + char_literal, + enum_literal, + /// "string"[0..end] + string_slice, + identifier, + @"if", + /// if (!operand) break; + if_not_break, + @"while", + /// while (true) operand + while_true, + @"switch", + /// else => operand, + switch_else, + /// items => body, + switch_prong, + break_val, + @"return", + field_access, + array_access, + call, + var_decl, + /// const name = struct { init } + wrapped_local, + /// var name = init.* + mut_str, + func, + warning, + @"struct", + @"union", + @"opaque", + @"comptime", + @"defer", + array_init, + tuple, + container_init, + container_init_dot, + /// _ = operand; + discard, + + // a + b + add, + // a = b + add_assign, + // c = (a = b) + add_wrap, + add_wrap_assign, + sub, + sub_assign, + sub_wrap, + sub_wrap_assign, + mul, + mul_assign, + mul_wrap, + mul_wrap_assign, + div, + div_assign, + shl, + shl_assign, + shr, + shr_assign, + mod, + mod_assign, + @"and", + @"or", + less_than, + less_than_equal, + greater_than, + greater_than_equal, + equal, + not_equal, + bit_and, + bit_and_assign, + bit_or, + bit_or_assign, + bit_xor, + bit_xor_assign, + array_cat, + ellipsis3, + assign, + + /// @intCast(operand) + int_cast, + /// @constCast(operand) + const_cast, + /// @volatileCast(operand) + volatile_cast, + /// @divTrunc(lhs, rhs) + div_trunc, + /// @intFromBool(operand) + int_from_bool, + /// @as(lhs, rhs) + as, + /// @truncate(operand) + truncate, + /// @bitCast(operand) + bit_cast, + /// @floatCast(operand) + float_cast, + /// @intFromFloat(operand) + int_from_float, + /// @floatFromInt(operand) + float_from_int, + /// @ptrFromInt(operand) + ptr_from_int, + /// @intFromPtr(operand) + int_from_ptr, + /// @alignCast(operand) + align_cast, + /// @ptrCast(operand) + ptr_cast, + /// @divExact(lhs, rhs) + div_exact, + /// @offsetOf(lhs, rhs) + offset_of, + /// @splat(operand) + vector_zero_init, + /// @shuffle(type, a, b, mask) + shuffle, + /// @extern(ty, .{ .name = n }) + builtin_extern, + + /// @byteSwap(operand) + byte_swap, + /// @ceil(operand) + ceil, + /// @cos(operand) + cos, + /// @sin(operand) + sin, + /// @exp(operand) + exp, + /// @exp2(operand) + exp2, + /// @exp10(operand) + exp10, + /// @abs(operand) + abs, + /// @log(operand) + log, + /// @log2(operand) + log2, + /// @log10(operand) + log10, + /// @round(operand) + round, + /// @sqrt(operand) + sqrt, + /// @trunc(operand) + trunc, + /// @floor(operand) + floor, + + /// __helpers.(argshelper_call) + helper_call, + /// __helpers. + helper_ref, + + asm_simple, + + negate, + negate_wrap, + bit_not, + not, + address_of, + /// .? + unwrap, + /// .* + deref, + + block, + /// { operand } + block_single, + + sizeof, + alignof, + typeof, + typeinfo, + type, + + optional_type, + c_pointer, + single_pointer, + array_type, + null_sentinel_array_type, + + /// @Vector(lhs, rhs) + vector, + /// @import("std").mem.zeroes(operand) + std_mem_zeroes, + /// @import("std").mem.zeroInit(lhs, rhs) + std_mem_zeroinit, + // pub const name = @compileError(msg); + fail_decl, + // var actual = mangled; + arg_redecl, + /// pub const alias = actual; + alias, + /// const name = init; + var_simple, + /// pub const name = init; + pub_var_simple, + /// pub? const name (: type)? = value + enum_constant, + + /// pub inline fn name(params) return_type body + pub_inline_fn, + + /// array_type{} + empty_array, + /// [1]type{val} ** count + array_filler, + + /// comptime { if (!(lhs)) @compileError(rhs); } + static_assert, + + pub const last_no_payload_tag = Tag.@"break"; + pub const no_payload_count = @intFromEnum(last_no_payload_tag) + 1; + + pub fn Type(comptime t: Tag) type { + return switch (t) { + .declaration, + .null_literal, + .undefined_literal, + .opaque_literal, + .true_literal, + .false_literal, + .empty_block, + .return_void, + .zero_literal, + .one_literal, + .void_type, + .noreturn_type, + .@"anytype", + .@"continue", + .@"break", + .@"unreachable", + => @compileError("Type Tag " ++ @tagName(t) ++ " has no payload"), + + .std_mem_zeroes, + .@"return", + .@"comptime", + .@"defer", + .asm_simple, + .negate, + .negate_wrap, + .bit_not, + .not, + .optional_type, + .address_of, + .unwrap, + .deref, + .int_from_ptr, + .empty_array, + .while_true, + .if_not_break, + .switch_else, + .block_single, + .int_from_bool, + .sizeof, + .alignof, + .typeof, + .typeinfo, + .align_cast, + .truncate, + .bit_cast, + .float_cast, + .int_from_float, + .float_from_int, + .ptr_from_int, + .ptr_cast, + .int_cast, + .const_cast, + .volatile_cast, + .vector_zero_init, + .byte_swap, + .ceil, + .cos, + .sin, + .exp, + .exp2, + .exp10, + .abs, + .log, + .log2, + .log10, + .round, + .sqrt, + .trunc, + .floor, + => Payload.UnOp, + + .add, + .add_assign, + .add_wrap, + .add_wrap_assign, + .sub, + .sub_assign, + .sub_wrap, + .sub_wrap_assign, + .mul, + .mul_assign, + .mul_wrap, + .mul_wrap_assign, + .div, + .div_assign, + .shl, + .shl_assign, + .shr, + .shr_assign, + .mod, + .mod_assign, + .@"and", + .@"or", + .less_than, + .less_than_equal, + .greater_than, + .greater_than_equal, + .equal, + .not_equal, + .bit_and, + .bit_and_assign, + .bit_or, + .bit_or_assign, + .bit_xor, + .bit_xor_assign, + .div_trunc, + .as, + .array_cat, + .ellipsis3, + .assign, + .array_access, + .std_mem_zeroinit, + .vector, + .div_exact, + .offset_of, + .static_assert, + => Payload.BinOp, + + .integer_literal, + .float_literal, + .string_literal, + .char_literal, + .enum_literal, + .identifier, + .warning, + .type, + => Payload.Value, + .discard => Payload.Discard, + .@"if" => Payload.If, + .@"while" => Payload.While, + .@"switch", .array_init, .switch_prong => Payload.Switch, + .break_val => Payload.BreakVal, + .call => Payload.Call, + .var_decl => Payload.VarDecl, + .func => Payload.Func, + .@"struct", .@"union", .@"opaque" => Payload.Container, + .tuple => Payload.TupleInit, + .container_init => Payload.ContainerInit, + .container_init_dot => Payload.ContainerInitDot, + .block => Payload.Block, + .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, .wrapped_local, .mut_str => Payload.SimpleVarDecl, + .enum_constant => Payload.EnumConstant, + .array_filler => Payload.ArrayFiller, + .pub_inline_fn => Payload.PubInlineFn, + .field_access => Payload.FieldAccess, + .string_slice => Payload.StringSlice, + .shuffle => Payload.Shuffle, + .builtin_extern => Payload.Extern, + .helper_call => Payload.HelperCall, + .helper_ref => Payload.HelperRef, + }; + } + + pub fn init(comptime t: Tag) Node { + comptime std.debug.assert(@intFromEnum(t) < Tag.no_payload_count); + return .{ .tag_if_small_enough = @intFromEnum(t) }; + } + + pub fn create(comptime t: Tag, ally: Allocator, data: Data(t)) error{OutOfMemory}!Node { + const ptr = try ally.create(t.Type()); + ptr.* = .{ + .base = .{ .tag = t }, + .data = data, + }; + return Node{ .ptr_otherwise = &ptr.base }; + } + + pub fn Data(comptime t: Tag) type { + return std.meta.fieldInfo(t.Type(), .data).type; + } + }; + + pub fn tag(self: Node) Tag { + if (self.tag_if_small_enough < Tag.no_payload_count) { + return @enumFromInt(@as(std.meta.Tag(Tag), @intCast(self.tag_if_small_enough))); + } else { + return self.ptr_otherwise.tag; + } + } + + pub fn castTag(self: Node, comptime t: Tag) ?*t.Type() { + if (self.tag_if_small_enough < Tag.no_payload_count) + return null; + + if (self.ptr_otherwise.tag == t) + return @alignCast(@fieldParentPtr("base", self.ptr_otherwise)); + + return null; + } + + pub fn initPayload(payload: *Payload) Node { + std.debug.assert(@intFromEnum(payload.tag) >= Tag.no_payload_count); + return .{ .ptr_otherwise = payload }; + } + + pub fn isNoreturn(node: Node, break_counts: bool) bool { + switch (node.tag()) { + .block => { + const block_node = node.castTag(.block).?; + if (block_node.data.stmts.len == 0) return false; + + const last = block_node.data.stmts[block_node.data.stmts.len - 1]; + return last.isNoreturn(break_counts); + }, + .@"switch" => { + const switch_node = node.castTag(.@"switch").?; + + for (switch_node.data.cases) |case| { + const body = if (case.castTag(.switch_else)) |some| + some.data + else if (case.castTag(.switch_prong)) |some| + some.data.cond + else + unreachable; + + if (!body.isNoreturn(break_counts)) return false; + } + return true; + }, + .@"return", .return_void => return true, + .@"break" => if (break_counts) return true, + else => {}, + } + return false; + } + + pub fn isBoolRes(res: Node) bool { + switch (res.tag()) { + .@"or", + .@"and", + .equal, + .not_equal, + .less_than, + .less_than_equal, + .greater_than, + .greater_than_equal, + .not, + .false_literal, + .true_literal, + => return true, + else => return false, + } + } +}; + +pub const Payload = struct { + tag: Node.Tag, + + pub const Value = struct { + base: Payload, + data: []const u8, + }; + + pub const UnOp = struct { + base: Payload, + data: Node, + }; + + pub const BinOp = struct { + base: Payload, + data: struct { + lhs: Node, + rhs: Node, + }, + }; + + pub const Discard = struct { + base: Payload, + data: struct { + should_skip: bool, + value: Node, + }, + }; + + pub const If = struct { + base: Payload, + data: struct { + cond: Node, + then: Node, + @"else": ?Node, + }, + }; + + pub const While = struct { + base: Payload, + data: struct { + cond: Node, + body: Node, + cont_expr: ?Node, + }, + }; + + pub const Switch = struct { + base: Payload, + data: struct { + cond: Node, + cases: []Node, + }, + }; + + pub const BreakVal = struct { + base: Payload, + data: struct { + label: ?[]const u8, + val: Node, + }, + }; + + pub const Call = struct { + base: Payload, + data: struct { + lhs: Node, + args: []Node, + }, + }; + + pub const VarDecl = struct { + base: Payload, + data: struct { + is_pub: bool, + is_const: bool, + is_extern: bool, + is_export: bool, + is_threadlocal: bool, + alignment: ?c_uint, + linksection_string: ?[]const u8, + name: []const u8, + type: Node, + init: ?Node, + }, + }; + + pub const Func = struct { + base: Payload, + data: struct { + is_pub: bool, + is_extern: bool, + is_export: bool, + is_inline: bool, + is_var_args: bool, + name: ?[]const u8, + linksection_string: ?[]const u8, + explicit_callconv: ?CallingConvention, + params: []Param, + return_type: Node, + body: ?Node, + alignment: ?c_uint, + }, + + pub const CallingConvention = enum { + c, + x86_64_sysv, + x86_64_win, + x86_stdcall, + x86_fastcall, + x86_thiscall, + x86_vectorcall, + x86_regcall, + aarch64_vfabi, + aarch64_sve_pcs, + arm_aapcs, + arm_aapcs_vfp, + m68k_rtd, + riscv_vector, + }; + }; + + pub const Param = struct { + is_noalias: bool, + name: ?[]const u8, + type: Node, + }; + + pub const Container = struct { + base: Payload, + data: struct { + layout: enum { @"packed", @"extern", none }, + fields: []Field, + decls: []Node, + }, + + pub const Field = struct { + name: []const u8, + type: Node, + alignment: ?c_uint, + default_value: ?Node, + }; + }; + + pub const TupleInit = struct { + base: Payload, + data: []Node, + }; + + pub const ContainerInit = struct { + base: Payload, + data: struct { + lhs: Node, + inits: []Initializer, + }, + + pub const Initializer = struct { + name: []const u8, + value: Node, + }; + }; + + pub const ContainerInitDot = struct { + base: Payload, + data: []Initializer, + + pub const Initializer = struct { + name: []const u8, + value: Node, + }; + }; + + pub const Block = struct { + base: Payload, + data: struct { + label: ?[]const u8, + stmts: []Node, + }, + }; + + pub const Array = struct { + base: Payload, + data: ArrayTypeInfo, + + pub const ArrayTypeInfo = struct { + elem_type: Node, + len: u64, + }; + }; + + pub const Pointer = struct { + base: Payload, + data: struct { + elem_type: Node, + is_const: bool, + is_volatile: bool, + is_allowzero: bool, + }, + }; + + pub const ArgRedecl = struct { + base: Payload, + data: struct { + actual: []const u8, + mangled: []const u8, + }, + }; + + pub const SimpleVarDecl = struct { + base: Payload, + data: struct { + name: []const u8, + init: Node, + }, + }; + + pub const EnumConstant = struct { + base: Payload, + data: struct { + name: []const u8, + is_public: bool, + type: ?Node, + value: Node, + }, + }; + + pub const ArrayFiller = struct { + base: Payload, + data: struct { + type: Node, + filler: Node, + count: u64, + }, + }; + + pub const PubInlineFn = struct { + base: Payload, + data: struct { + name: []const u8, + params: []Param, + return_type: Node, + body: Node, + }, + }; + + pub const FieldAccess = struct { + base: Payload, + data: struct { + lhs: Node, + field_name: []const u8, + }, + }; + + pub const StringSlice = struct { + base: Payload, + data: struct { + string: Node, + end: u64, + }, + }; + + pub const Shuffle = struct { + base: Payload, + data: struct { + element_type: Node, + a: Node, + b: Node, + mask_vector: Node, + }, + }; + + pub const Extern = struct { + base: Payload, + data: struct { + type: Node, + name: Node, + }, + }; + + pub const HelperCall = struct { + base: Payload, + data: struct { + name: []const u8, + args: []const Node, + }, + }; + + pub const HelperRef = struct { + base: Payload, + data: []const u8, + }; +}; + +/// Converts the nodes into a Zig Ast. +/// Caller must free the source slice. +pub fn render(gpa: Allocator, nodes: []const Node) !std.zig.Ast { + var ctx: Context = .{ + .gpa = gpa, + .buf = std.array_list.Managed(u8).init(gpa), + }; + defer ctx.buf.deinit(); + defer ctx.nodes.deinit(gpa); + defer ctx.extra_data.deinit(gpa); + defer ctx.tokens.deinit(gpa); + + // Estimate that each top level node has 10 child nodes. + const estimated_node_count = nodes.len * 10 + 1; // +1 for the .root node + try ctx.nodes.ensureTotalCapacity(gpa, estimated_node_count); + // Estimate that each each node has 2 tokens. + const estimated_tokens_count = estimated_node_count * 2; + try ctx.tokens.ensureTotalCapacity(gpa, estimated_tokens_count); + // Estimate that each each token is 3 bytes long. + const estimated_buf_len = estimated_tokens_count * 3; + try ctx.buf.ensureTotalCapacity(estimated_buf_len); + + ctx.nodes.appendAssumeCapacity(.{ + .tag = .root, + .main_token = 0, + .data = undefined, + }); + + const root_members = blk: { + var result = std.array_list.Managed(NodeIndex).init(gpa); + defer result.deinit(); + + for (nodes) |node| { + const res = (try renderNodeOpt(&ctx, node)) orelse continue; + try result.append(res); + } + break :blk try ctx.listToSpan(result.items); + }; + + ctx.nodes.items(.data)[0] = .{ .extra_range = .{ + .start = root_members.start, + .end = root_members.end, + } }; + + try ctx.tokens.append(gpa, .{ + .tag = .eof, + .start = @as(u32, @intCast(ctx.buf.items.len)), + }); + + return .{ + .source = try ctx.buf.toOwnedSliceSentinel(0), + .tokens = ctx.tokens.toOwnedSlice(), + .nodes = ctx.nodes.toOwnedSlice(), + .extra_data = try ctx.extra_data.toOwnedSlice(gpa), + .errors = &.{}, + .mode = .zig, + }; +} + +const NodeIndex = std.zig.Ast.Node.Index; +const NodeSubRange = std.zig.Ast.Node.SubRange; +const TokenIndex = std.zig.Ast.TokenIndex; +const TokenTag = std.zig.Token.Tag; + +const Context = struct { + gpa: Allocator, + buf: std.array_list.Managed(u8), + nodes: std.zig.Ast.NodeList = .{}, + extra_data: std.ArrayListUnmanaged(u32) = .empty, + tokens: std.zig.Ast.TokenList = .{}, + + fn addTokenFmt(c: *Context, tag: TokenTag, comptime format: []const u8, args: anytype) Allocator.Error!TokenIndex { + const start_index = c.buf.items.len; + try c.buf.print(format ++ " ", args); + + try c.tokens.append(c.gpa, .{ + .tag = tag, + .start = @intCast(start_index), + }); + + return @intCast(c.tokens.len - 1); + } + + fn addToken(c: *Context, tag: TokenTag, bytes: []const u8) Allocator.Error!TokenIndex { + return c.addTokenFmt(tag, "{s}", .{bytes}); + } + + fn addIdentifier(c: *Context, bytes: []const u8) Allocator.Error!TokenIndex { + if (std.zig.primitives.isPrimitive(bytes)) + return c.addTokenFmt(.identifier, "@\"{s}\"", .{bytes}); + return c.addTokenFmt(.identifier, "{f}", .{std.zig.fmtId(bytes)}); + } + + fn listToSpan(c: *Context, list: []const NodeIndex) Allocator.Error!NodeSubRange { + try c.extra_data.appendSlice(c.gpa, @ptrCast(list)); + return .{ + .start = @enumFromInt(c.extra_data.items.len - list.len), + .end = @enumFromInt(c.extra_data.items.len), + }; + } + + fn addNode(c: *Context, elem: std.zig.Ast.Node) Allocator.Error!NodeIndex { + const result: NodeIndex = @enumFromInt(c.nodes.len); + try c.nodes.append(c.gpa, elem); + return result; + } + + fn addExtra(c: *Context, extra: anytype) Allocator.Error!std.zig.Ast.ExtraIndex { + const fields = std.meta.fields(@TypeOf(extra)); + try c.extra_data.ensureUnusedCapacity(c.gpa, fields.len); + const result: std.zig.Ast.ExtraIndex = @enumFromInt(c.extra_data.items.len); + inline for (fields) |field| { + const data: u32 = switch (field.type) { + NodeIndex, + std.zig.Ast.Node.OptionalIndex, + std.zig.Ast.OptionalTokenIndex, + std.zig.Ast.ExtraIndex, + => @intFromEnum(@field(extra, field.name)), + TokenIndex, + => @field(extra, field.name), + else => @compileError("unexpected field type"), + }; + c.extra_data.appendAssumeCapacity(data); + } + return result; + } +}; + +fn renderNodeOpt(c: *Context, node: Node) Allocator.Error!?NodeIndex { + switch (node.tag()) { + .warning => { + const payload = node.castTag(.warning).?.data; + try c.buf.appendSlice(payload); + try c.buf.append('\n'); + return null; + }, + .discard => { + const payload = node.castTag(.discard).?.data; + if (payload.should_skip) return null; + + return try renderNode(c, node); + }, + else => return try renderNode(c, node), + } +} + +fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex { + switch (node.tag()) { + .declaration => unreachable, + .warning => unreachable, + .discard => { + const payload = node.castTag(.discard).?.data; + std.debug.assert(!payload.should_skip); + + const lhs = try c.addNode(.{ + .tag = .identifier, + .main_token = try c.addToken(.identifier, "_"), + .data = undefined, + }); + const main_token = try c.addToken(.equal, "="); + if (payload.value.tag() == .identifier) { + // Render as `_ = &foo;` to avoid tripping "pointless discard" and "local variable never mutated" errors. + var addr_of_pl: Payload.UnOp = .{ + .base = .{ .tag = .address_of }, + .data = payload.value, + }; + const addr_of: Node = .{ .ptr_otherwise = &addr_of_pl.base }; + return try c.addNode(.{ + .tag = .assign, + .main_token = main_token, + .data = .{ .node_and_node = .{ + lhs, try renderNode(c, addr_of), + } }, + }); + } else { + return try c.addNode(.{ + .tag = .assign, + .main_token = main_token, + .data = .{ .node_and_node = .{ + lhs, try renderNode(c, payload.value), + } }, + }); + } + }, + .std_mem_zeroes => { + const payload = node.castTag(.std_mem_zeroes).?.data; + const import_node = try renderStdImport(c, &.{ "mem", "zeroes" }); + return renderCall(c, import_node, &.{payload}); + }, + .std_mem_zeroinit => { + const payload = node.castTag(.std_mem_zeroinit).?.data; + const import_node = try renderStdImport(c, &.{ "mem", "zeroInit" }); + return renderCall(c, import_node, &.{ payload.lhs, payload.rhs }); + }, + .vector => { + const payload = node.castTag(.vector).?.data; + return renderBuiltinCall(c, "@Vector", &.{ payload.lhs, payload.rhs }); + }, + .call => { + const payload = node.castTag(.call).?.data; + const lhs = try renderNodeGrouped(c, payload.lhs); + return renderCall(c, lhs, payload.args); + }, + .null_literal => return c.addNode(.{ + .tag = .identifier, + .main_token = try c.addToken(.identifier, "null"), + .data = undefined, + }), + .undefined_literal => return c.addNode(.{ + .tag = .identifier, + .main_token = try c.addToken(.identifier, "undefined"), + .data = undefined, + }), + .true_literal => return c.addNode(.{ + .tag = .identifier, + .main_token = try c.addToken(.identifier, "true"), + .data = undefined, + }), + .false_literal => return c.addNode(.{ + .tag = .identifier, + .main_token = try c.addToken(.identifier, "false"), + .data = undefined, + }), + .zero_literal => return c.addNode(.{ + .tag = .number_literal, + .main_token = try c.addToken(.number_literal, "0"), + .data = undefined, + }), + .one_literal => return c.addNode(.{ + .tag = .number_literal, + .main_token = try c.addToken(.number_literal, "1"), + .data = undefined, + }), + .@"unreachable" => return c.addNode(.{ + .tag = .unreachable_literal, + .main_token = try c.addToken(.keyword_unreachable, "unreachable"), + .data = undefined, + }), + .void_type => return c.addNode(.{ + .tag = .identifier, + .main_token = try c.addToken(.identifier, "void"), + .data = undefined, + }), + .noreturn_type => return c.addNode(.{ + .tag = .identifier, + .main_token = try c.addToken(.identifier, "noreturn"), + .data = undefined, + }), + .@"continue" => return c.addNode(.{ + .tag = .@"continue", + .main_token = try c.addToken(.keyword_continue, "continue"), + .data = .{ .opt_token_and_opt_node = .{ + .none, .none, + } }, + }), + .return_void => return c.addNode(.{ + .tag = .@"return", + .main_token = try c.addToken(.keyword_return, "return"), + .data = .{ .opt_node = .none }, + }), + .@"break" => return c.addNode(.{ + .tag = .@"break", + .main_token = try c.addToken(.keyword_break, "break"), + .data = .{ .opt_token_and_opt_node = .{ + .none, .none, + } }, + }), + .break_val => { + const payload = node.castTag(.break_val).?.data; + const tok = try c.addToken(.keyword_break, "break"); + const break_label = if (payload.label) |some| blk: { + _ = try c.addToken(.colon, ":"); + break :blk try c.addIdentifier(some); + } else 0; + return c.addNode(.{ + .tag = .@"break", + .main_token = tok, + .data = .{ .opt_token_and_opt_node = .{ + .fromToken(break_label), (try renderNode(c, payload.val)).toOptional(), + } }, + }); + }, + .@"return" => { + const payload = node.castTag(.@"return").?.data; + return c.addNode(.{ + .tag = .@"return", + .main_token = try c.addToken(.keyword_return, "return"), + .data = .{ .opt_node = (try renderNode(c, payload)).toOptional() }, + }); + }, + .@"comptime" => { + const payload = node.castTag(.@"comptime").?.data; + return c.addNode(.{ + .tag = .@"comptime", + .main_token = try c.addToken(.keyword_comptime, "comptime"), + .data = .{ + .node = try renderNode(c, payload), + }, + }); + }, + .@"defer" => { + const payload = node.castTag(.@"defer").?.data; + return c.addNode(.{ + .tag = .@"defer", + .main_token = try c.addToken(.keyword_defer, "defer"), + .data = .{ + .node = try renderNode(c, payload), + }, + }); + }, + .asm_simple => { + const payload = node.castTag(.asm_simple).?.data; + const asm_token = try c.addToken(.keyword_asm, "asm"); + _ = try c.addToken(.l_paren, "("); + return c.addNode(.{ + .tag = .asm_simple, + .main_token = asm_token, + .data = .{ .node_and_token = .{ + try renderNode(c, payload), + try c.addToken(.r_paren, ")"), + } }, + }); + }, + .type => { + const payload = node.castTag(.type).?.data; + return c.addNode(.{ + .tag = .identifier, + .main_token = try c.addToken(.identifier, payload), + .data = undefined, + }); + }, + .identifier => { + const payload = node.castTag(.identifier).?.data; + return c.addNode(.{ + .tag = .identifier, + .main_token = try c.addIdentifier(payload), + .data = undefined, + }); + }, + .float_literal => { + const payload = node.castTag(.float_literal).?.data; + return c.addNode(.{ + .tag = .number_literal, + .main_token = try c.addToken(.number_literal, payload), + .data = undefined, + }); + }, + .integer_literal => { + const payload = node.castTag(.integer_literal).?.data; + return c.addNode(.{ + .tag = .number_literal, + .main_token = try c.addToken(.number_literal, payload), + .data = undefined, + }); + }, + .string_literal => { + const payload = node.castTag(.string_literal).?.data; + return c.addNode(.{ + .tag = .string_literal, + .main_token = try c.addToken(.string_literal, payload), + .data = undefined, + }); + }, + .char_literal => { + const payload = node.castTag(.char_literal).?.data; + return c.addNode(.{ + .tag = .char_literal, + .main_token = try c.addToken(.char_literal, payload), + .data = undefined, + }); + }, + .enum_literal => { + const payload = node.castTag(.enum_literal).?.data; + _ = try c.addToken(.period, "."); + return c.addNode(.{ + .tag = .enum_literal, + .main_token = try c.addToken(.identifier, payload), + .data = undefined, + }); + }, + .string_slice => { + const payload = node.castTag(.string_slice).?.data; + + const string = try renderNode(c, payload.string); + const l_bracket = try c.addToken(.l_bracket, "["); + const start = try c.addNode(.{ + .tag = .number_literal, + .main_token = try c.addToken(.number_literal, "0"), + .data = undefined, + }); + _ = try c.addToken(.ellipsis2, ".."); + const end = try c.addNode(.{ + .tag = .number_literal, + .main_token = try c.addTokenFmt(.number_literal, "{d}", .{payload.end}), + .data = undefined, + }); + _ = try c.addToken(.r_bracket, "]"); + + return c.addNode(.{ + .tag = .slice, + .main_token = l_bracket, + .data = .{ .node_and_extra = .{ + string, try c.addExtra(std.zig.Ast.Node.Slice{ + .start = start, + .end = end, + }), + } }, + }); + }, + .fail_decl => { + const payload = node.castTag(.fail_decl).?.data; + // pub const name = @compileError(msg); + _ = try c.addToken(.keyword_pub, "pub"); + const const_tok = try c.addToken(.keyword_const, "const"); + _ = try c.addIdentifier(payload.actual); + _ = try c.addToken(.equal, "="); + + const compile_error_tok = try c.addToken(.builtin, "@compileError"); + _ = try c.addToken(.l_paren, "("); + const err_msg_tok = try c.addTokenFmt(.string_literal, "\"{f}\"", .{std.zig.fmtString(payload.mangled)}); + const err_msg = try c.addNode(.{ + .tag = .string_literal, + .main_token = err_msg_tok, + .data = undefined, + }); + _ = try c.addToken(.r_paren, ")"); + const compile_error = try c.addNode(.{ + .tag = .builtin_call_two, + .main_token = compile_error_tok, + .data = .{ .opt_node_and_opt_node = .{ + err_msg.toOptional(), .none, + } }, + }); + _ = try c.addToken(.semicolon, ";"); + + return c.addNode(.{ + .tag = .simple_var_decl, + .main_token = const_tok, + .data = .{ + .opt_node_and_opt_node = .{ + .none, // Type expression + compile_error.toOptional(), // Init expression + }, + }, + }); + }, + .pub_var_simple, .var_simple => { + const payload = @as(*Payload.SimpleVarDecl, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data; + if (node.tag() == .pub_var_simple) _ = try c.addToken(.keyword_pub, "pub"); + const const_tok = try c.addToken(.keyword_const, "const"); + _ = try c.addIdentifier(payload.name); + _ = try c.addToken(.equal, "="); + + const init = try renderNode(c, payload.init); + _ = try c.addToken(.semicolon, ";"); + + return c.addNode(.{ + .tag = .simple_var_decl, + .main_token = const_tok, + .data = .{ + .opt_node_and_opt_node = .{ + .none, // Type expression + init.toOptional(), // Init expression + }, + }, + }); + }, + .wrapped_local => { + const payload = node.castTag(.wrapped_local).?.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 = .{ .opt_node_and_opt_node = .{ + (try renderNode(c, payload.init)).toOptional(), .none, + } }, + }); + _ = try c.addToken(.r_brace, "}"); + _ = try c.addToken(.semicolon, ";"); + + return c.addNode(.{ + .tag = .simple_var_decl, + .main_token = const_tok, + .data = .{ + .opt_node_and_opt_node = .{ + .none, // Type expression + container_def.toOptional(), // Init expression + }, + }, + }); + }, + .mut_str => { + const payload = node.castTag(.mut_str).?.data; + + const var_tok = try c.addToken(.keyword_var, "var"); + _ = try c.addIdentifier(payload.name); + _ = try c.addToken(.equal, "="); + + const deref = try c.addNode(.{ + .tag = .deref, + .data = .{ + .node = try renderNodeGrouped(c, payload.init), + }, + .main_token = try c.addToken(.period_asterisk, ".*"), + }); + _ = try c.addToken(.semicolon, ";"); + + return c.addNode(.{ + .tag = .simple_var_decl, + .main_token = var_tok, + .data = .{ + .opt_node_and_opt_node = .{ + .none, // Type expression + deref.toOptional(), // Init expression + }, + }, + }); + }, + .var_decl => return renderVar(c, node), + .arg_redecl, .alias => { + const payload = @as(*Payload.ArgRedecl, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data; + if (node.tag() == .alias) _ = try c.addToken(.keyword_pub, "pub"); + const mut_tok = if (node.tag() == .alias) + try c.addToken(.keyword_const, "const") + else + try c.addToken(.keyword_var, "var"); + _ = try c.addIdentifier(payload.actual); + _ = try c.addToken(.equal, "="); + + const init = try c.addNode(.{ + .tag = .identifier, + .main_token = try c.addIdentifier(payload.mangled), + .data = undefined, + }); + _ = try c.addToken(.semicolon, ";"); + + return c.addNode(.{ + .tag = .simple_var_decl, + .main_token = mut_tok, + .data = .{ + .opt_node_and_opt_node = .{ + .none, // Type expression + init.toOptional(), // Init expression + }, + }, + }); + }, + .int_cast => { + const payload = node.castTag(.int_cast).?.data; + return renderBuiltinCall(c, "@intCast", &.{payload}); + }, + .const_cast => { + const payload = node.castTag(.const_cast).?.data; + return renderBuiltinCall(c, "@constCast", &.{payload}); + }, + .volatile_cast => { + const payload = node.castTag(.volatile_cast).?.data; + return renderBuiltinCall(c, "@volatileCast", &.{payload}); + }, + .div_trunc => { + const payload = node.castTag(.div_trunc).?.data; + return renderBuiltinCall(c, "@divTrunc", &.{ payload.lhs, payload.rhs }); + }, + .int_from_bool => { + const payload = node.castTag(.int_from_bool).?.data; + return renderBuiltinCall(c, "@intFromBool", &.{payload}); + }, + .as => { + const payload = node.castTag(.as).?.data; + return renderBuiltinCall(c, "@as", &.{ payload.lhs, payload.rhs }); + }, + .truncate => { + const payload = node.castTag(.truncate).?.data; + return renderBuiltinCall(c, "@truncate", &.{payload}); + }, + .bit_cast => { + const payload = node.castTag(.bit_cast).?.data; + return renderBuiltinCall(c, "@bitCast", &.{payload}); + }, + .float_cast => { + const payload = node.castTag(.float_cast).?.data; + return renderBuiltinCall(c, "@floatCast", &.{payload}); + }, + .int_from_float => { + const payload = node.castTag(.int_from_float).?.data; + return renderBuiltinCall(c, "@intFromFloat", &.{payload}); + }, + .float_from_int => { + const payload = node.castTag(.float_from_int).?.data; + return renderBuiltinCall(c, "@floatFromInt", &.{payload}); + }, + .ptr_from_int => { + const payload = node.castTag(.ptr_from_int).?.data; + return renderBuiltinCall(c, "@ptrFromInt", &.{payload}); + }, + .int_from_ptr => { + const payload = node.castTag(.int_from_ptr).?.data; + return renderBuiltinCall(c, "@intFromPtr", &.{payload}); + }, + .align_cast => { + const payload = node.castTag(.align_cast).?.data; + return renderBuiltinCall(c, "@alignCast", &.{payload}); + }, + .ptr_cast => { + const payload = node.castTag(.ptr_cast).?.data; + return renderBuiltinCall(c, "@ptrCast", &.{payload}); + }, + .div_exact => { + const payload = node.castTag(.div_exact).?.data; + return renderBuiltinCall(c, "@divExact", &.{ payload.lhs, payload.rhs }); + }, + .offset_of => { + const payload = node.castTag(.offset_of).?.data; + return renderBuiltinCall(c, "@offsetOf", &.{ payload.lhs, payload.rhs }); + }, + .sizeof => { + const payload = node.castTag(.sizeof).?.data; + return renderBuiltinCall(c, "@sizeOf", &.{payload}); + }, + .shuffle => { + const payload = node.castTag(.shuffle).?.data; + return renderBuiltinCall(c, "@shuffle", &.{ + payload.element_type, + payload.a, + payload.b, + payload.mask_vector, + }); + }, + .builtin_extern => { + const payload = node.castTag(.builtin_extern).?.data; + + var info_inits: [1]Payload.ContainerInitDot.Initializer = .{ + .{ .name = "name", .value = payload.name }, + }; + var info_payload: Payload.ContainerInitDot = .{ + .base = .{ .tag = .container_init_dot }, + .data = &info_inits, + }; + + return renderBuiltinCall(c, "@extern", &.{ + payload.type, + .{ .ptr_otherwise = &info_payload.base }, + }); + }, + .helper_call => { + const payload = node.castTag(.helper_call).?.data; + const helpers_tok = try c.addNode(.{ + .tag = .identifier, + .main_token = try c.addIdentifier("__helpers"), + .data = undefined, + }); + const func = try renderFieldAccess(c, helpers_tok, payload.name); + return renderCall(c, func, payload.args); + }, + .helper_ref => { + const payload = node.castTag(.helper_ref).?.data; + const helpers_tok = try c.addNode(.{ + .tag = .identifier, + .main_token = try c.addIdentifier("__helpers"), + .data = undefined, + }); + return renderFieldAccess(c, helpers_tok, payload); + }, + .alignof => { + const payload = node.castTag(.alignof).?.data; + return renderBuiltinCall(c, "@alignOf", &.{payload}); + }, + .typeof => { + const payload = node.castTag(.typeof).?.data; + return renderBuiltinCall(c, "@TypeOf", &.{payload}); + }, + .typeinfo => { + const payload = node.castTag(.typeinfo).?.data; + return renderBuiltinCall(c, "@typeInfo", &.{payload}); + }, + .byte_swap => { + const payload = node.castTag(.byte_swap).?.data; + return renderBuiltinCall(c, "@byteSwap", &.{payload}); + }, + .ceil => { + const payload = node.castTag(.ceil).?.data; + return renderBuiltinCall(c, "@ceil", &.{payload}); + }, + .cos => { + const payload = node.castTag(.cos).?.data; + return renderBuiltinCall(c, "@cos", &.{payload}); + }, + .sin => { + const payload = node.castTag(.sin).?.data; + return renderBuiltinCall(c, "@sin", &.{payload}); + }, + .exp => { + const payload = node.castTag(.exp).?.data; + return renderBuiltinCall(c, "@exp", &.{payload}); + }, + .exp2 => { + const payload = node.castTag(.exp2).?.data; + return renderBuiltinCall(c, "@exp2", &.{payload}); + }, + .exp10 => { + const payload = node.castTag(.exp10).?.data; + return renderBuiltinCall(c, "@exp10", &.{payload}); + }, + .abs => { + const payload = node.castTag(.abs).?.data; + return renderBuiltinCall(c, "@abs", &.{payload}); + }, + .log => { + const payload = node.castTag(.log).?.data; + return renderBuiltinCall(c, "@log", &.{payload}); + }, + .log2 => { + const payload = node.castTag(.log2).?.data; + return renderBuiltinCall(c, "@log2", &.{payload}); + }, + .log10 => { + const payload = node.castTag(.log10).?.data; + return renderBuiltinCall(c, "@log10", &.{payload}); + }, + .round => { + const payload = node.castTag(.round).?.data; + return renderBuiltinCall(c, "@round", &.{payload}); + }, + .sqrt => { + const payload = node.castTag(.sqrt).?.data; + return renderBuiltinCall(c, "@sqrt", &.{payload}); + }, + .trunc => { + const payload = node.castTag(.trunc).?.data; + return renderBuiltinCall(c, "@trunc", &.{payload}); + }, + .floor => { + const payload = node.castTag(.floor).?.data; + return renderBuiltinCall(c, "@floor", &.{payload}); + }, + .negate => return renderPrefixOp(c, node, .negation, .minus, "-"), + .negate_wrap => return renderPrefixOp(c, node, .negation_wrap, .minus_percent, "-%"), + .bit_not => return renderPrefixOp(c, node, .bit_not, .tilde, "~"), + .not => return renderPrefixOp(c, node, .bool_not, .bang, "!"), + .optional_type => return renderPrefixOp(c, node, .optional_type, .question_mark, "?"), + .address_of => { + const payload = node.castTag(.address_of).?.data; + + const ampersand = try c.addToken(.ampersand, "&"); + const base = try renderNodeGrouped(c, payload); + return c.addNode(.{ + .tag = .address_of, + .main_token = ampersand, + .data = .{ + .node = base, + }, + }); + }, + .deref => { + const payload = node.castTag(.deref).?.data; + const operand = try renderNodeGrouped(c, payload); + const deref_tok = try c.addToken(.period_asterisk, ".*"); + return c.addNode(.{ + .tag = .deref, + .main_token = deref_tok, + .data = .{ + .node = operand, + }, + }); + }, + .unwrap => { + const payload = node.castTag(.unwrap).?.data; + const operand = try renderNodeGrouped(c, payload); + const period = try c.addToken(.period, "."); + const question_mark = try c.addToken(.question_mark, "?"); + return c.addNode(.{ + .tag = .unwrap_optional, + .main_token = period, + .data = .{ .node_and_token = .{ + operand, question_mark, + } }, + }); + }, + .c_pointer, .single_pointer => { + const payload = @as(*Payload.Pointer, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data; + + const main_token = if (node.tag() == .single_pointer) + try c.addToken(.asterisk, "*") + else blk: { + const res = try c.addToken(.l_bracket, "["); + _ = try c.addToken(.asterisk, "*"); + _ = try c.addIdentifier("c"); + _ = try c.addToken(.r_bracket, "]"); + break :blk res; + }; + if (payload.is_const) _ = try c.addToken(.keyword_const, "const"); + if (payload.is_volatile) _ = try c.addToken(.keyword_volatile, "volatile"); + if (payload.is_allowzero) _ = try c.addToken(.keyword_allowzero, "allowzero"); + const elem_type = try renderNodeGrouped(c, payload.elem_type); + + return c.addNode(.{ + .tag = .ptr_type_aligned, + .main_token = main_token, + .data = .{ + .opt_node_and_node = .{ + .none, // Align node + elem_type, + }, + }, + }); + }, + .add => return renderBinOpGrouped(c, node, .add, .plus, "+"), + .add_assign => return renderBinOp(c, node, .assign_add, .plus_equal, "+="), + .add_wrap => return renderBinOpGrouped(c, node, .add_wrap, .plus_percent, "+%"), + .add_wrap_assign => return renderBinOp(c, node, .assign_add_wrap, .plus_percent_equal, "+%="), + .sub => return renderBinOpGrouped(c, node, .sub, .minus, "-"), + .sub_assign => return renderBinOp(c, node, .assign_sub, .minus_equal, "-="), + .sub_wrap => return renderBinOpGrouped(c, node, .sub_wrap, .minus_percent, "-%"), + .sub_wrap_assign => return renderBinOp(c, node, .assign_sub_wrap, .minus_percent_equal, "-%="), + .mul => return renderBinOpGrouped(c, node, .mul, .asterisk, "*"), + .mul_assign => return renderBinOp(c, node, .assign_mul, .asterisk_equal, "*="), + .mul_wrap => return renderBinOpGrouped(c, node, .mul_wrap, .asterisk_percent, "*%"), + .mul_wrap_assign => return renderBinOp(c, node, .assign_mul_wrap, .asterisk_percent_equal, "*%="), + .div => return renderBinOpGrouped(c, node, .div, .slash, "/"), + .div_assign => return renderBinOp(c, node, .assign_div, .slash_equal, "/="), + .shl => return renderBinOpGrouped(c, node, .shl, .angle_bracket_angle_bracket_left, "<<"), + .shl_assign => return renderBinOp(c, node, .assign_shl, .angle_bracket_angle_bracket_left_equal, "<<="), + .shr => return renderBinOpGrouped(c, node, .shr, .angle_bracket_angle_bracket_right, ">>"), + .shr_assign => return renderBinOp(c, node, .assign_shr, .angle_bracket_angle_bracket_right_equal, ">>="), + .mod => return renderBinOpGrouped(c, node, .mod, .percent, "%"), + .mod_assign => return renderBinOp(c, node, .assign_mod, .percent_equal, "%="), + .@"and" => return renderBinOpGrouped(c, node, .bool_and, .keyword_and, "and"), + .@"or" => return renderBinOpGrouped(c, node, .bool_or, .keyword_or, "or"), + .less_than => return renderBinOpGrouped(c, node, .less_than, .angle_bracket_left, "<"), + .less_than_equal => return renderBinOpGrouped(c, node, .less_or_equal, .angle_bracket_left_equal, "<="), + .greater_than => return renderBinOpGrouped(c, node, .greater_than, .angle_bracket_right, ">="), + .greater_than_equal => return renderBinOpGrouped(c, node, .greater_or_equal, .angle_bracket_right_equal, ">="), + .equal => return renderBinOpGrouped(c, node, .equal_equal, .equal_equal, "=="), + .not_equal => return renderBinOpGrouped(c, node, .bang_equal, .bang_equal, "!="), + .bit_and => return renderBinOpGrouped(c, node, .bit_and, .ampersand, "&"), + .bit_and_assign => return renderBinOp(c, node, .assign_bit_and, .ampersand_equal, "&="), + .bit_or => return renderBinOpGrouped(c, node, .bit_or, .pipe, "|"), + .bit_or_assign => return renderBinOp(c, node, .assign_bit_or, .pipe_equal, "|="), + .bit_xor => return renderBinOpGrouped(c, node, .bit_xor, .caret, "^"), + .bit_xor_assign => return renderBinOp(c, node, .assign_bit_xor, .caret_equal, "^="), + .array_cat => return renderBinOp(c, node, .array_cat, .plus_plus, "++"), + .ellipsis3 => return renderBinOpGrouped(c, node, .switch_range, .ellipsis3, "..."), + .assign => return renderBinOp(c, node, .assign, .equal, "="), + .empty_block => { + const l_brace = try c.addToken(.l_brace, "{"); + _ = try c.addToken(.r_brace, "}"); + return c.addNode(.{ + .tag = .block_two, + .main_token = l_brace, + .data = .{ .opt_node_and_opt_node = .{ + .none, .none, + } }, + }); + }, + .block_single => { + const payload = node.castTag(.block_single).?.data; + const l_brace = try c.addToken(.l_brace, "{"); + + const stmt = (try renderNodeOpt(c, payload)) orelse { + _ = try c.addToken(.r_brace, "}"); + return c.addNode(.{ + .tag = .block_two, + .main_token = l_brace, + .data = .{ .opt_node_and_opt_node = .{ + .none, .none, + } }, + }); + }; + try addSemicolonIfNeeded(c, payload); + + _ = try c.addToken(.r_brace, "}"); + return c.addNode(.{ + .tag = .block_two_semicolon, + .main_token = l_brace, + .data = .{ .opt_node_and_opt_node = .{ + stmt.toOptional(), .none, + } }, + }); + }, + .block => { + const payload = node.castTag(.block).?.data; + if (payload.label) |some| { + _ = try c.addIdentifier(some); + _ = try c.addToken(.colon, ":"); + } + const l_brace = try c.addToken(.l_brace, "{"); + + var stmts = std.array_list.Managed(NodeIndex).init(c.gpa); + defer stmts.deinit(); + for (payload.stmts) |stmt| { + const res = (try renderNodeOpt(c, stmt)) orelse continue; + try addSemicolonIfNeeded(c, stmt); + try stmts.append(res); + } + const span = try c.listToSpan(stmts.items); + _ = try c.addToken(.r_brace, "}"); + + const semicolon = c.tokens.items(.tag)[c.tokens.len - 2] == .semicolon; + return c.addNode(.{ + .tag = if (semicolon) .block_semicolon else .block, + .main_token = l_brace, + .data = .{ .extra_range = span }, + }); + }, + .func => return renderFunc(c, node), + .pub_inline_fn => return renderMacroFunc(c, node), + .@"while" => { + const payload = node.castTag(.@"while").?.data; + const while_tok = try c.addToken(.keyword_while, "while"); + _ = try c.addToken(.l_paren, "("); + const cond = try renderNode(c, payload.cond); + _ = try c.addToken(.r_paren, ")"); + + const cont_expr_opt = if (payload.cont_expr) |some| blk: { + _ = try c.addToken(.colon, ":"); + _ = try c.addToken(.l_paren, "("); + const res = try renderNode(c, some); + _ = try c.addToken(.r_paren, ")"); + break :blk res; + } else null; + const body = try renderNode(c, payload.body); + + if (cont_expr_opt) |cont_expr| { + return c.addNode(.{ + .tag = .while_cont, + .main_token = while_tok, + .data = .{ .node_and_extra = .{ + cond, + try c.addExtra(std.zig.Ast.Node.WhileCont{ + .cont_expr = cont_expr, + .then_expr = body, + }), + } }, + }); + } else { + return c.addNode(.{ + .tag = .while_simple, + .main_token = while_tok, + .data = .{ .node_and_node = .{ + cond, body, + } }, + }); + } + }, + .while_true => { + const payload = node.castTag(.while_true).?.data; + const while_tok = try c.addToken(.keyword_while, "while"); + _ = try c.addToken(.l_paren, "("); + const cond = try c.addNode(.{ + .tag = .identifier, + .main_token = try c.addToken(.identifier, "true"), + .data = undefined, + }); + _ = try c.addToken(.r_paren, ")"); + const body = try renderNode(c, payload); + + return c.addNode(.{ + .tag = .while_simple, + .main_token = while_tok, + .data = .{ .node_and_node = .{ + cond, body, + } }, + }); + }, + .@"if" => { + const payload = node.castTag(.@"if").?.data; + const if_tok = try c.addToken(.keyword_if, "if"); + _ = try c.addToken(.l_paren, "("); + const cond = try renderNode(c, payload.cond); + _ = try c.addToken(.r_paren, ")"); + + const then_expr = try renderNode(c, payload.then); + const else_node = payload.@"else" orelse return c.addNode(.{ + .tag = .if_simple, + .main_token = if_tok, + .data = .{ .node_and_node = .{ + cond, then_expr, + } }, + }); + _ = try c.addToken(.keyword_else, "else"); + const else_expr = try renderNode(c, else_node); + + return c.addNode(.{ + .tag = .@"if", + .main_token = if_tok, + .data = .{ .node_and_extra = .{ + cond, + try c.addExtra(std.zig.Ast.Node.If{ + .then_expr = then_expr, + .else_expr = else_expr, + }), + } }, + }); + }, + .if_not_break => { + const payload = node.castTag(.if_not_break).?.data; + const if_tok = try c.addToken(.keyword_if, "if"); + _ = try c.addToken(.l_paren, "("); + const cond = try c.addNode(.{ + .tag = .bool_not, + .main_token = try c.addToken(.bang, "!"), + .data = .{ + .node = try renderNodeGrouped(c, payload), + }, + }); + _ = try c.addToken(.r_paren, ")"); + const then_expr = try c.addNode(.{ + .tag = .@"break", + .main_token = try c.addToken(.keyword_break, "break"), + .data = .{ .opt_token_and_opt_node = .{ + .none, .none, + } }, + }); + + return c.addNode(.{ + .tag = .if_simple, + .main_token = if_tok, + .data = .{ .node_and_node = .{ + cond, then_expr, + } }, + }); + }, + .@"switch" => { + const payload = node.castTag(.@"switch").?.data; + const switch_tok = try c.addToken(.keyword_switch, "switch"); + _ = try c.addToken(.l_paren, "("); + const cond = try renderNode(c, payload.cond); + _ = try c.addToken(.r_paren, ")"); + + _ = try c.addToken(.l_brace, "{"); + var cases = try c.gpa.alloc(NodeIndex, payload.cases.len); + defer c.gpa.free(cases); + for (payload.cases, 0..) |case, i| { + cases[i] = try renderNode(c, case); + _ = try c.addToken(.comma, ","); + } + const span = try c.listToSpan(cases); + _ = try c.addToken(.r_brace, "}"); + return c.addNode(.{ + .tag = .switch_comma, + .main_token = switch_tok, + .data = .{ .node_and_extra = .{ + cond, + try c.addExtra(NodeSubRange{ + .start = span.start, + .end = span.end, + }), + } }, + }); + }, + .switch_else => { + const payload = node.castTag(.switch_else).?.data; + _ = try c.addToken(.keyword_else, "else"); + return c.addNode(.{ + .tag = .switch_case_one, + .main_token = try c.addToken(.equal_angle_bracket_right, "=>"), + .data = .{ .opt_node_and_node = .{ + .none, try renderNode(c, payload), + } }, + }); + }, + .switch_prong => { + const payload = node.castTag(.switch_prong).?.data; + var items = try c.gpa.alloc(NodeIndex, payload.cases.len); + defer c.gpa.free(items); + + for (payload.cases, 0..) |item, i| { + if (i != 0) _ = try c.addToken(.comma, ","); + items[i] = try renderNode(c, item); + } + _ = try c.addToken(.r_brace, "}"); + if (items.len < 2) { + return c.addNode(.{ + .tag = .switch_case_one, + .main_token = try c.addToken(.equal_angle_bracket_right, "=>"), + .data = .{ .opt_node_and_node = .{ + if (payload.cases.len == 1) items[0].toOptional() else .none, + try renderNode(c, payload.cond), + } }, + }); + } else { + return c.addNode(.{ + .tag = .switch_case, + .main_token = try c.addToken(.equal_angle_bracket_right, "=>"), + .data = .{ .extra_and_node = .{ + try c.addExtra(try c.listToSpan(items)), + try renderNode(c, payload.cond), + } }, + }); + } + }, + .opaque_literal => { + const opaque_tok = try c.addToken(.keyword_opaque, "opaque"); + _ = try c.addToken(.l_brace, "{"); + _ = try c.addToken(.r_brace, "}"); + + return c.addNode(.{ + .tag = .container_decl_two, + .main_token = opaque_tok, + .data = .{ .opt_node_and_opt_node = .{ + .none, .none, + } }, + }); + }, + .array_access => { + const payload = node.castTag(.array_access).?.data; + const lhs = try renderNodeGrouped(c, payload.lhs); + const l_bracket = try c.addToken(.l_bracket, "["); + const index_expr = try renderNode(c, payload.rhs); + _ = try c.addToken(.r_bracket, "]"); + return c.addNode(.{ + .tag = .array_access, + .main_token = l_bracket, + .data = .{ .node_and_node = .{ + lhs, index_expr, + } }, + }); + }, + .array_type => { + const payload = node.castTag(.array_type).?.data; + return renderArrayType(c, payload.len, payload.elem_type); + }, + .null_sentinel_array_type => { + const payload = node.castTag(.null_sentinel_array_type).?.data; + return renderNullSentinelArrayType(c, payload.len, payload.elem_type); + }, + .array_filler => { + const payload = node.castTag(.array_filler).?.data; + + const type_expr = try renderArrayType(c, 1, payload.type); + const l_brace = try c.addToken(.l_brace, "{"); + const val = try renderNode(c, payload.filler); + _ = try c.addToken(.r_brace, "}"); + + const init = try c.addNode(.{ + .tag = .array_init_one, + .main_token = l_brace, + .data = .{ .node_and_node = .{ + type_expr, val, + } }, + }); + return c.addNode(.{ + .tag = .array_cat, + .main_token = try c.addToken(.asterisk_asterisk, "**"), + .data = .{ .node_and_node = .{ + init, + try c.addNode(.{ + .tag = .number_literal, + .main_token = try c.addTokenFmt(.number_literal, "{d}", .{payload.count}), + .data = undefined, + }), + } }, + }); + }, + .empty_array => { + const payload = node.castTag(.empty_array).?.data; + + const type_expr = try renderNode(c, payload); + return renderArrayInit(c, type_expr, &.{}); + }, + .array_init => { + const payload = node.castTag(.array_init).?.data; + const type_expr = try renderNode(c, payload.cond); + return renderArrayInit(c, type_expr, payload.cases); + }, + .vector_zero_init => { + const payload = node.castTag(.vector_zero_init).?.data; + return renderBuiltinCall(c, "@splat", &.{payload}); + }, + .field_access => { + const payload = node.castTag(.field_access).?.data; + const lhs = try renderNodeGrouped(c, payload.lhs); + return renderFieldAccess(c, lhs, payload.field_name); + }, + .@"struct", .@"union", .@"opaque" => return renderContainer(c, node), + .enum_constant => { + const payload = node.castTag(.enum_constant).?.data; + + if (payload.is_public) _ = try c.addToken(.keyword_pub, "pub"); + const const_tok = try c.addToken(.keyword_const, "const"); + _ = try c.addIdentifier(payload.name); + + const type_node_opt = if (payload.type) |enum_const_type| blk: { + _ = try c.addToken(.colon, ":"); + break :blk try renderNode(c, enum_const_type); + } else null; + + _ = try c.addToken(.equal, "="); + + const init_node = try renderNode(c, payload.value); + _ = try c.addToken(.semicolon, ";"); + + return c.addNode(.{ + .tag = .simple_var_decl, + .main_token = const_tok, + .data = .{ .opt_node_and_opt_node = .{ + .fromOptional(type_node_opt), + init_node.toOptional(), + } }, + }); + }, + .tuple => { + const payload = node.castTag(.tuple).?.data; + _ = try c.addToken(.period, "."); + const l_brace = try c.addToken(.l_brace, "{"); + var inits = try c.gpa.alloc(NodeIndex, payload.len); + defer c.gpa.free(inits); + + for (payload, 0..) |init, i| { + if (i != 0) _ = try c.addToken(.comma, ","); + inits[i] = try renderNode(c, init); + } + _ = try c.addToken(.r_brace, "}"); + if (payload.len < 3) { + return c.addNode(.{ + .tag = .array_init_dot_two, + .main_token = l_brace, + .data = .{ .opt_node_and_opt_node = .{ + if (inits.len >= 1) inits[0].toOptional() else .none, + if (inits.len >= 2) inits[1].toOptional() else .none, + } }, + }); + } else { + return c.addNode(.{ + .tag = .array_init_dot, + .main_token = l_brace, + .data = .{ .extra_range = try c.listToSpan(inits) }, + }); + } + }, + .container_init_dot => { + const payload = node.castTag(.container_init_dot).?.data; + _ = try c.addToken(.period, "."); + const l_brace = try c.addToken(.l_brace, "{"); + var inits = try c.gpa.alloc(NodeIndex, payload.len); + defer c.gpa.free(inits); + + for (payload, 0..) |init, i| { + _ = try c.addToken(.period, "."); + _ = try c.addIdentifier(init.name); + _ = try c.addToken(.equal, "="); + inits[i] = try renderNode(c, init.value); + _ = try c.addToken(.comma, ","); + } + _ = try c.addToken(.r_brace, "}"); + + if (payload.len < 3) { + return c.addNode(.{ + .tag = .struct_init_dot_two_comma, + .main_token = l_brace, + .data = .{ .opt_node_and_opt_node = .{ + if (inits.len >= 1) inits[0].toOptional() else .none, + if (inits.len >= 2) inits[1].toOptional() else .none, + } }, + }); + } else { + return c.addNode(.{ + .tag = .struct_init_dot_comma, + .main_token = l_brace, + .data = .{ .extra_range = try c.listToSpan(inits) }, + }); + } + }, + .container_init => { + const payload = node.castTag(.container_init).?.data; + const lhs = try renderNode(c, payload.lhs); + + const l_brace = try c.addToken(.l_brace, "{"); + var inits = try c.gpa.alloc(NodeIndex, payload.inits.len); + defer c.gpa.free(inits); + + for (payload.inits, 0..) |init, i| { + _ = try c.addToken(.period, "."); + _ = try c.addIdentifier(init.name); + _ = try c.addToken(.equal, "="); + inits[i] = try renderNode(c, init.value); + _ = try c.addToken(.comma, ","); + } + _ = try c.addToken(.r_brace, "}"); + + switch (inits.len) { + 0 => return c.addNode(.{ + .tag = .struct_init_one, + .main_token = l_brace, + .data = .{ .node_and_opt_node = .{ + lhs, .none, + } }, + }), + 1 => return c.addNode(.{ + .tag = .struct_init_one_comma, + .main_token = l_brace, + .data = .{ .node_and_opt_node = .{ + lhs, inits[0].toOptional(), + } }, + }), + else => return c.addNode(.{ + .tag = .struct_init_comma, + .main_token = l_brace, + .data = .{ .node_and_extra = .{ + lhs, + try c.addExtra(try c.listToSpan(inits)), + } }, + }), + } + }, + .static_assert => { + const payload = node.castTag(.static_assert).?.data; + const comptime_tok = try c.addToken(.keyword_comptime, "comptime"); + const l_brace = try c.addToken(.l_brace, "{"); + + const if_tok = try c.addToken(.keyword_if, "if"); + _ = try c.addToken(.l_paren, "("); + const cond = try c.addNode(.{ + .tag = .bool_not, + .main_token = try c.addToken(.bang, "!"), + .data = .{ + .node = try renderNodeGrouped(c, payload.lhs), + }, + }); + _ = try c.addToken(.r_paren, ")"); + + const compile_error_tok = try c.addToken(.builtin, "@compileError"); + _ = try c.addToken(.l_paren, "("); + const err_msg = try renderNode(c, payload.rhs); + _ = try c.addToken(.r_paren, ")"); + const compile_error = try c.addNode(.{ + .tag = .builtin_call_two, + .main_token = compile_error_tok, + .data = .{ .opt_node_and_opt_node = .{ + err_msg.toOptional(), .none, + } }, + }); + + const if_node = try c.addNode(.{ + .tag = .if_simple, + .main_token = if_tok, + .data = .{ .node_and_node = .{ + cond, compile_error, + } }, + }); + _ = try c.addToken(.semicolon, ";"); + _ = try c.addToken(.r_brace, "}"); + const block_node = try c.addNode(.{ + .tag = .block_two_semicolon, + .main_token = l_brace, + .data = .{ .opt_node_and_opt_node = .{ + if_node.toOptional(), .none, + } }, + }); + + return c.addNode(.{ + .tag = .@"comptime", + .main_token = comptime_tok, + .data = .{ + .node = block_node, + }, + }); + }, + .@"anytype" => unreachable, // Handled in renderParams + } +} + +fn renderContainer(c: *Context, node: Node) !NodeIndex { + const payload = @as(*Payload.Container, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data; + if (payload.layout == .@"packed") + _ = try c.addToken(.keyword_packed, "packed") + else if (payload.layout == .@"extern") + _ = try c.addToken(.keyword_extern, "extern"); + const kind_tok = if (node.tag() == .@"struct") + try c.addToken(.keyword_struct, "struct") + else if (node.tag() == .@"union") + try c.addToken(.keyword_union, "union") + else if (node.tag() == .@"opaque") + try c.addToken(.keyword_opaque, "opaque") + else + unreachable; + + _ = try c.addToken(.l_brace, "{"); + + const num_decls = payload.decls.len; + const total_members = payload.fields.len + num_decls; + const members = try c.gpa.alloc(NodeIndex, total_members); + defer c.gpa.free(members); + + for (payload.fields, 0..) |field, i| { + const name_tok = try c.addTokenFmt(.identifier, "{f}", .{std.zig.fmtIdFlags(field.name, .{ .allow_primitive = true })}); + _ = try c.addToken(.colon, ":"); + const type_expr = try renderNode(c, field.type); + + const align_expr_opt = if (field.alignment) |alignment| blk: { + _ = try c.addToken(.keyword_align, "align"); + _ = try c.addToken(.l_paren, "("); + const align_expr = try c.addNode(.{ + .tag = .number_literal, + .main_token = try c.addTokenFmt(.number_literal, "{d}", .{alignment}), + .data = undefined, + }); + _ = try c.addToken(.r_paren, ")"); + break :blk align_expr; + } else null; + + const value_expr_opt = if (field.default_value) |value| blk: { + _ = try c.addToken(.equal, "="); + break :blk try renderNode(c, value); + } else null; + + if (align_expr_opt) |align_expr| { + if (value_expr_opt) |value_expr| { + members[i] = try c.addNode(.{ + .tag = .container_field, + .main_token = name_tok, + .data = .{ .node_and_extra = .{ + type_expr, + try c.addExtra(std.zig.Ast.Node.ContainerField{ + .align_expr = align_expr, + .value_expr = value_expr, + }), + } }, + }); + } else { + members[i] = try c.addNode(.{ + .tag = .container_field_align, + .main_token = name_tok, + .data = .{ .node_and_node = .{ + type_expr, + align_expr, + } }, + }); + } + } else { + members[i] = try c.addNode(.{ + .tag = .container_field_init, + .main_token = name_tok, + .data = .{ .node_and_opt_node = .{ + type_expr, + .fromOptional(value_expr_opt), + } }, + }); + } + _ = try c.addToken(.comma, ","); + } + for (members[payload.fields.len..], payload.decls) |*member, decl| { + member.* = try renderNode(c, decl); + } + const trailing = switch (c.tokens.items(.tag)[c.tokens.len - 1]) { + .comma, .semicolon => true, + else => false, + }; + _ = try c.addToken(.r_brace, "}"); + + if (total_members == 0) { + return c.addNode(.{ + .tag = .container_decl_two, + .main_token = kind_tok, + .data = .{ .opt_node_and_opt_node = .{ + .none, .none, + } }, + }); + } else if (total_members <= 2) { + return c.addNode(.{ + .tag = if (trailing) .container_decl_two_trailing else .container_decl_two, + .main_token = kind_tok, + .data = .{ .opt_node_and_opt_node = .{ + if (members.len >= 1) members[0].toOptional() else .none, + if (members.len >= 2) members[1].toOptional() else .none, + } }, + }); + } else { + const span = try c.listToSpan(members); + return c.addNode(.{ + .tag = if (trailing) .container_decl_trailing else .container_decl, + .main_token = kind_tok, + .data = .{ .extra_range = span }, + }); + } +} + +fn renderFieldAccess(c: *Context, lhs: NodeIndex, field_name: []const u8) !NodeIndex { + return c.addNode(.{ + .tag = .field_access, + .main_token = try c.addToken(.period, "."), + .data = .{ .node_and_token = .{ + lhs, try c.addTokenFmt(.identifier, "{f}", .{std.zig.fmtIdFlags(field_name, .{ .allow_primitive = true })}), + } }, + }); +} + +fn renderArrayInit(c: *Context, lhs: NodeIndex, inits: []const Node) !NodeIndex { + const l_brace = try c.addToken(.l_brace, "{"); + var rendered = try c.gpa.alloc(NodeIndex, inits.len); + defer c.gpa.free(rendered); + + for (inits, 0..) |init, i| { + rendered[i] = try renderNode(c, init); + _ = try c.addToken(.comma, ","); + } + _ = try c.addToken(.r_brace, "}"); + switch (inits.len) { + 0 => return c.addNode(.{ + .tag = .struct_init_one, + .main_token = l_brace, + .data = .{ .node_and_opt_node = .{ + lhs, .none, + } }, + }), + 1 => return c.addNode(.{ + .tag = .array_init_one_comma, + .main_token = l_brace, + .data = .{ .node_and_node = .{ + lhs, rendered[0], + } }, + }), + else => return c.addNode(.{ + .tag = .array_init_comma, + .main_token = l_brace, + .data = .{ .node_and_extra = .{ + lhs, + try c.addExtra(try c.listToSpan(rendered)), + } }, + }), + } +} + +fn renderArrayType(c: *Context, len: u64, elem_type: Node) !NodeIndex { + const l_bracket = try c.addToken(.l_bracket, "["); + const len_expr = try c.addNode(.{ + .tag = .number_literal, + .main_token = try c.addTokenFmt(.number_literal, "{d}", .{len}), + .data = undefined, + }); + _ = try c.addToken(.r_bracket, "]"); + const elem_type_expr = try renderNode(c, elem_type); + return c.addNode(.{ + .tag = .array_type, + .main_token = l_bracket, + .data = .{ .node_and_node = .{ + len_expr, elem_type_expr, + } }, + }); +} + +fn renderNullSentinelArrayType(c: *Context, len: u64, elem_type: Node) !NodeIndex { + const l_bracket = try c.addToken(.l_bracket, "["); + const len_expr = try c.addNode(.{ + .tag = .number_literal, + .main_token = try c.addTokenFmt(.number_literal, "{d}", .{len}), + .data = undefined, + }); + _ = try c.addToken(.colon, ":"); + + const sentinel_expr = try c.addNode(.{ + .tag = .number_literal, + .main_token = try c.addToken(.number_literal, "0"), + .data = undefined, + }); + + _ = try c.addToken(.r_bracket, "]"); + const elem_type_expr = try renderNode(c, elem_type); + return c.addNode(.{ + .tag = .array_type_sentinel, + .main_token = l_bracket, + .data = .{ .node_and_extra = .{ + len_expr, + try c.addExtra(std.zig.Ast.Node.ArrayTypeSentinel{ + .sentinel = sentinel_expr, + .elem_type = elem_type_expr, + }), + } }, + }); +} + +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", .wrapped_local, .mut_str => {}, + .while_true => { + const payload = node.castTag(.while_true).?.data; + return addSemicolonIfNotBlock(c, payload); + }, + .@"while" => { + const payload = node.castTag(.@"while").?.data; + return addSemicolonIfNotBlock(c, payload.body); + }, + .@"if" => { + const payload = node.castTag(.@"if").?.data; + if (payload.@"else") |some| + return addSemicolonIfNeeded(c, some); + return addSemicolonIfNotBlock(c, payload.then); + }, + else => _ = try c.addToken(.semicolon, ";"), + } +} + +fn addSemicolonIfNotBlock(c: *Context, node: Node) !void { + switch (node.tag()) { + .block, .empty_block, .block_single => {}, + else => _ = try c.addToken(.semicolon, ";"), + } +} + +fn renderNodeGrouped(c: *Context, node: Node) !NodeIndex { + switch (node.tag()) { + .declaration => unreachable, + .null_literal, + .undefined_literal, + .true_literal, + .false_literal, + .return_void, + .zero_literal, + .one_literal, + .void_type, + .noreturn_type, + .@"anytype", + .div_trunc, + .int_cast, + .const_cast, + .volatile_cast, + .as, + .truncate, + .bit_cast, + .float_cast, + .int_from_float, + .float_from_int, + .ptr_from_int, + .std_mem_zeroes, + .int_from_ptr, + .sizeof, + .alignof, + .typeof, + .typeinfo, + .vector, + .std_mem_zeroinit, + .integer_literal, + .float_literal, + .string_literal, + .string_slice, + .char_literal, + .enum_literal, + .identifier, + .field_access, + .ptr_cast, + .type, + .array_access, + .align_cast, + .optional_type, + .c_pointer, + .single_pointer, + .unwrap, + .deref, + .not, + .negate, + .negate_wrap, + .bit_not, + .func, + .call, + .array_type, + .null_sentinel_array_type, + .int_from_bool, + .div_exact, + .offset_of, + .shuffle, + .builtin_extern, + .wrapped_local, + .mut_str, + .helper_call, + .helper_ref, + .byte_swap, + .ceil, + .cos, + .sin, + .exp, + .exp2, + .exp10, + .abs, + .log, + .log2, + .log10, + .round, + .sqrt, + .trunc, + .floor, + => { + // no grouping needed + return renderNode(c, node); + }, + + .opaque_literal, + .@"opaque", + .empty_array, + .block_single, + .add, + .add_wrap, + .sub, + .sub_wrap, + .mul, + .mul_wrap, + .div, + .shl, + .shr, + .mod, + .@"and", + .@"or", + .less_than, + .less_than_equal, + .greater_than, + .greater_than_equal, + .equal, + .not_equal, + .bit_and, + .bit_or, + .bit_xor, + .empty_block, + .array_cat, + .array_filler, + .@"if", + .@"struct", + .@"union", + .array_init, + .vector_zero_init, + .tuple, + .container_init, + .container_init_dot, + .block, + .address_of, + => return c.addNode(.{ + .tag = .grouped_expression, + .main_token = try c.addToken(.l_paren, "("), + .data = .{ .node_and_token = .{ + try renderNode(c, node), + try c.addToken(.r_paren, ")"), + } }, + }), + .ellipsis3, + .switch_prong, + .warning, + .var_decl, + .fail_decl, + .arg_redecl, + .alias, + .var_simple, + .pub_var_simple, + .enum_constant, + .@"while", + .@"switch", + .@"break", + .break_val, + .pub_inline_fn, + .discard, + .@"continue", + .@"return", + .@"comptime", + .@"defer", + .asm_simple, + .while_true, + .if_not_break, + .switch_else, + .add_assign, + .add_wrap_assign, + .sub_assign, + .sub_wrap_assign, + .mul_assign, + .mul_wrap_assign, + .div_assign, + .shl_assign, + .shr_assign, + .mod_assign, + .bit_and_assign, + .bit_or_assign, + .bit_xor_assign, + .assign, + .static_assert, + .@"unreachable", + => { + // these should never appear in places where grouping might be needed. + unreachable; + }, + } +} + +fn renderPrefixOp(c: *Context, node: Node, tag: std.zig.Ast.Node.Tag, tok_tag: TokenTag, bytes: []const u8) !NodeIndex { + const payload = @as(*Payload.UnOp, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data; + return c.addNode(.{ + .tag = tag, + .main_token = try c.addToken(tok_tag, bytes), + .data = .{ + .node = try renderNodeGrouped(c, payload), + }, + }); +} + +fn renderBinOpGrouped(c: *Context, node: Node, tag: std.zig.Ast.Node.Tag, tok_tag: TokenTag, bytes: []const u8) !NodeIndex { + const payload = @as(*Payload.BinOp, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data; + const lhs = try renderNodeGrouped(c, payload.lhs); + return c.addNode(.{ + .tag = tag, + .main_token = try c.addToken(tok_tag, bytes), + .data = .{ .node_and_node = .{ + lhs, try renderNodeGrouped(c, payload.rhs), + } }, + }); +} + +fn renderBinOp(c: *Context, node: Node, tag: std.zig.Ast.Node.Tag, tok_tag: TokenTag, bytes: []const u8) !NodeIndex { + const payload = @as(*Payload.BinOp, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data; + const lhs = try renderNode(c, payload.lhs); + return c.addNode(.{ + .tag = tag, + .main_token = try c.addToken(tok_tag, bytes), + .data = .{ .node_and_node = .{ + lhs, try renderNode(c, payload.rhs), + } }, + }); +} + +fn renderStdImport(c: *Context, parts: []const []const u8) !NodeIndex { + const import_tok = try c.addToken(.builtin, "@import"); + _ = try c.addToken(.l_paren, "("); + const std_tok = try c.addToken(.string_literal, "\"std\""); + const std_node = try c.addNode(.{ + .tag = .string_literal, + .main_token = std_tok, + .data = undefined, + }); + _ = try c.addToken(.r_paren, ")"); + + const import_node = try c.addNode(.{ + .tag = .builtin_call_two, + .main_token = import_tok, + .data = .{ .opt_node_and_opt_node = .{ + std_node.toOptional(), .none, + } }, + }); + + var access_chain = import_node; + for (parts) |part| { + access_chain = try renderFieldAccess(c, access_chain, part); + } + return access_chain; +} + +fn renderCall(c: *Context, lhs: NodeIndex, args: []const Node) !NodeIndex { + const lparen = try c.addToken(.l_paren, "("); + const res = switch (args.len) { + 0 => try c.addNode(.{ + .tag = .call_one, + .main_token = lparen, + .data = .{ .node_and_opt_node = .{ + lhs, .none, + } }, + }), + 1 => try c.addNode(.{ + .tag = .call_one, + .main_token = lparen, + .data = .{ .node_and_opt_node = .{ + lhs, (try renderNode(c, args[0])).toOptional(), + } }, + }), + else => blk: { + var rendered = try c.gpa.alloc(NodeIndex, args.len); + defer c.gpa.free(rendered); + + for (args, 0..) |arg, i| { + if (i != 0) _ = try c.addToken(.comma, ","); + rendered[i] = try renderNode(c, arg); + } + const span = try c.listToSpan(rendered); + break :blk try c.addNode(.{ + .tag = .call, + .main_token = lparen, + .data = .{ .node_and_extra = .{ + lhs, try c.addExtra(NodeSubRange{ + .start = span.start, + .end = span.end, + }), + } }, + }); + }, + }; + _ = try c.addToken(.r_paren, ")"); + return res; +} + +fn renderBuiltinCall(c: *Context, builtin: []const u8, args: []const Node) !NodeIndex { + const builtin_tok = try c.addToken(.builtin, builtin); + _ = try c.addToken(.l_paren, "("); + var arg_1: ?NodeIndex = null; + var arg_2: ?NodeIndex = null; + var arg_3: ?NodeIndex = null; + var arg_4: ?NodeIndex = null; + switch (args.len) { + 0 => {}, + 1 => { + arg_1 = try renderNode(c, args[0]); + }, + 2 => { + arg_1 = try renderNode(c, args[0]); + _ = try c.addToken(.comma, ","); + arg_2 = try renderNode(c, args[1]); + }, + 4 => { + arg_1 = try renderNode(c, args[0]); + _ = try c.addToken(.comma, ","); + arg_2 = try renderNode(c, args[1]); + _ = try c.addToken(.comma, ","); + arg_3 = try renderNode(c, args[2]); + _ = try c.addToken(.comma, ","); + arg_4 = try renderNode(c, args[3]); + }, + else => unreachable, // expand this function as needed. + } + + _ = try c.addToken(.r_paren, ")"); + if (args.len <= 2) { + return c.addNode(.{ + .tag = .builtin_call_two, + .main_token = builtin_tok, + .data = .{ .opt_node_and_opt_node = .{ + .fromOptional(arg_1), .fromOptional(arg_2), + } }, + }); + } else { + std.debug.assert(args.len == 4); + + const params = try c.listToSpan(&.{ arg_1.?, arg_2.?, arg_3.?, arg_4.? }); + return c.addNode(.{ + .tag = .builtin_call, + .main_token = builtin_tok, + .data = .{ .extra_range = .{ + .start = params.start, + .end = params.end, + } }, + }); + } +} + +fn renderVar(c: *Context, node: Node) !NodeIndex { + const payload = node.castTag(.var_decl).?.data; + if (payload.is_pub) _ = try c.addToken(.keyword_pub, "pub"); + if (payload.is_extern) _ = try c.addToken(.keyword_extern, "extern"); + if (payload.is_export) _ = try c.addToken(.keyword_export, "export"); + if (payload.is_threadlocal) _ = try c.addToken(.keyword_threadlocal, "threadlocal"); + const mut_tok = if (payload.is_const) + try c.addToken(.keyword_const, "const") + else + try c.addToken(.keyword_var, "var"); + _ = try c.addIdentifier(payload.name); + _ = try c.addToken(.colon, ":"); + const type_node = try renderNode(c, payload.type); + + const align_node_opt = if (payload.alignment) |some| blk: { + _ = try c.addToken(.keyword_align, "align"); + _ = try c.addToken(.l_paren, "("); + const res = try c.addNode(.{ + .tag = .number_literal, + .main_token = try c.addTokenFmt(.number_literal, "{d}", .{some}), + .data = undefined, + }); + _ = try c.addToken(.r_paren, ")"); + break :blk res; + } else null; + + const section_node_opt = if (payload.linksection_string) |some| blk: { + _ = try c.addToken(.keyword_linksection, "linksection"); + _ = try c.addToken(.l_paren, "("); + const res = try c.addNode(.{ + .tag = .string_literal, + .main_token = try c.addTokenFmt(.string_literal, "\"{f}\"", .{std.zig.fmtString(some)}), + .data = undefined, + }); + _ = try c.addToken(.r_paren, ")"); + break :blk res; + } else null; + + const init_node_opt = if (payload.init) |some| blk: { + _ = try c.addToken(.equal, "="); + break :blk try renderNode(c, some); + } else null; + _ = try c.addToken(.semicolon, ";"); + + if (section_node_opt) |section_node| { + return c.addNode(.{ + .tag = .global_var_decl, + .main_token = mut_tok, + .data = .{ .extra_and_opt_node = .{ + try c.addExtra(std.zig.Ast.Node.GlobalVarDecl{ + .type_node = type_node.toOptional(), + .align_node = .fromOptional(align_node_opt), + .section_node = section_node.toOptional(), + .addrspace_node = .none, + }), + .fromOptional(init_node_opt), + } }, + }); + } else { + if (align_node_opt) |align_node| { + return c.addNode(.{ + .tag = .local_var_decl, + .main_token = mut_tok, + .data = .{ .extra_and_opt_node = .{ + try c.addExtra(std.zig.Ast.Node.LocalVarDecl{ + .type_node = type_node, + .align_node = align_node, + }), + .fromOptional(init_node_opt), + } }, + }); + } else { + return c.addNode(.{ + .tag = .simple_var_decl, + .main_token = mut_tok, + .data = .{ + .opt_node_and_opt_node = .{ + type_node.toOptional(), // Type expression + .fromOptional(init_node_opt), // Init expression + }, + }, + }); + } + } +} + +fn renderFunc(c: *Context, node: Node) !NodeIndex { + const payload = node.castTag(.func).?.data; + if (payload.is_pub) _ = try c.addToken(.keyword_pub, "pub"); + if (payload.is_extern) _ = try c.addToken(.keyword_extern, "extern"); + if (payload.is_export) _ = try c.addToken(.keyword_export, "export"); + if (payload.is_inline) _ = try c.addToken(.keyword_inline, "inline"); + const fn_token = try c.addToken(.keyword_fn, "fn"); + if (payload.name) |some| _ = try c.addIdentifier(some); + + const params = try renderParams(c, payload.params, payload.is_var_args); + defer params.deinit(); + var span: NodeSubRange = undefined; + if (params.items.len > 1) span = try c.listToSpan(params.items); + + const align_expr_opt = if (payload.alignment) |some| blk: { + _ = try c.addToken(.keyword_align, "align"); + _ = try c.addToken(.l_paren, "("); + const res = try c.addNode(.{ + .tag = .number_literal, + .main_token = try c.addTokenFmt(.number_literal, "{d}", .{some}), + .data = undefined, + }); + _ = try c.addToken(.r_paren, ")"); + break :blk res; + } else null; + + const section_expr_opt = if (payload.linksection_string) |some| blk: { + _ = try c.addToken(.keyword_linksection, "linksection"); + _ = try c.addToken(.l_paren, "("); + const res = try c.addNode(.{ + .tag = .string_literal, + .main_token = try c.addTokenFmt(.string_literal, "\"{f}\"", .{std.zig.fmtString(some)}), + .data = undefined, + }); + _ = try c.addToken(.r_paren, ")"); + break :blk res; + } else null; + + const callconv_expr_opt = if (payload.explicit_callconv) |some| blk: { + _ = try c.addToken(.keyword_callconv, "callconv"); + _ = try c.addToken(.l_paren, "("); + const cc_node = switch (some) { + .c => cc_node: { + _ = try c.addToken(.period, "."); + break :cc_node try c.addNode(.{ + .tag = .enum_literal, + .main_token = try c.addToken(.identifier, "c"), + .data = undefined, + }); + }, + .x86_64_sysv, + .x86_64_win, + .x86_stdcall, + .x86_fastcall, + .x86_thiscall, + .x86_vectorcall, + .x86_regcall, + .aarch64_vfabi, + .aarch64_sve_pcs, + .arm_aapcs, + .arm_aapcs_vfp, + .m68k_rtd, + .riscv_vector, + => cc_node: { + // .{ .foo = .{} } + _ = try c.addToken(.period, "."); + const outer_lbrace = try c.addToken(.l_brace, "{"); + _ = try c.addToken(.period, "."); + _ = try c.addToken(.identifier, @tagName(some)); + _ = try c.addToken(.equal, "="); + _ = try c.addToken(.period, "."); + const inner_lbrace = try c.addToken(.l_brace, "{"); + _ = try c.addToken(.r_brace, "}"); + _ = try c.addToken(.r_brace, "}"); + break :cc_node try c.addNode(.{ + .tag = .struct_init_dot_two, + .main_token = outer_lbrace, + .data = .{ .opt_node_and_opt_node = .{ + (try c.addNode(.{ + .tag = .struct_init_dot_two, + .main_token = inner_lbrace, + .data = .{ .opt_node_and_opt_node = .{ + .none, .none, + } }, + })).toOptional(), + .none, + } }, + }); + }, + }; + _ = try c.addToken(.r_paren, ")"); + break :blk cc_node; + } else null; + + const return_type_expr = try renderNode(c, payload.return_type); + + const fn_proto = try blk: { + if (align_expr_opt == null and section_expr_opt == null and callconv_expr_opt == null) { + if (params.items.len < 2) + break :blk c.addNode(.{ + .tag = .fn_proto_simple, + .main_token = fn_token, + .data = .{ .opt_node_and_opt_node = .{ + if (params.items.len == 1) params.items[0].toOptional() else .none, + return_type_expr.toOptional(), + } }, + }) + else + break :blk c.addNode(.{ + .tag = .fn_proto_multi, + .main_token = fn_token, + .data = .{ .extra_and_opt_node = .{ + try c.addExtra(span), + return_type_expr.toOptional(), + } }, + }); + } + if (params.items.len < 2) + break :blk c.addNode(.{ + .tag = .fn_proto_one, + .main_token = fn_token, + .data = .{ + .extra_and_opt_node = .{ + try c.addExtra(std.zig.Ast.Node.FnProtoOne{ + .param = if (params.items.len == 1) params.items[0].toOptional() else .none, + .align_expr = .fromOptional(align_expr_opt), + .addrspace_expr = .none, // TODO + .section_expr = .fromOptional(section_expr_opt), + .callconv_expr = .fromOptional(callconv_expr_opt), + }), + return_type_expr.toOptional(), + }, + }, + }) + else + break :blk c.addNode(.{ + .tag = .fn_proto, + .main_token = fn_token, + .data = .{ + .extra_and_opt_node = .{ + try c.addExtra(std.zig.Ast.Node.FnProto{ + .params_start = span.start, + .params_end = span.end, + .align_expr = .fromOptional(align_expr_opt), + .addrspace_expr = .none, // TODO + .section_expr = .fromOptional(section_expr_opt), + .callconv_expr = .fromOptional(callconv_expr_opt), + }), + return_type_expr.toOptional(), + }, + }, + }); + }; + + const payload_body = payload.body orelse { + if (payload.is_extern) { + _ = try c.addToken(.semicolon, ";"); + } + return fn_proto; + }; + const body = try renderNode(c, payload_body); + return c.addNode(.{ + .tag = .fn_decl, + .main_token = fn_token, + .data = .{ .node_and_node = .{ + fn_proto, body, + } }, + }); +} + +fn renderMacroFunc(c: *Context, node: Node) !NodeIndex { + const payload = node.castTag(.pub_inline_fn).?.data; + _ = try c.addToken(.keyword_pub, "pub"); + _ = try c.addToken(.keyword_inline, "inline"); + const fn_token = try c.addToken(.keyword_fn, "fn"); + _ = try c.addIdentifier(payload.name); + + const params = try renderParams(c, payload.params, false); + defer params.deinit(); + var span: NodeSubRange = undefined; + if (params.items.len > 1) span = try c.listToSpan(params.items); + + const return_type_expr = try renderNodeGrouped(c, payload.return_type); + + const fn_proto = blk: { + if (params.items.len < 2) { + break :blk try c.addNode(.{ + .tag = .fn_proto_simple, + .main_token = fn_token, + .data = .{ .opt_node_and_opt_node = .{ + if (params.items.len == 1) params.items[0].toOptional() else .none, + return_type_expr.toOptional(), + } }, + }); + } else { + break :blk try c.addNode(.{ + .tag = .fn_proto_multi, + .main_token = fn_token, + .data = .{ .extra_and_opt_node = .{ + try c.addExtra(span), + return_type_expr.toOptional(), + } }, + }); + } + }; + return c.addNode(.{ + .tag = .fn_decl, + .main_token = fn_token, + .data = .{ .node_and_node = .{ + fn_proto, try renderNode(c, payload.body), + } }, + }); +} + +fn renderParams(c: *Context, params: []Payload.Param, is_var_args: bool) !std.array_list.Managed(NodeIndex) { + _ = try c.addToken(.l_paren, "("); + var rendered = try std.array_list.Managed(NodeIndex).initCapacity(c.gpa, @max(params.len, 1)); + errdefer rendered.deinit(); + + for (params, 0..) |param, i| { + if (i != 0) _ = try c.addToken(.comma, ","); + if (param.is_noalias) _ = try c.addToken(.keyword_noalias, "noalias"); + if (param.name) |some| { + _ = try c.addIdentifier(some); + _ = try c.addToken(.colon, ":"); + } + if (param.type.tag() == .@"anytype") { + _ = try c.addToken(.keyword_anytype, "anytype"); + continue; + } + rendered.appendAssumeCapacity(try renderNode(c, param.type)); + } + if (is_var_args) { + if (params.len != 0) _ = try c.addToken(.comma, ","); + _ = try c.addToken(.ellipsis3, "..."); + } + _ = try c.addToken(.r_paren, ")"); + + return rendered; +} diff --git a/lib/compiler/translate-c/builtins.zig b/lib/compiler/translate-c/builtins.zig new file mode 100644 index 0000000000000000000000000000000000000000..cc385f8007a3b75610d3819b0c9f2cd130b985d4 --- /dev/null +++ b/lib/compiler/translate-c/builtins.zig @@ -0,0 +1,76 @@ +const std = @import("std"); + +const ast = @import("ast.zig"); + +/// All builtins need to have a source so that macros can reference them +/// but for some it is possible to directly call an equivalent Zig builtin +/// which is preferrable. +pub const Builtin = struct { + /// The name of the builtin in `c_builtins.zig`. + name: []const u8, + tag: ?ast.Node.Tag = null, +}; + +pub const map = std.StaticStringMap(Builtin).initComptime([_]struct { []const u8, Builtin }{ + .{ "__builtin_abs", .{ .name = "abs" } }, + .{ "__builtin_assume", .{ .name = "assume" } }, + .{ "__builtin_bswap16", .{ .name = "bswap16", .tag = .byte_swap } }, + .{ "__builtin_bswap32", .{ .name = "bswap32", .tag = .byte_swap } }, + .{ "__builtin_bswap64", .{ .name = "bswap64", .tag = .byte_swap } }, + .{ "__builtin_ceilf", .{ .name = "ceilf", .tag = .ceil } }, + .{ "__builtin_ceil", .{ .name = "ceil", .tag = .ceil } }, + .{ "__builtin_clz", .{ .name = "clz" } }, + .{ "__builtin_constant_p", .{ .name = "constant_p" } }, + .{ "__builtin_cosf", .{ .name = "cosf", .tag = .cos } }, + .{ "__builtin_cos", .{ .name = "cos", .tag = .cos } }, + .{ "__builtin_ctz", .{ .name = "ctz" } }, + .{ "__builtin_exp2f", .{ .name = "exp2f", .tag = .exp2 } }, + .{ "__builtin_exp2", .{ .name = "exp2", .tag = .exp2 } }, + .{ "__builtin_expf", .{ .name = "expf", .tag = .exp } }, + .{ "__builtin_exp", .{ .name = "exp", .tag = .exp } }, + .{ "__builtin_expect", .{ .name = "expect" } }, + .{ "__builtin_fabsf", .{ .name = "fabsf", .tag = .abs } }, + .{ "__builtin_fabs", .{ .name = "fabs", .tag = .abs } }, + .{ "__builtin_floorf", .{ .name = "floorf", .tag = .floor } }, + .{ "__builtin_floor", .{ .name = "floor", .tag = .floor } }, + .{ "__builtin_huge_valf", .{ .name = "huge_valf" } }, + .{ "__builtin_inff", .{ .name = "inff" } }, + .{ "__builtin_isinf_sign", .{ .name = "isinf_sign" } }, + .{ "__builtin_isinf", .{ .name = "isinf" } }, + .{ "__builtin_isnan", .{ .name = "isnan" } }, + .{ "__builtin_labs", .{ .name = "labs" } }, + .{ "__builtin_llabs", .{ .name = "llabs" } }, + .{ "__builtin_log10f", .{ .name = "log10f", .tag = .log10 } }, + .{ "__builtin_log10", .{ .name = "log10", .tag = .log10 } }, + .{ "__builtin_log2f", .{ .name = "log2f", .tag = .log2 } }, + .{ "__builtin_log2", .{ .name = "log2", .tag = .log2 } }, + .{ "__builtin_logf", .{ .name = "logf", .tag = .log } }, + .{ "__builtin_log", .{ .name = "log", .tag = .log } }, + .{ "__builtin___memcpy_chk", .{ .name = "memcpy_chk" } }, + .{ "__builtin_memcpy", .{ .name = "memcpy" } }, + .{ "__builtin___memset_chk", .{ .name = "memset_chk" } }, + .{ "__builtin_memset", .{ .name = "memset" } }, + .{ "__builtin_mul_overflow", .{ .name = "mul_overflow" } }, + .{ "__builtin_nanf", .{ .name = "nanf" } }, + .{ "__builtin_object_size", .{ .name = "object_size" } }, + .{ "__builtin_popcount", .{ .name = "popcount" } }, + .{ "__builtin_roundf", .{ .name = "roundf", .tag = .round } }, + .{ "__builtin_round", .{ .name = "round", .tag = .round } }, + .{ "__builtin_signbitf", .{ .name = "signbitf" } }, + .{ "__builtin_signbit", .{ .name = "signbit" } }, + .{ "__builtin_sinf", .{ .name = "sinf", .tag = .sin } }, + .{ "__builtin_sin", .{ .name = "sin", .tag = .sin } }, + .{ "__builtin_sqrtf", .{ .name = "sqrtf", .tag = .sqrt } }, + .{ "__builtin_sqrt", .{ .name = "sqrt", .tag = .sqrt } }, + .{ "__builtin_strcmp", .{ .name = "strcmp" } }, + .{ "__builtin_strlen", .{ .name = "strlen" } }, + .{ "__builtin_truncf", .{ .name = "truncf", .tag = .trunc } }, + .{ "__builtin_trunc", .{ .name = "trunc", .tag = .trunc } }, + .{ "__builtin_unreachable", .{ .name = "unreachable", .tag = .@"unreachable" } }, + .{ "__has_builtin", .{ .name = "has_builtin" } }, + + // __builtin_alloca_with_align is not currently implemented. + // It is used in a run and a translate test to ensure that non-implemented + // builtins are correctly demoted. If you implement __builtin_alloca_with_align, + // please update the tests to use a different non-implemented builtin. +}); diff --git a/lib/compiler/translate-c/helpers.zig b/lib/compiler/translate-c/helpers.zig new file mode 100644 index 0000000000000000000000000000000000000000..db19cf75b8dedbc4f9643a01d282ffaee8e87759 --- /dev/null +++ b/lib/compiler/translate-c/helpers.zig @@ -0,0 +1,327 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const testing = std.testing; +const math = std.math; + +const helpers = @import("helpers"); + +const cast = helpers.cast; + +test cast { + var i = @as(i64, 10); + + try testing.expect(cast(*u8, 16) == @as(*u8, @ptrFromInt(16))); + try testing.expect(cast(*u64, &i).* == @as(u64, 10)); + try testing.expect(cast(*i64, @as(?*align(1) i64, &i)) == &i); + + try testing.expect(cast(?*u8, 2) == @as(*u8, @ptrFromInt(2))); + try testing.expect(cast(?*i64, @as(*align(1) i64, &i)) == &i); + try testing.expect(cast(?*i64, @as(?*align(1) i64, &i)) == &i); + + try testing.expectEqual(@as(u32, 4), cast(u32, @as(*u32, @ptrFromInt(4)))); + try testing.expectEqual(@as(u32, 4), cast(u32, @as(?*u32, @ptrFromInt(4)))); + try testing.expectEqual(@as(u32, 10), cast(u32, @as(u64, 10))); + + try testing.expectEqual(@as(i32, @bitCast(@as(u32, 0x8000_0000))), cast(i32, @as(u32, 0x8000_0000))); + + try testing.expectEqual(@as(*u8, @ptrFromInt(2)), cast(*u8, @as(*const u8, @ptrFromInt(2)))); + try testing.expectEqual(@as(*u8, @ptrFromInt(2)), cast(*u8, @as(*volatile u8, @ptrFromInt(2)))); + + try testing.expectEqual(@as(?*anyopaque, @ptrFromInt(2)), cast(?*anyopaque, @as(*u8, @ptrFromInt(2)))); + + var foo: c_int = -1; + _ = &foo; + try testing.expect(cast(*anyopaque, -1) == @as(*anyopaque, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1)))))); + try testing.expect(cast(*anyopaque, foo) == @as(*anyopaque, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1)))))); + try testing.expect(cast(?*anyopaque, -1) == @as(?*anyopaque, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1)))))); + try testing.expect(cast(?*anyopaque, foo) == @as(?*anyopaque, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1)))))); + + const FnPtr = ?*align(1) const fn (*anyopaque) void; + try testing.expect(cast(FnPtr, 0) == @as(FnPtr, @ptrFromInt(@as(usize, 0)))); + try testing.expect(cast(FnPtr, foo) == @as(FnPtr, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1)))))); + + const complexFunction = struct { + fn f(_: ?*anyopaque, _: c_uint, _: ?*const fn (?*anyopaque) callconv(.c) c_uint, _: ?*anyopaque, _: c_uint, _: [*c]c_uint) callconv(.c) usize { + return 0; + } + }.f; + + const SDL_FunctionPointer = ?*const fn () callconv(.c) void; + const fn_ptr = cast(SDL_FunctionPointer, complexFunction); + try testing.expect(fn_ptr != null); +} + +const sizeof = helpers.sizeof; + +test sizeof { + const S = extern struct { a: u32 }; + + const ptr_size = @sizeOf(*anyopaque); + + try testing.expect(sizeof(u32) == 4); + try testing.expect(sizeof(@as(u32, 2)) == 4); + try testing.expect(sizeof(2) == @sizeOf(c_int)); + + try testing.expect(sizeof(2.0) == @sizeOf(f64)); + + try testing.expect(sizeof(S) == 4); + + try testing.expect(sizeof([_]u32{ 4, 5, 6 }) == 12); + try testing.expect(sizeof([3]u32) == 12); + try testing.expect(sizeof([3:0]u32) == 16); + try testing.expect(sizeof(&[_]u32{ 4, 5, 6 }) == ptr_size); + + try testing.expect(sizeof(*u32) == ptr_size); + try testing.expect(sizeof([*]u32) == ptr_size); + try testing.expect(sizeof([*c]u32) == ptr_size); + try testing.expect(sizeof(?*u32) == ptr_size); + try testing.expect(sizeof(?[*]u32) == ptr_size); + try testing.expect(sizeof(*anyopaque) == ptr_size); + try testing.expect(sizeof(*void) == ptr_size); + try testing.expect(sizeof(null) == ptr_size); + + try testing.expect(sizeof("foobar") == 7); + try testing.expect(sizeof(&[_:0]u16{ 'f', 'o', 'o', 'b', 'a', 'r' }) == 14); + try testing.expect(sizeof(*const [4:0]u8) == 5); + try testing.expect(sizeof(*[4:0]u8) == ptr_size); + try testing.expect(sizeof([*]const [4:0]u8) == ptr_size); + try testing.expect(sizeof(*const *const [4:0]u8) == ptr_size); + try testing.expect(sizeof(*const [4]u8) == ptr_size); + + if (false) { // TODO + try testing.expect(sizeof(&sizeof) == @sizeOf(@TypeOf(&sizeof))); + try testing.expect(sizeof(sizeof) == 1); + } + + try testing.expect(sizeof(void) == 1); + try testing.expect(sizeof(anyopaque) == 1); +} + +const promoteIntLiteral = helpers.promoteIntLiteral; + +test promoteIntLiteral { + const signed_hex = promoteIntLiteral(c_int, math.maxInt(c_int) + 1, .hex); + try testing.expectEqual(c_uint, @TypeOf(signed_hex)); + + if (math.maxInt(c_longlong) == math.maxInt(c_int)) return; + + const signed_decimal = promoteIntLiteral(c_int, math.maxInt(c_int) + 1, .decimal); + const unsigned = promoteIntLiteral(c_uint, math.maxInt(c_uint) + 1, .hex); + + if (math.maxInt(c_long) > math.maxInt(c_int)) { + try testing.expectEqual(c_long, @TypeOf(signed_decimal)); + try testing.expectEqual(c_ulong, @TypeOf(unsigned)); + } else { + try testing.expectEqual(c_longlong, @TypeOf(signed_decimal)); + try testing.expectEqual(c_ulonglong, @TypeOf(unsigned)); + } +} + +const shuffleVectorIndex = helpers.shuffleVectorIndex; + +test shuffleVectorIndex { + const vector_len: usize = 4; + + _ = shuffleVectorIndex(-1, vector_len); + + try testing.expect(shuffleVectorIndex(0, vector_len) == 0); + try testing.expect(shuffleVectorIndex(1, vector_len) == 1); + try testing.expect(shuffleVectorIndex(2, vector_len) == 2); + try testing.expect(shuffleVectorIndex(3, vector_len) == 3); + + try testing.expect(shuffleVectorIndex(4, vector_len) == -1); + try testing.expect(shuffleVectorIndex(5, vector_len) == -2); + try testing.expect(shuffleVectorIndex(6, vector_len) == -3); + try testing.expect(shuffleVectorIndex(7, vector_len) == -4); +} + +const FlexibleArrayType = helpers.FlexibleArrayType; + +test FlexibleArrayType { + const Container = extern struct { + size: usize, + }; + + try testing.expectEqual(FlexibleArrayType(*Container, c_int), [*c]c_int); + try testing.expectEqual(FlexibleArrayType(*const Container, c_int), [*c]const c_int); + try testing.expectEqual(FlexibleArrayType(*volatile Container, c_int), [*c]volatile c_int); + try testing.expectEqual(FlexibleArrayType(*const volatile Container, c_int), [*c]const volatile c_int); +} + +const signedRemainder = helpers.signedRemainder; + +test signedRemainder { + // TODO add test + return error.SkipZigTest; +} + +const ArithmeticConversion = helpers.ArithmeticConversion; + +test ArithmeticConversion { + // Promotions not necessarily the same for other platforms + if (builtin.target.cpu.arch != .x86_64 or builtin.target.os.tag != .linux) return error.SkipZigTest; + + const Test = struct { + /// Order of operands should not matter for arithmetic conversions + fn checkPromotion(comptime A: type, comptime B: type, comptime Expected: type) !void { + try std.testing.expect(ArithmeticConversion(A, B) == Expected); + try std.testing.expect(ArithmeticConversion(B, A) == Expected); + } + }; + + try Test.checkPromotion(c_longdouble, c_int, c_longdouble); + try Test.checkPromotion(c_int, f64, f64); + try Test.checkPromotion(f32, bool, f32); + + try Test.checkPromotion(bool, c_short, c_int); + try Test.checkPromotion(c_int, c_int, c_int); + try Test.checkPromotion(c_short, c_int, c_int); + + try Test.checkPromotion(c_int, c_long, c_long); + + try Test.checkPromotion(c_ulonglong, c_uint, c_ulonglong); + + try Test.checkPromotion(c_uint, c_int, c_uint); + + try Test.checkPromotion(c_uint, c_long, c_long); + + try Test.checkPromotion(c_ulong, c_longlong, c_ulonglong); + + // stdint.h + try Test.checkPromotion(u8, i8, c_int); + try Test.checkPromotion(u16, i16, c_int); + try Test.checkPromotion(i32, c_int, c_int); + try Test.checkPromotion(u32, c_int, c_uint); + try Test.checkPromotion(i64, c_int, c_long); + try Test.checkPromotion(u64, c_int, c_ulong); + try Test.checkPromotion(isize, c_int, c_long); + try Test.checkPromotion(usize, c_int, c_ulong); +} + +const F_SUFFIX = helpers.F_SUFFIX; + +test F_SUFFIX { + try testing.expect(@TypeOf(F_SUFFIX(1)) == f32); +} + +const U_SUFFIX = helpers.U_SUFFIX; + +test U_SUFFIX { + try testing.expect(@TypeOf(U_SUFFIX(1)) == c_uint); + if (math.maxInt(c_ulong) > math.maxInt(c_uint)) { + try testing.expect(@TypeOf(U_SUFFIX(math.maxInt(c_uint) + 1)) == c_ulong); + } + if (math.maxInt(c_ulonglong) > math.maxInt(c_ulong)) { + try testing.expect(@TypeOf(U_SUFFIX(math.maxInt(c_ulong) + 1)) == c_ulonglong); + } +} + +const L_SUFFIX = helpers.L_SUFFIX; + +test L_SUFFIX { + try testing.expect(@TypeOf(L_SUFFIX(1)) == c_long); + if (math.maxInt(c_long) > math.maxInt(c_int)) { + try testing.expect(@TypeOf(L_SUFFIX(math.maxInt(c_int) + 1)) == c_long); + } + if (math.maxInt(c_longlong) > math.maxInt(c_long)) { + try testing.expect(@TypeOf(L_SUFFIX(math.maxInt(c_long) + 1)) == c_longlong); + } +} +const UL_SUFFIX = helpers.UL_SUFFIX; + +test UL_SUFFIX { + try testing.expect(@TypeOf(UL_SUFFIX(1)) == c_ulong); + if (math.maxInt(c_ulonglong) > math.maxInt(c_ulong)) { + try testing.expect(@TypeOf(UL_SUFFIX(math.maxInt(c_ulong) + 1)) == c_ulonglong); + } +} +const LL_SUFFIX = helpers.LL_SUFFIX; + +test LL_SUFFIX { + try testing.expect(@TypeOf(LL_SUFFIX(1)) == c_longlong); +} +const ULL_SUFFIX = helpers.ULL_SUFFIX; + +test ULL_SUFFIX { + try testing.expect(@TypeOf(ULL_SUFFIX(1)) == c_ulonglong); +} + +test "Extended C ABI casting" { + if (math.maxInt(c_long) > math.maxInt(c_char)) { + try testing.expect(@TypeOf(L_SUFFIX(@as(c_char, math.maxInt(c_char) - 1))) == c_long); // c_char + } + if (math.maxInt(c_long) > math.maxInt(c_short)) { + try testing.expect(@TypeOf(L_SUFFIX(@as(c_short, math.maxInt(c_short) - 1))) == c_long); // c_short + } + + if (math.maxInt(c_long) > math.maxInt(c_ushort)) { + try testing.expect(@TypeOf(L_SUFFIX(@as(c_ushort, math.maxInt(c_ushort) - 1))) == c_long); //c_ushort + } + + if (math.maxInt(c_long) > math.maxInt(c_int)) { + try testing.expect(@TypeOf(L_SUFFIX(@as(c_int, math.maxInt(c_int) - 1))) == c_long); // c_int + } + + if (math.maxInt(c_long) > math.maxInt(c_uint)) { + try testing.expect(@TypeOf(L_SUFFIX(@as(c_uint, math.maxInt(c_uint) - 1))) == c_long); // c_uint + try testing.expect(@TypeOf(L_SUFFIX(math.maxInt(c_uint) + 1)) == c_long); // comptime_int -> c_long + } + + if (math.maxInt(c_longlong) > math.maxInt(c_long)) { + try testing.expect(@TypeOf(L_SUFFIX(@as(c_long, math.maxInt(c_long) - 1))) == c_long); // c_long + try testing.expect(@TypeOf(L_SUFFIX(math.maxInt(c_long) + 1)) == c_longlong); // comptime_int -> c_longlong + } +} + +const WL_CONTAINER_OF = helpers.WL_CONTAINER_OF; + +test WL_CONTAINER_OF { + const S = struct { + a: u32 = 0, + b: u32 = 0, + }; + const x = S{}; + const y = S{}; + const ptr = WL_CONTAINER_OF(&x.b, &y, "b"); + try testing.expectEqual(&x, ptr); +} + +const CAST_OR_CALL = helpers.CAST_OR_CALL; + +test "CAST_OR_CALL casting" { + const arg: c_int = 1000; + const casted = CAST_OR_CALL(u8, arg); + try testing.expectEqual(cast(u8, arg), casted); + + const S = struct { + x: u32 = 0, + }; + var s: S = .{}; + const casted_ptr = CAST_OR_CALL(*u8, &s); + try testing.expectEqual(cast(*u8, &s), casted_ptr); +} + +test "CAST_OR_CALL calling" { + const Helper = struct { + var last_val: bool = false; + fn returnsVoid(val: bool) void { + last_val = val; + } + fn returnsBool(f: f32) bool { + return f > 0; + } + fn identity(self: c_uint) c_uint { + return self; + } + }; + + CAST_OR_CALL(Helper.returnsVoid, true); + try testing.expectEqual(true, Helper.last_val); + CAST_OR_CALL(Helper.returnsVoid, false); + try testing.expectEqual(false, Helper.last_val); + + try testing.expectEqual(Helper.returnsBool(1), CAST_OR_CALL(Helper.returnsBool, @as(f32, 1))); + try testing.expectEqual(Helper.returnsBool(-1), CAST_OR_CALL(Helper.returnsBool, @as(f32, -1))); + + try testing.expectEqual(Helper.identity(@as(c_uint, 100)), CAST_OR_CALL(Helper.identity, @as(c_uint, 100))); +} diff --git a/lib/compiler/translate-c/lib/c_builtins.zig b/lib/compiler/translate-c/lib/c_builtins.zig deleted file mode 100644 index c704b65e23652ec0142e7abd007552f0235b0822..0000000000000000000000000000000000000000 --- a/lib/compiler/translate-c/lib/c_builtins.zig +++ /dev/null @@ -1,301 +0,0 @@ -const std = @import("std"); - -/// Standard C Library bug: The absolute value of the most negative integer remains negative. -pub inline fn abs(val: c_int) c_int { - return if (val == std.math.minInt(c_int)) val else @intCast(@abs(val)); -} - -pub inline fn assume(cond: bool) void { - if (!cond) unreachable; -} - -pub inline fn bswap16(val: u16) u16 { - return @byteSwap(val); -} - -pub inline fn bswap32(val: u32) u32 { - return @byteSwap(val); -} - -pub inline fn bswap64(val: u64) u64 { - return @byteSwap(val); -} - -pub inline fn ceilf(val: f32) f32 { - return @ceil(val); -} - -pub inline fn ceil(val: f64) f64 { - return @ceil(val); -} - -/// Returns the number of leading 0-bits in x, starting at the most significant bit position. -/// In C if `val` is 0, the result is undefined; in zig it's the number of bits in a c_uint -pub inline fn clz(val: c_uint) c_int { - @setRuntimeSafety(false); - return @as(c_int, @bitCast(@as(c_uint, @clz(val)))); -} - -pub inline fn constant_p(expr: anytype) c_int { - _ = expr; - return @intFromBool(false); -} - -pub inline fn cosf(val: f32) f32 { - return @cos(val); -} - -pub inline fn cos(val: f64) f64 { - return @cos(val); -} - -/// Returns the number of trailing 0-bits in val, starting at the least significant bit position. -/// In C if `val` is 0, the result is undefined; in zig it's the number of bits in a c_uint -pub inline fn ctz(val: c_uint) c_int { - @setRuntimeSafety(false); - return @as(c_int, @bitCast(@as(c_uint, @ctz(val)))); -} - -pub inline fn exp2f(val: f32) f32 { - return @exp2(val); -} - -pub inline fn exp2(val: f64) f64 { - return @exp2(val); -} - -pub inline fn expf(val: f32) f32 { - return @exp(val); -} - -pub inline fn exp(val: f64) f64 { - return @exp(val); -} - -/// The return value of __builtin_expect is `expr`. `c` is the expected value -/// of `expr` and is used as a hint to the compiler in C. Here it is unused. -pub inline fn expect(expr: c_long, c: c_long) c_long { - _ = c; - return expr; -} - -pub inline fn fabsf(val: f32) f32 { - return @abs(val); -} - -pub inline fn fabs(val: f64) f64 { - return @abs(val); -} - -pub inline fn floorf(val: f32) f32 { - return @floor(val); -} - -pub inline fn floor(val: f64) f64 { - return @floor(val); -} - -pub inline fn has_builtin(func: anytype) c_int { - _ = func; - return @intFromBool(true); -} - -pub inline fn huge_valf() f32 { - return std.math.inf(f32); -} - -pub inline fn inff() f32 { - return std.math.inf(f32); -} - -/// Similar to isinf, except the return value is -1 for an argument of -Inf and 1 for an argument of +Inf. -pub inline fn isinf_sign(x: anytype) c_int { - if (!std.math.isInf(x)) return 0; - return if (std.math.isPositiveInf(x)) 1 else -1; -} - -pub inline fn isinf(x: anytype) c_int { - return @intFromBool(std.math.isInf(x)); -} - -pub inline fn isnan(x: anytype) c_int { - return @intFromBool(std.math.isNan(x)); -} - -/// Standard C Library bug: The absolute value of the most negative integer remains negative. -pub inline fn labs(val: c_long) c_long { - return if (val == std.math.minInt(c_long)) val else @intCast(@abs(val)); -} - -/// Standard C Library bug: The absolute value of the most negative integer remains negative. -pub inline fn llabs(val: c_longlong) c_longlong { - return if (val == std.math.minInt(c_longlong)) val else @intCast(@abs(val)); -} - -pub inline fn log10f(val: f32) f32 { - return @log10(val); -} - -pub inline fn log10(val: f64) f64 { - return @log10(val); -} - -pub inline fn log2f(val: f32) f32 { - return @log2(val); -} - -pub inline fn log2(val: f64) f64 { - return @log2(val); -} - -pub inline fn logf(val: f32) f32 { - return @log(val); -} - -pub inline fn log(val: f64) f64 { - return @log(val); -} - -pub inline fn memcpy_chk( - noalias dst: ?*anyopaque, - noalias src: ?*const anyopaque, - len: usize, - remaining: usize, -) ?*anyopaque { - if (len > remaining) @panic("__builtin___memcpy_chk called with len > remaining"); - if (len > 0) @memcpy( - @as([*]u8, @ptrCast(dst.?))[0..len], - @as([*]const u8, @ptrCast(src.?)), - ); - return dst; -} - -pub inline fn memcpy( - noalias dst: ?*anyopaque, - noalias src: ?*const anyopaque, - len: usize, -) ?*anyopaque { - if (len > 0) @memcpy( - @as([*]u8, @ptrCast(dst.?))[0..len], - @as([*]const u8, @ptrCast(src.?)), - ); - return dst; -} - -pub inline fn memset_chk( - dst: ?*anyopaque, - val: c_int, - len: usize, - remaining: usize, -) ?*anyopaque { - if (len > remaining) @panic("__builtin___memset_chk called with len > remaining"); - const dst_cast = @as([*c]u8, @ptrCast(dst)); - @memset(dst_cast[0..len], @as(u8, @bitCast(@as(i8, @truncate(val))))); - return dst; -} - -pub inline fn memset(dst: ?*anyopaque, val: c_int, len: usize) ?*anyopaque { - const dst_cast = @as([*c]u8, @ptrCast(dst)); - @memset(dst_cast[0..len], @as(u8, @bitCast(@as(i8, @truncate(val))))); - return dst; -} - -pub fn mul_overflow(a: anytype, b: anytype, result: *@TypeOf(a, b)) c_int { - const res = @mulWithOverflow(a, b); - result.* = res[0]; - return res[1]; -} - -/// returns a quiet NaN. Quiet NaNs have many representations; tagp is used to select one in an -/// implementation-defined way. -/// This implementation is based on the description for nan provided in the GCC docs at -/// https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html#index-_005f_005fbuiltin_005fnan -/// Comment is reproduced below: -/// Since ISO C99 defines this function in terms of strtod, which we do not implement, a description -/// of the parsing is in order. -/// The string is parsed as by strtol; that is, the base is recognized by leading ‘0’ or ‘0x’ prefixes. -/// The number parsed is placed in the significand such that the least significant bit of the number is -/// at the least significant bit of the significand. -/// The number is truncated to fit the significand field provided. -/// The significand is forced to be a quiet NaN. -/// -/// If tagp contains any non-numeric characters, the function returns a NaN whose significand is zero. -/// If tagp is empty, the function returns a NaN whose significand is zero. -pub inline fn nanf(tagp: []const u8) f32 { - const parsed = std.fmt.parseUnsigned(c_ulong, tagp, 0) catch 0; - const bits: u23 = @truncate(parsed); // single-precision float trailing significand is 23 bits - return @bitCast(@as(u32, bits) | @as(u32, @bitCast(std.math.nan(f32)))); -} - -pub inline fn object_size(ptr: ?*const anyopaque, ty: c_int) usize { - _ = ptr; - // clang semantics match gcc's: https://gcc.gnu.org/onlinedocs/gcc/Object-Size-Checking.html - // If it is not possible to determine which objects ptr points to at compile time, - // object_size should return (size_t) -1 for type 0 or 1 and (size_t) 0 - // for type 2 or 3. - if (ty == 0 or ty == 1) return @as(usize, @bitCast(-@as(isize, 1))); - if (ty == 2 or ty == 3) return 0; - unreachable; -} - -/// popcount of a c_uint will never exceed the capacity of a c_int -pub inline fn popcount(val: c_uint) c_int { - @setRuntimeSafety(false); - return @as(c_int, @bitCast(@as(c_uint, @popCount(val)))); -} - -pub inline fn roundf(val: f32) f32 { - return @round(val); -} - -pub inline fn round(val: f64) f64 { - return @round(val); -} - -pub inline fn signbitf(val: f32) c_int { - return @intFromBool(std.math.signbit(val)); -} - -pub inline fn signbit(val: f64) c_int { - return @intFromBool(std.math.signbit(val)); -} - -pub inline fn sinf(val: f32) f32 { - return @sin(val); -} - -pub inline fn sin(val: f64) f64 { - return @sin(val); -} - -pub inline fn sqrtf(val: f32) f32 { - return @sqrt(val); -} - -pub inline fn sqrt(val: f64) f64 { - return @sqrt(val); -} - -pub inline fn strcmp(s1: [*c]const u8, s2: [*c]const u8) c_int { - return switch (std.mem.orderZ(u8, s1, s2)) { - .lt => -1, - .eq => 0, - .gt => 1, - }; -} - -pub inline fn strlen(s: [*c]const u8) usize { - return std.mem.sliceTo(s, 0).len; -} - -pub inline fn truncf(val: f32) f32 { - return @trunc(val); -} - -pub inline fn trunc(val: f64) f64 { - return @trunc(val); -} - -pub inline fn @"unreachable"() noreturn { - unreachable; -} diff --git a/lib/compiler/translate-c/lib/helpers.zig b/lib/compiler/translate-c/lib/helpers.zig deleted file mode 100644 index 0b804bf316be80427a53e3cf04b1beabcca056c8..0000000000000000000000000000000000000000 --- a/lib/compiler/translate-c/lib/helpers.zig +++ /dev/null @@ -1,413 +0,0 @@ -const std = @import("std"); - -/// "Usual arithmetic conversions" from C11 standard 6.3.1.8 -pub fn ArithmeticConversion(comptime A: type, comptime B: type) type { - if (A == c_longdouble or B == c_longdouble) return c_longdouble; - if (A == f80 or B == f80) return f80; - if (A == f64 or B == f64) return f64; - if (A == f32 or B == f32) return f32; - - const A_Promoted = PromotedIntType(A); - const B_Promoted = PromotedIntType(B); - comptime { - std.debug.assert(integerRank(A_Promoted) >= integerRank(c_int)); - std.debug.assert(integerRank(B_Promoted) >= integerRank(c_int)); - } - - if (A_Promoted == B_Promoted) return A_Promoted; - - const a_signed = @typeInfo(A_Promoted).int.signedness == .signed; - const b_signed = @typeInfo(B_Promoted).int.signedness == .signed; - - if (a_signed == b_signed) { - return if (integerRank(A_Promoted) > integerRank(B_Promoted)) A_Promoted else B_Promoted; - } - - const SignedType = if (a_signed) A_Promoted else B_Promoted; - const UnsignedType = if (!a_signed) A_Promoted else B_Promoted; - - if (integerRank(UnsignedType) >= integerRank(SignedType)) return UnsignedType; - - if (std.math.maxInt(SignedType) >= std.math.maxInt(UnsignedType)) return SignedType; - - return ToUnsigned(SignedType); -} - -/// Integer promotion described in C11 6.3.1.1.2 -fn PromotedIntType(comptime T: type) type { - return switch (T) { - bool, c_short => c_int, - c_ushort => if (@sizeOf(c_ushort) == @sizeOf(c_int)) c_uint else c_int, - c_int, c_uint, c_long, c_ulong, c_longlong, c_ulonglong => T, - else => switch (@typeInfo(T)) { - .comptime_int => @compileError("Cannot promote `" ++ @typeName(T) ++ "`; a fixed-size number type is required"), - // promote to c_int if it can represent all values of T - .int => |int_info| if (int_info.bits < @bitSizeOf(c_int)) - c_int - // otherwise, restore the original C type - else if (int_info.bits == @bitSizeOf(c_int)) - if (int_info.signedness == .unsigned) c_uint else c_int - else if (int_info.bits <= @bitSizeOf(c_long)) - if (int_info.signedness == .unsigned) c_ulong else c_long - else if (int_info.bits <= @bitSizeOf(c_longlong)) - if (int_info.signedness == .unsigned) c_ulonglong else c_longlong - else - @compileError("Cannot promote `" ++ @typeName(T) ++ "`; a C ABI type is required"), - else => @compileError("Attempted to promote invalid type `" ++ @typeName(T) ++ "`"), - }, - }; -} - -/// C11 6.3.1.1.1 -fn integerRank(comptime T: type) u8 { - return switch (T) { - bool => 0, - u8, i8 => 1, - c_short, c_ushort => 2, - c_int, c_uint => 3, - c_long, c_ulong => 4, - c_longlong, c_ulonglong => 5, - else => @compileError("integer rank not supported for `" ++ @typeName(T) ++ "`"), - }; -} - -fn ToUnsigned(comptime T: type) type { - return switch (T) { - c_int => c_uint, - c_long => c_ulong, - c_longlong => c_ulonglong, - else => @compileError("Cannot convert `" ++ @typeName(T) ++ "` to unsigned"), - }; -} - -/// Constructs a [*c] pointer with the const and volatile annotations -/// from SelfType for pointing to a C flexible array of ElementType. -pub fn FlexibleArrayType(comptime SelfType: type, comptime ElementType: type) type { - switch (@typeInfo(SelfType)) { - .pointer => |ptr| { - return @Type(.{ .pointer = .{ - .size = .c, - .is_const = ptr.is_const, - .is_volatile = ptr.is_volatile, - .alignment = @alignOf(ElementType), - .address_space = .generic, - .child = ElementType, - .is_allowzero = true, - .sentinel_ptr = null, - } }); - }, - else => |info| @compileError("Invalid self type \"" ++ @tagName(info) ++ "\" for flexible array getter: " ++ @typeName(SelfType)), - } -} - -/// Promote the type of an integer literal until it fits as C would. -pub fn promoteIntLiteral( - comptime SuffixType: type, - comptime number: comptime_int, - comptime base: CIntLiteralBase, -) PromoteIntLiteralReturnType(SuffixType, number, base) { - return number; -} - -const CIntLiteralBase = enum { decimal, octal, hex }; - -fn PromoteIntLiteralReturnType(comptime SuffixType: type, comptime number: comptime_int, comptime base: CIntLiteralBase) type { - const signed_decimal = [_]type{ c_int, c_long, c_longlong, c_ulonglong }; - const signed_oct_hex = [_]type{ c_int, c_uint, c_long, c_ulong, c_longlong, c_ulonglong }; - const unsigned = [_]type{ c_uint, c_ulong, c_ulonglong }; - - const list: []const type = if (@typeInfo(SuffixType).int.signedness == .unsigned) - &unsigned - else if (base == .decimal) - &signed_decimal - else - &signed_oct_hex; - - var pos = std.mem.indexOfScalar(type, list, SuffixType).?; - while (pos < list.len) : (pos += 1) { - if (number >= std.math.minInt(list[pos]) and number <= std.math.maxInt(list[pos])) { - return list[pos]; - } - } - - @compileError("Integer literal is too large"); -} - -/// Convert from clang __builtin_shufflevector index to Zig @shuffle index -/// clang requires __builtin_shufflevector index arguments to be integer constants. -/// negative values for `this_index` indicate "don't care". -/// clang enforces that `this_index` is less than the total number of vector elements -/// See https://ziglang.org/documentation/master/#shuffle -/// See https://clang.llvm.org/docs/LanguageExtensions.html#langext-builtin-shufflevector -pub fn shuffleVectorIndex(comptime this_index: c_int, comptime source_vector_len: usize) i32 { - const positive_index = std.math.cast(usize, this_index) orelse return undefined; - if (positive_index < source_vector_len) return @as(i32, @intCast(this_index)); - const b_index = positive_index - source_vector_len; - return ~@as(i32, @intCast(b_index)); -} - -/// C `%` operator for signed integers -/// C standard states: "If the quotient a/b is representable, the expression (a/b)*b + a%b shall equal a" -/// The quotient is not representable if denominator is zero, or if numerator is the minimum integer for -/// the type and denominator is -1. C has undefined behavior for those two cases; this function has safety -/// checked undefined behavior -pub fn signedRemainder(numerator: anytype, denominator: anytype) @TypeOf(numerator, denominator) { - std.debug.assert(@typeInfo(@TypeOf(numerator, denominator)).int.signedness == .signed); - if (denominator > 0) return @rem(numerator, denominator); - return numerator - @divTrunc(numerator, denominator) * denominator; -} - -/// Given a type and value, cast the value to the type as c would. -pub fn cast(comptime DestType: type, target: anytype) DestType { - // this function should behave like transCCast in translate-c, except it's for macros - const SourceType = @TypeOf(target); - switch (@typeInfo(DestType)) { - .@"fn" => return castToPtr(*const DestType, SourceType, target), - .pointer => return castToPtr(DestType, SourceType, target), - .optional => |dest_opt| { - if (@typeInfo(dest_opt.child) == .pointer) { - return castToPtr(DestType, SourceType, target); - } else if (@typeInfo(dest_opt.child) == .@"fn") { - return castToPtr(?*const dest_opt.child, SourceType, target); - } - }, - .int => { - switch (@typeInfo(SourceType)) { - .pointer => { - return castInt(DestType, @intFromPtr(target)); - }, - .optional => |opt| { - if (@typeInfo(opt.child) == .pointer) { - return castInt(DestType, @intFromPtr(target)); - } - }, - .int => { - return castInt(DestType, target); - }, - .@"fn" => { - return castInt(DestType, @intFromPtr(&target)); - }, - .bool => { - return @intFromBool(target); - }, - else => {}, - } - }, - .float => { - switch (@typeInfo(SourceType)) { - .int => return @as(DestType, @floatFromInt(target)), - .float => return @as(DestType, @floatCast(target)), - .bool => return @as(DestType, @floatFromInt(@intFromBool(target))), - else => {}, - } - }, - .@"union" => |info| { - inline for (info.fields) |field| { - if (field.type == SourceType) return @unionInit(DestType, field.name, target); - } - - @compileError("cast to union type '" ++ @typeName(DestType) ++ "' from type '" ++ @typeName(SourceType) ++ "' which is not present in union"); - }, - .bool => return cast(usize, target) != 0, - else => {}, - } - - return @as(DestType, target); -} - -fn castInt(comptime DestType: type, target: anytype) DestType { - const dest = @typeInfo(DestType).int; - const source = @typeInfo(@TypeOf(target)).int; - - const Int = @Type(.{ .int = .{ .bits = dest.bits, .signedness = source.signedness } }); - - if (dest.bits < source.bits) - return @as(DestType, @bitCast(@as(Int, @truncate(target)))) - else - return @as(DestType, @bitCast(@as(Int, target))); -} - -fn castPtr(comptime DestType: type, target: anytype) DestType { - return @constCast(@volatileCast(@alignCast(@ptrCast(target)))); -} - -fn castToPtr(comptime DestType: type, comptime SourceType: type, target: anytype) DestType { - switch (@typeInfo(SourceType)) { - .int => { - return @as(DestType, @ptrFromInt(castInt(usize, target))); - }, - .comptime_int => { - if (target < 0) - return @as(DestType, @ptrFromInt(@as(usize, @bitCast(@as(isize, @intCast(target)))))) - else - return @as(DestType, @ptrFromInt(@as(usize, @intCast(target)))); - }, - .pointer => { - return castPtr(DestType, target); - }, - .@"fn" => { - return castPtr(DestType, &target); - }, - .optional => |target_opt| { - if (@typeInfo(target_opt.child) == .pointer) { - return castPtr(DestType, target); - } - }, - else => {}, - } - - return @as(DestType, target); -} - -/// Given a value returns its size as C's sizeof operator would. -pub fn sizeof(target: anytype) usize { - const T: type = if (@TypeOf(target) == type) target else @TypeOf(target); - switch (@typeInfo(T)) { - .float, .int, .@"struct", .@"union", .array, .bool, .vector => return @sizeOf(T), - .@"fn" => { - // sizeof(main) in C returns 1 - return 1; - }, - .null => return @sizeOf(*anyopaque), - .void => { - // Note: sizeof(void) is 1 on clang/gcc and 0 on MSVC. - return 1; - }, - .@"opaque" => { - if (T == anyopaque) { - // Note: sizeof(void) is 1 on clang/gcc and 0 on MSVC. - return 1; - } else { - @compileError("Cannot use C sizeof on opaque type " ++ @typeName(T)); - } - }, - .optional => |opt| { - if (@typeInfo(opt.child) == .pointer) { - return sizeof(opt.child); - } else { - @compileError("Cannot use C sizeof on non-pointer optional " ++ @typeName(T)); - } - }, - .pointer => |ptr| { - if (ptr.size == .slice) { - @compileError("Cannot use C sizeof on slice type " ++ @typeName(T)); - } - - // for strings, sizeof("a") returns 2. - // normal pointer decay scenarios from C are handled - // in the .array case above, but strings remain literals - // and are therefore always pointers, so they need to be - // specially handled here. - if (ptr.size == .one and ptr.is_const and @typeInfo(ptr.child) == .array) { - const array_info = @typeInfo(ptr.child).array; - if ((array_info.child == u8 or array_info.child == u16) and array_info.sentinel() == 0) { - // length of the string plus one for the null terminator. - return (array_info.len + 1) * @sizeOf(array_info.child); - } - } - - // When zero sized pointers are removed, this case will no - // longer be reachable and can be deleted. - if (@sizeOf(T) == 0) { - return @sizeOf(*anyopaque); - } - - return @sizeOf(T); - }, - .comptime_float => return @sizeOf(f64), // TODO c_double #3999 - .comptime_int => { - // TODO to get the correct result we have to translate - // `1073741824 * 4` as `int(1073741824) *% int(4)` since - // sizeof(1073741824 * 4) != sizeof(4294967296). - - // TODO test if target fits in int, long or long long - return @sizeOf(c_int); - }, - else => @compileError("__helpers.sizeof does not support type " ++ @typeName(T)), - } -} - -pub fn div(a: anytype, b: anytype) ArithmeticConversion(@TypeOf(a), @TypeOf(b)) { - const ResType = ArithmeticConversion(@TypeOf(a), @TypeOf(b)); - const a_casted = cast(ResType, a); - const b_casted = cast(ResType, b); - switch (@typeInfo(ResType)) { - .float => return a_casted / b_casted, - .int => return @divTrunc(a_casted, b_casted), - else => unreachable, - } -} - -pub fn rem(a: anytype, b: anytype) ArithmeticConversion(@TypeOf(a), @TypeOf(b)) { - const ResType = ArithmeticConversion(@TypeOf(a), @TypeOf(b)); - const a_casted = cast(ResType, a); - const b_casted = cast(ResType, b); - switch (@typeInfo(ResType)) { - .int => { - if (@typeInfo(ResType).int.signedness == .signed) { - return signedRemainder(a_casted, b_casted); - } else { - return a_casted % b_casted; - } - }, - else => unreachable, - } -} - -/// A 2-argument function-like macro defined as #define FOO(A, B) (A)(B) -/// could be either: cast B to A, or call A with the value B. -pub fn CAST_OR_CALL(a: anytype, b: anytype) switch (@typeInfo(@TypeOf(a))) { - .type => a, - .@"fn" => |fn_info| fn_info.return_type orelse void, - else => |info| @compileError("Unexpected argument type: " ++ @tagName(info)), -} { - switch (@typeInfo(@TypeOf(a))) { - .type => return cast(a, b), - .@"fn" => return a(b), - else => unreachable, // return type will be a compile error otherwise - } -} - -pub inline fn DISCARD(x: anytype) void { - _ = x; -} - -pub fn F_SUFFIX(comptime f: comptime_float) f32 { - return @as(f32, f); -} - -fn L_SUFFIX_ReturnType(comptime number: anytype) type { - switch (@typeInfo(@TypeOf(number))) { - .int, .comptime_int => return @TypeOf(promoteIntLiteral(c_long, number, .decimal)), - .float, .comptime_float => return c_longdouble, - else => @compileError("Invalid value for L suffix"), - } -} - -pub fn L_SUFFIX(comptime number: anytype) L_SUFFIX_ReturnType(number) { - switch (@typeInfo(@TypeOf(number))) { - .int, .comptime_int => return promoteIntLiteral(c_long, number, .decimal), - .float, .comptime_float => @compileError("TODO: c_longdouble initialization from comptime_float not supported"), - else => @compileError("Invalid value for L suffix"), - } -} - -pub fn LL_SUFFIX(comptime n: comptime_int) @TypeOf(promoteIntLiteral(c_longlong, n, .decimal)) { - return promoteIntLiteral(c_longlong, n, .decimal); -} - -pub fn U_SUFFIX(comptime n: comptime_int) @TypeOf(promoteIntLiteral(c_uint, n, .decimal)) { - return promoteIntLiteral(c_uint, n, .decimal); -} - -pub fn UL_SUFFIX(comptime n: comptime_int) @TypeOf(promoteIntLiteral(c_ulong, n, .decimal)) { - return promoteIntLiteral(c_ulong, n, .decimal); -} - -pub fn ULL_SUFFIX(comptime n: comptime_int) @TypeOf(promoteIntLiteral(c_ulonglong, n, .decimal)) { - return promoteIntLiteral(c_ulonglong, n, .decimal); -} - -pub fn WL_CONTAINER_OF(ptr: anytype, sample: anytype, comptime member: []const u8) @TypeOf(sample) { - return @fieldParentPtr(member, ptr); -} diff --git a/lib/compiler/translate-c/main.zig b/lib/compiler/translate-c/main.zig new file mode 100644 index 0000000000000000000000000000000000000000..b3848096666dff5c443c674be5c1d6f27acdec7d --- /dev/null +++ b/lib/compiler/translate-c/main.zig @@ -0,0 +1,251 @@ +const std = @import("std"); +const assert = std.debug.assert; +const mem = std.mem; +const process = std.process; +const aro = @import("aro"); +const Translator = @import("Translator.zig"); + +const fast_exit = @import("builtin").mode != .Debug; + +var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init; + +pub fn main() u8 { + const gpa = general_purpose_allocator.allocator(); + defer _ = general_purpose_allocator.deinit(); + + var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer arena_instance.deinit(); + const arena = arena_instance.allocator(); + + const args = process.argsAlloc(arena) catch { + std.debug.print("ran out of memory allocating arguments\n", .{}); + if (fast_exit) process.exit(1); + return 1; + }; + + var stderr_buf: [1024]u8 = undefined; + var stderr = std.fs.File.stderr().writer(&stderr_buf); + var diagnostics: aro.Diagnostics = .{ + .output = .{ .to_writer = .{ + .color = .detect(stderr.file), + .writer = &stderr.interface, + } }, + }; + + var comp = aro.Compilation.initDefault(gpa, arena, &diagnostics, std.fs.cwd()) catch |err| switch (err) { + error.OutOfMemory => { + std.debug.print("ran out of memory initializing C compilation\n", .{}); + if (fast_exit) process.exit(1); + return 1; + }, + }; + defer comp.deinit(); + + const exe_name = std.fs.selfExePathAlloc(gpa) catch { + std.debug.print("unable to find translate-c executable path\n", .{}); + if (fast_exit) process.exit(1); + return 1; + }; + defer gpa.free(exe_name); + + var driver: aro.Driver = .{ .comp = &comp, .diagnostics = &diagnostics, .aro_name = exe_name }; + defer driver.deinit(); + + var toolchain: aro.Toolchain = .{ .driver = &driver, .filesystem = .{ .real = comp.cwd } }; + defer toolchain.deinit(); + + translate(&driver, &toolchain, args) catch |err| switch (err) { + error.OutOfMemory => { + std.debug.print("ran out of memory translating\n", .{}); + if (fast_exit) process.exit(1); + return 1; + }, + error.FatalError => { + if (fast_exit) process.exit(1); + return 1; + }, + error.WriteFailed => { + std.debug.print("unable to write to stdout\n", .{}); + if (fast_exit) process.exit(1); + return 1; + }, + }; + if (fast_exit) process.exit(@intFromBool(comp.diagnostics.errors != 0)); + return @intFromBool(comp.diagnostics.errors != 0); +} + +pub const usage = + \\Usage {s}: [options] file [CC options] + \\ + \\Options: + \\ --help Print this message + \\ --version Print translate-c version + \\ -fmodule-libs Import libraries as modules + \\ -fno-module-libs (default) Install libraries next to output file + \\ + \\ +; + +fn translate(d: *aro.Driver, tc: *aro.Toolchain, args: [][:0]u8) !void { + const gpa = d.comp.gpa; + + var module_libs = false; + + const aro_args = args: { + var i: usize = 0; + for (args) |arg| { + args[i] = arg; + if (mem.eql(u8, arg, "--help")) { + var stdout_buf: [512]u8 = undefined; + var stdout = std.fs.File.stdout().writer(&stdout_buf); + try stdout.interface.print(usage, .{args[0]}); + try stdout.interface.flush(); + return; + } else if (mem.eql(u8, arg, "--version")) { + var stdout_buf: [512]u8 = undefined; + var stdout = std.fs.File.stdout().writer(&stdout_buf); + // TODO add version + try stdout.interface.writeAll("0.0.0-dev\n"); + try stdout.interface.flush(); + return; + } else if (mem.eql(u8, arg, "-fmodule-libs")) { + module_libs = true; + } else if (mem.eql(u8, arg, "-fno-module-libs")) { + module_libs = false; + } else { + i += 1; + } + } + break :args args[0..i]; + }; + const user_macros = macros: { + var macro_buf: std.ArrayListUnmanaged(u8) = .empty; + defer macro_buf.deinit(gpa); + + try macro_buf.appendSlice(gpa, "#define __TRANSLATE_C__ 1\n"); + + var discard_buf: [256]u8 = undefined; + var discarding: std.io.Writer.Discarding = .init(&discard_buf); + assert(!try d.parseArgs(&discarding.writer, ¯o_buf, aro_args)); + if (macro_buf.items.len > std.math.maxInt(u32)) { + return d.fatal("user provided macro source exceeded max size", .{}); + } + + const content = try macro_buf.toOwnedSlice(gpa); + errdefer gpa.free(content); + + break :macros try d.comp.addSourceFromOwnedBuffer("", content, .user); + }; + + if (d.inputs.items.len != 1) { + return d.fatal("expected exactly one input file", .{}); + } + const source = d.inputs.items[0]; + + tc.discover() catch |er| switch (er) { + error.OutOfMemory => return error.OutOfMemory, + error.TooManyMultilibs => return d.fatal("found more than one multilib with the same priority", .{}), + }; + tc.defineSystemIncludes() catch |er| switch (er) { + error.OutOfMemory => return error.OutOfMemory, + error.AroIncludeNotFound => return d.fatal("unable to find Aro builtin headers", .{}), + }; + + const builtin_macros = d.comp.generateBuiltinMacros(.include_system_defines) catch |err| switch (err) { + error.FileTooBig => return d.fatal("builtin macro source exceeded max size", .{}), + else => |e| return e, + }; + + var pp = try aro.Preprocessor.initDefault(d.comp); + defer pp.deinit(); + + try pp.preprocessSources(&.{ source, builtin_macros, user_macros }); + + var c_tree = try pp.parse(); + defer c_tree.deinit(); + + if (d.diagnostics.errors != 0) { + if (fast_exit) process.exit(1); + return error.FatalError; + } + + const rendered_zig = try Translator.translate(.{ + .gpa = gpa, + .comp = d.comp, + .pp = &pp, + .tree = &c_tree, + .module_libs = module_libs, + }); + defer gpa.free(rendered_zig); + + var close_out_file = false; + var out_file_path: []const u8 = ""; + var out_file: std.fs.File = .stdout(); + defer if (close_out_file) out_file.close(); + + if (d.output_name) |path| blk: { + if (std.mem.eql(u8, path, "-")) break :blk; + if (std.fs.path.dirname(path)) |dirname| { + std.fs.cwd().makePath(dirname) catch |err| + return d.fatal("failed to create path to '{s}': {s}", .{ path, aro.Driver.errorDescription(err) }); + } + out_file = std.fs.cwd().createFile(path, .{}) catch |err| { + return d.fatal("failed to create output file '{s}': {s}", .{ path, aro.Driver.errorDescription(err) }); + }; + close_out_file = true; + out_file_path = path; + } + + var out_buf: [4096]u8 = undefined; + var out_writer = out_file.writer(&out_buf); + out_writer.interface.writeAll(rendered_zig) catch + return d.fatal("failed to write result to '{s}': {s}", .{ out_file_path, aro.Driver.errorDescription(out_writer.err.?) }); + + if (!module_libs) { + const dest_path = if (d.output_name) |path| std.fs.path.dirname(path) else null; + installLibs(d, dest_path) catch |err| + return d.fatal("failed to install library files: {s}", .{aro.Driver.errorDescription(err)}); + } + + if (fast_exit) process.exit(0); +} + +fn installLibs(d: *aro.Driver, dest_path: ?[]const u8) !void { + const gpa = d.comp.gpa; + const cwd = std.fs.cwd(); + + const self_exe_path = try std.fs.selfExePathAlloc(gpa); + defer gpa.free(self_exe_path); + + var cur_dir: []const u8 = self_exe_path; + while (std.fs.path.dirname(cur_dir)) |dirname| : (cur_dir = dirname) { + var base_dir = cwd.openDir(dirname, .{}) catch continue; + defer base_dir.close(); + + var lib_dir = base_dir.openDir("lib", .{}) catch continue; + defer lib_dir.close(); + + lib_dir.access("c_builtins.zig", .{}) catch continue; + + { + const install_path = try std.fs.path.join(gpa, &.{ dest_path orelse "", "c_builtins.zig" }); + defer gpa.free(install_path); + try lib_dir.copyFile("c_builtins.zig", cwd, install_path, .{}); + } + { + const install_path = try std.fs.path.join(gpa, &.{ dest_path orelse "", "helpers.zig" }); + defer gpa.free(install_path); + try lib_dir.copyFile("helpers.zig", cwd, install_path, .{}); + } + return; + } + return error.FileNotFound; +} + +comptime { + if (@import("builtin").is_test) { + _ = Translator; + _ = @import("helpers.zig"); + _ = @import("PatternList.zig"); + } +} diff --git a/lib/compiler/translate-c/src/MacroTranslator.zig b/lib/compiler/translate-c/src/MacroTranslator.zig deleted file mode 100644 index 2d1824d8a57c234a71197bb057383d8af9cc1594..0000000000000000000000000000000000000000 --- a/lib/compiler/translate-c/src/MacroTranslator.zig +++ /dev/null @@ -1,1307 +0,0 @@ -const std = @import("std"); -const math = std.math; -const mem = std.mem; -const assert = std.debug.assert; - -const aro = @import("aro"); -const CToken = aro.Tokenizer.Token; - -const ast = @import("ast.zig"); -const builtins = @import("builtins.zig"); -const ZigNode = ast.Node; -const ZigTag = ZigNode.Tag; -const Scope = @import("Scope.zig"); -const Translator = @import("Translator.zig"); - -const Error = Translator.Error; -pub const ParseError = Error || error{ParseError}; - -const MacroTranslator = @This(); - -t: *Translator, -macro: aro.Preprocessor.Macro, -name: []const u8, - -tokens: []const CToken, -source: []const u8, -i: usize = 0, -/// If an object macro references a global var it needs to be converted into -/// an inline function. -refs_var_decl: bool = false, - -fn peek(mt: *MacroTranslator) CToken.Id { - if (mt.i >= mt.tokens.len) return .eof; - return mt.tokens[mt.i].id; -} - -fn eat(mt: *MacroTranslator, expected_id: CToken.Id) bool { - if (mt.peek() == expected_id) { - mt.i += 1; - return true; - } - return false; -} - -fn expect(mt: *MacroTranslator, expected_id: CToken.Id) ParseError!void { - const next_id = mt.peek(); - if (next_id != expected_id and !(expected_id == .identifier and next_id == .extended_identifier)) { - try mt.fail( - "unable to translate C expr: expected '{s}' instead got '{s}'", - .{ expected_id.symbol(), next_id.symbol() }, - ); - return error.ParseError; - } - mt.i += 1; -} - -fn fail(mt: *MacroTranslator, comptime fmt: []const u8, args: anytype) !void { - return mt.t.failDeclExtra(&mt.t.global_scope.base, mt.macro.loc, mt.name, fmt, args); -} - -fn tokSlice(mt: *const MacroTranslator) []const u8 { - const tok = mt.tokens[mt.i]; - return mt.source[tok.start..tok.end]; -} - -pub fn transFnMacro(mt: *MacroTranslator) ParseError!void { - var block_scope = try Scope.Block.init(mt.t, &mt.t.global_scope.base, false); - defer block_scope.deinit(); - const scope = &block_scope.base; - - const fn_params = try mt.t.arena.alloc(ast.Payload.Param, mt.macro.params.len); - for (fn_params, mt.macro.params) |*param, param_name| { - const mangled_name = try block_scope.makeMangledName(param_name); - param.* = .{ - .is_noalias = false, - .name = mangled_name, - .type = ZigTag.@"anytype".init(), - }; - try block_scope.discardVariable(mangled_name); - } - - const expr = try mt.parseCExpr(scope); - const last = mt.peek(); - if (last != .eof) - return mt.fail("unable to translate C expr: unexpected token '{s}'", .{last.symbol()}); - - const typeof_arg = if (expr.castTag(.block)) |some| blk: { - const stmts = some.data.stmts; - const blk_last = stmts[stmts.len - 1]; - const br = blk_last.castTag(.break_val).?; - break :blk br.data.val; - } else expr; - - const return_type = ret: { - if (typeof_arg.castTag(.helper_call)) |some| { - if (std.mem.eql(u8, some.data.name, "cast")) { - break :ret some.data.args[0]; - } - } - if (typeof_arg.castTag(.std_mem_zeroinit)) |some| break :ret some.data.lhs; - if (typeof_arg.castTag(.std_mem_zeroes)) |some| break :ret some.data; - break :ret try ZigTag.typeof.create(mt.t.arena, typeof_arg); - }; - - const return_expr = try ZigTag.@"return".create(mt.t.arena, expr); - try block_scope.statements.append(mt.t.gpa, return_expr); - - const fn_decl = try ZigTag.pub_inline_fn.create(mt.t.arena, .{ - .name = mt.name, - .params = fn_params, - .return_type = return_type, - .body = try block_scope.complete(), - }); - try mt.t.addTopLevelDecl(mt.name, fn_decl); -} - -pub fn transMacro(mt: *MacroTranslator) ParseError!void { - const scope = &mt.t.global_scope.base; - - // Check if the macro only uses other blank macros. - while (true) { - switch (mt.peek()) { - .identifier, .extended_identifier => { - if (mt.t.global_scope.blank_macros.contains(mt.tokSlice())) { - mt.i += 1; - continue; - } - }, - .eof, .nl => { - try mt.t.global_scope.blank_macros.put(mt.t.gpa, mt.name, {}); - const init_node = try ZigTag.string_literal.create(mt.t.arena, "\"\""); - const var_decl = try ZigTag.pub_var_simple.create(mt.t.arena, .{ .name = mt.name, .init = init_node }); - try mt.t.addTopLevelDecl(mt.name, var_decl); - return; - }, - else => {}, - } - break; - } - - const init_node = try mt.parseCExpr(scope); - const last = mt.peek(); - if (last != .eof) - return mt.fail("unable to translate C expr: unexpected token '{s}'", .{last.symbol()}); - - const node = node: { - const var_decl = try ZigTag.pub_var_simple.create(mt.t.arena, .{ .name = mt.name, .init = init_node }); - - if (mt.t.getFnProto(var_decl)) |proto_node| { - // If a macro aliases a global variable which is a function pointer, we conclude that - // the macro is intended to represent a function that assumes the function pointer - // variable is non-null and calls it. - break :node try mt.createMacroFn(mt.name, var_decl, proto_node); - } else if (mt.refs_var_decl) { - const return_type = try ZigTag.typeof.create(mt.t.arena, init_node); - const return_expr = try ZigTag.@"return".create(mt.t.arena, init_node); - const block = try ZigTag.block_single.create(mt.t.arena, return_expr); - - const loc_str = try mt.t.locStr(mt.macro.loc); - const value = try std.fmt.allocPrint(mt.t.arena, "\n// {s}: warning: macro '{s}' contains a runtime value, translated to function", .{ loc_str, mt.name }); - try scope.appendNode(try ZigTag.warning.create(mt.t.arena, value)); - - break :node try ZigTag.pub_inline_fn.create(mt.t.arena, .{ - .name = mt.name, - .params = &.{}, - .return_type = return_type, - .body = block, - }); - } - - break :node var_decl; - }; - - try mt.t.addTopLevelDecl(mt.name, node); -} - -fn createMacroFn(mt: *MacroTranslator, name: []const u8, ref: ZigNode, proto_alias: *ast.Payload.Func) !ZigNode { - var fn_params = std.ArrayList(ast.Payload.Param).init(mt.t.gpa); - defer fn_params.deinit(); - - var block_scope = try Scope.Block.init(mt.t, &mt.t.global_scope.base, false); - defer block_scope.deinit(); - - for (proto_alias.data.params) |param| { - const param_name = try block_scope.makeMangledName(param.name orelse "arg"); - - try fn_params.append(.{ - .name = param_name, - .type = param.type, - .is_noalias = param.is_noalias, - }); - } - - const init = if (ref.castTag(.var_decl)) |v| - v.data.init.? - else if (ref.castTag(.var_simple) orelse ref.castTag(.pub_var_simple)) |v| - v.data.init - else - unreachable; - - const unwrap_expr = try ZigTag.unwrap.create(mt.t.arena, init); - const args = try mt.t.arena.alloc(ZigNode, fn_params.items.len); - for (fn_params.items, 0..) |param, i| { - args[i] = try ZigTag.identifier.create(mt.t.arena, param.name.?); - } - const call_expr = try ZigTag.call.create(mt.t.arena, .{ - .lhs = unwrap_expr, - .args = args, - }); - const return_expr = try ZigTag.@"return".create(mt.t.arena, call_expr); - const block = try ZigTag.block_single.create(mt.t.arena, return_expr); - - return ZigTag.pub_inline_fn.create(mt.t.arena, .{ - .name = name, - .params = try mt.t.arena.dupe(ast.Payload.Param, fn_params.items), - .return_type = proto_alias.data.return_type, - .body = block, - }); -} - -fn parseCExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { - // TODO parseCAssignExpr here - var block_scope = try Scope.Block.init(mt.t, scope, true); - defer block_scope.deinit(); - - const node = try mt.parseCCondExpr(&block_scope.base); - if (!mt.eat(.comma)) return node; - - var last = node; - while (true) { - // suppress result - const ignore = try ZigTag.discard.create(mt.t.arena, .{ .should_skip = false, .value = last }); - try block_scope.statements.append(mt.t.gpa, ignore); - - last = try mt.parseCCondExpr(&block_scope.base); - if (!mt.eat(.comma)) break; - } - - const break_node = try ZigTag.break_val.create(mt.t.arena, .{ - .label = block_scope.label, - .val = last, - }); - try block_scope.statements.append(mt.t.gpa, break_node); - return try block_scope.complete(); -} - -fn parseCNumLit(mt: *MacroTranslator) ParseError!ZigNode { - const lit_bytes = mt.tokSlice(); - mt.i += 1; - - var bytes = try std.ArrayListUnmanaged(u8).initCapacity(mt.t.arena, lit_bytes.len + 3); - - const prefix = aro.Tree.Token.NumberPrefix.fromString(lit_bytes); - switch (prefix) { - .binary => bytes.appendSliceAssumeCapacity("0b"), - .octal => bytes.appendSliceAssumeCapacity("0o"), - .hex => bytes.appendSliceAssumeCapacity("0x"), - .decimal => {}, - } - - const after_prefix = lit_bytes[prefix.stringLen()..]; - const after_int = for (after_prefix, 0..) |c, i| switch (c) { - '.' => { - if (i == 0) { - bytes.appendAssumeCapacity('0'); - } - break after_prefix[i..]; - }, - 'e', 'E' => { - if (prefix != .hex) break after_prefix[i..]; - bytes.appendAssumeCapacity(c); - }, - 'p', 'P' => break after_prefix[i..], - '0'...'9', 'a'...'d', 'A'...'D', 'f', 'F' => { - if (!prefix.digitAllowed(c)) break after_prefix[i..]; - bytes.appendAssumeCapacity(c); - }, - '\'' => { - bytes.appendAssumeCapacity('_'); - }, - else => break after_prefix[i..], - } else ""; - - const after_frac = frac: { - if (after_int.len == 0 or after_int[0] != '.') break :frac after_int; - bytes.appendAssumeCapacity('.'); - for (after_int[1..], 1..) |c, i| { - if (c == '\'') { - bytes.appendAssumeCapacity('_'); - continue; - } - if (!prefix.digitAllowed(c)) break :frac after_int[i..]; - bytes.appendAssumeCapacity(c); - } - break :frac ""; - }; - - const suffix_str = exponent: { - if (after_frac.len == 0) break :exponent after_frac; - switch (after_frac[0]) { - 'e', 'E' => {}, - 'p', 'P' => if (prefix != .hex) break :exponent after_frac, - else => break :exponent after_frac, - } - bytes.appendAssumeCapacity(after_frac[0]); - for (after_frac[1..], 1..) |c, i| switch (c) { - '+', '-', '0'...'9' => { - bytes.appendAssumeCapacity(c); - }, - '\'' => { - bytes.appendAssumeCapacity('_'); - }, - else => break :exponent after_frac[i..], - }; - break :exponent ""; - }; - - const is_float = after_int.len != suffix_str.len; - const suffix = aro.Tree.Token.NumberSuffix.fromString(suffix_str, if (is_float) .float else .int) orelse { - try mt.fail("invalid number suffix: '{s}'", .{suffix_str}); - return error.ParseError; - }; - if (suffix.isImaginary()) { - try mt.fail("TODO: imaginary literals", .{}); - return error.ParseError; - } - if (suffix.isBitInt()) { - try mt.fail("TODO: _BitInt literals", .{}); - return error.ParseError; - } - - if (is_float) { - const type_node = try ZigTag.type.create(mt.t.arena, switch (suffix) { - .F16 => "f16", - .F => "f32", - .None => "f64", - .L => "c_longdouble", - .W => "f80", - .Q, .F128 => "f128", - else => unreachable, - }); - const rhs = try ZigTag.float_literal.create(mt.t.arena, bytes.items); - return ZigTag.as.create(mt.t.arena, .{ .lhs = type_node, .rhs = rhs }); - } else { - const type_node = try ZigTag.type.create(mt.t.arena, switch (suffix) { - .None => "c_int", - .U => "c_uint", - .L => "c_long", - .UL => "c_ulong", - .LL => "c_longlong", - .ULL => "c_ulonglong", - else => unreachable, - }); - const value = std.fmt.parseInt(i128, bytes.items, 0) catch math.maxInt(i128); - - // make the output less noisy by skipping promoteIntLiteral where - // it's guaranteed to not be required because of C standard type constraints - const guaranteed_to_fit = switch (suffix) { - .None => math.cast(i16, value) != null, - .U => math.cast(u16, value) != null, - .L => math.cast(i32, value) != null, - .UL => math.cast(u32, value) != null, - .LL => math.cast(i64, value) != null, - .ULL => math.cast(u64, value) != null, - else => unreachable, - }; - - const literal_node = try ZigTag.integer_literal.create(mt.t.arena, bytes.items); - if (guaranteed_to_fit) { - return ZigTag.as.create(mt.t.arena, .{ .lhs = type_node, .rhs = literal_node }); - } else { - return mt.t.createHelperCallNode(.promoteIntLiteral, &.{ type_node, literal_node, try ZigTag.enum_literal.create(mt.t.arena, @tagName(prefix)) }); - } - } -} - -fn zigifyEscapeSequences(mt: *MacroTranslator, slice: []const u8) ![]const u8 { - var source = slice; - for (source, 0..) |c, i| { - if (c == '\"' or c == '\'') { - source = source[i..]; - break; - } - } - for (source) |c| { - if (c == '\\' or c == '\t') { - break; - } - } else return source; - const bytes = try mt.t.arena.alloc(u8, source.len * 2); - var state: enum { - start, - escape, - hex, - octal, - } = .start; - var i: usize = 0; - var count: u8 = 0; - var num: u8 = 0; - for (source) |c| { - switch (state) { - .escape => { - switch (c) { - 'n', 'r', 't', '\\', '\'', '\"' => { - bytes[i] = c; - }, - '0'...'7' => { - count += 1; - num += c - '0'; - state = .octal; - bytes[i] = 'x'; - }, - 'x' => { - state = .hex; - bytes[i] = 'x'; - }, - 'a' => { - bytes[i] = 'x'; - i += 1; - bytes[i] = '0'; - i += 1; - bytes[i] = '7'; - }, - 'b' => { - bytes[i] = 'x'; - i += 1; - bytes[i] = '0'; - i += 1; - bytes[i] = '8'; - }, - 'f' => { - bytes[i] = 'x'; - i += 1; - bytes[i] = '0'; - i += 1; - bytes[i] = 'C'; - }, - 'v' => { - bytes[i] = 'x'; - i += 1; - bytes[i] = '0'; - i += 1; - bytes[i] = 'B'; - }, - '?' => { - i -= 1; - bytes[i] = '?'; - }, - 'u', 'U' => { - try mt.fail("macro tokenizing failed: TODO unicode escape sequences", .{}); - return error.ParseError; - }, - else => { - try mt.fail("macro tokenizing failed: unknown escape sequence", .{}); - return error.ParseError; - }, - } - i += 1; - if (state == .escape) - state = .start; - }, - .start => { - if (c == '\t') { - bytes[i] = '\\'; - i += 1; - bytes[i] = 't'; - i += 1; - continue; - } - if (c == '\\') { - state = .escape; - } - bytes[i] = c; - i += 1; - }, - .hex => { - switch (c) { - '0'...'9' => { - num = std.math.mul(u8, num, 16) catch { - try mt.fail("macro tokenizing failed: hex literal overflowed", .{}); - return error.ParseError; - }; - num += c - '0'; - }, - 'a'...'f' => { - num = std.math.mul(u8, num, 16) catch { - try mt.fail("macro tokenizing failed: hex literal overflowed", .{}); - return error.ParseError; - }; - num += c - 'a' + 10; - }, - 'A'...'F' => { - num = std.math.mul(u8, num, 16) catch { - try mt.fail("macro tokenizing failed: hex literal overflowed", .{}); - return error.ParseError; - }; - num += c - 'A' + 10; - }, - else => { - i += std.fmt.printInt(bytes[i..], num, 16, .lower, .{ .fill = '0', .width = 2 }); - num = 0; - if (c == '\\') - state = .escape - else - state = .start; - bytes[i] = c; - i += 1; - }, - } - }, - .octal => { - const accept_digit = switch (c) { - // The maximum length of a octal literal is 3 digits - '0'...'7' => count < 3, - else => false, - }; - - if (accept_digit) { - count += 1; - num = std.math.mul(u8, num, 8) catch { - try mt.fail("macro tokenizing failed: octal literal overflowed", .{}); - return error.ParseError; - }; - num += c - '0'; - } else { - i += std.fmt.printInt(bytes[i..], num, 16, .lower, .{ .fill = '0', .width = 2 }); - num = 0; - count = 0; - if (c == '\\') - state = .escape - else - state = .start; - bytes[i] = c; - i += 1; - } - }, - } - } - if (state == .hex or state == .octal) { - i += std.fmt.printInt(bytes[i..], num, 16, .lower, .{ .fill = '0', .width = 2 }); - } - - return bytes[0..i]; -} - -/// non-ASCII characters (mt > 127) are also treated as non-printable by fmtSliceEscapeLower. -/// If a C string literal or char literal in a macro is not valid UTF-8, we need to escape -/// non-ASCII characters so that the Zig source we output will itself be UTF-8. -fn escapeUnprintables(mt: *MacroTranslator) ![]const u8 { - const slice = mt.tokSlice(); - mt.i += 1; - - const zigified = try mt.zigifyEscapeSequences(slice); - if (std.unicode.utf8ValidateSlice(zigified)) return zigified; - - const formatter = std.ascii.hexEscape(zigified, .lower); - const encoded_size = @as(usize, @intCast(std.fmt.count("{f}", .{formatter}))); - const output = try mt.t.arena.alloc(u8, encoded_size); - return std.fmt.bufPrint(output, "{f}", .{formatter}) catch |err| switch (err) { - error.NoSpaceLeft => unreachable, - else => |e| return e, - }; -} - -fn parseCPrimaryExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { - const tok = mt.peek(); - switch (tok) { - .char_literal, - .char_literal_utf_8, - .char_literal_utf_16, - .char_literal_utf_32, - .char_literal_wide, - => { - const slice = mt.tokSlice(); - if (slice[0] != '\'' or slice[1] == '\\' or slice.len == 3) { - return ZigTag.char_literal.create(mt.t.arena, try mt.escapeUnprintables()); - } else { - mt.i += 1; - - const str = try std.fmt.allocPrint(mt.t.arena, "0x{x}", .{slice[1 .. slice.len - 1]}); - return ZigTag.integer_literal.create(mt.t.arena, str); - } - }, - .string_literal, - .string_literal_utf_16, - .string_literal_utf_8, - .string_literal_utf_32, - .string_literal_wide, - => return ZigTag.string_literal.create(mt.t.arena, try mt.escapeUnprintables()), - .pp_num => return mt.parseCNumLit(), - .l_paren => { - mt.i += 1; - const inner_node = try mt.parseCExpr(scope); - - try mt.expect(.r_paren); - return inner_node; - }, - .macro_param, .macro_param_no_expand => { - const param = mt.macro.params[mt.tokens[mt.i].end]; - mt.i += 1; - - const mangled_name = scope.getAlias(param) orelse param; - return try ZigTag.identifier.create(mt.t.arena, mangled_name); - }, - .identifier, .extended_identifier => { - const slice = mt.tokSlice(); - mt.i += 1; - - const mangled_name = scope.getAlias(slice) orelse slice; - if (Translator.builtin_typedef_map.get(mangled_name)) |ty| { - return ZigTag.type.create(mt.t.arena, ty); - } - if (builtins.map.get(mangled_name)) |builtin| { - const builtin_identifier = try ZigTag.identifier.create(mt.t.arena, "__builtin"); - return ZigTag.field_access.create(mt.t.arena, .{ - .lhs = builtin_identifier, - .field_name = builtin.name, - }); - } - - const identifier = try ZigTag.identifier.create(mt.t.arena, mangled_name); - scope.skipVariableDiscard(mangled_name); - refs_var: { - const ident_node = mt.t.global_scope.sym_table.get(slice) orelse break :refs_var; - const var_decl_node = ident_node.castTag(.var_decl) orelse break :refs_var; - if (!var_decl_node.data.is_const) mt.refs_var_decl = true; - } - return identifier; - }, - else => {}, - } - - // for handling type macros (EVIL) - // TODO maybe detect and treat type macros as typedefs in parseCSpecifierQualifierList? - if (try mt.parseCTypeName(scope, true)) |type_name| { - return type_name; - } - - try mt.fail("unable to translate C expr: unexpected token '{s}'", .{tok.symbol()}); - return error.ParseError; -} - -fn macroIntFromBool(mt: *MacroTranslator, node: ZigNode) !ZigNode { - if (!node.isBoolRes()) return node; - - return ZigTag.int_from_bool.create(mt.t.arena, node); -} - -fn macroIntToBool(mt: *MacroTranslator, node: ZigNode) !ZigNode { - if (node.isBoolRes()) return node; - - if (node.tag() == .string_literal) { - // @intFromPtr(node) != 0 - const int_from_ptr = try ZigTag.int_from_ptr.create(mt.t.arena, node); - return ZigTag.not_equal.create(mt.t.arena, .{ .lhs = int_from_ptr, .rhs = ZigTag.zero_literal.init() }); - } - // node != 0 - return ZigTag.not_equal.create(mt.t.arena, .{ .lhs = node, .rhs = ZigTag.zero_literal.init() }); -} - -fn parseCCondExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { - const node = try mt.parseCOrExpr(scope); - if (!mt.eat(.question_mark)) return node; - - const then_body = try mt.parseCOrExpr(scope); - try mt.expect(.colon); - const else_body = try mt.parseCCondExpr(scope); - return ZigTag.@"if".create(mt.t.arena, .{ .cond = node, .then = then_body, .@"else" = else_body }); -} - -fn parseCOrExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { - var node = try mt.parseCAndExpr(scope); - while (mt.eat(.pipe_pipe)) { - const lhs = try mt.macroIntToBool(node); - const rhs = try mt.macroIntToBool(try mt.parseCAndExpr(scope)); - node = try ZigTag.@"or".create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); - } - return node; -} - -fn parseCAndExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { - var node = try mt.parseCBitOrExpr(scope); - while (mt.eat(.ampersand_ampersand)) { - const lhs = try mt.macroIntToBool(node); - const rhs = try mt.macroIntToBool(try mt.parseCBitOrExpr(scope)); - node = try ZigTag.@"and".create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); - } - return node; -} - -fn parseCBitOrExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { - var node = try mt.parseCBitXorExpr(scope); - while (mt.eat(.pipe)) { - const lhs = try mt.macroIntFromBool(node); - const rhs = try mt.macroIntFromBool(try mt.parseCBitXorExpr(scope)); - node = try ZigTag.bit_or.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); - } - return node; -} - -fn parseCBitXorExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { - var node = try mt.parseCBitAndExpr(scope); - while (mt.eat(.caret)) { - const lhs = try mt.macroIntFromBool(node); - const rhs = try mt.macroIntFromBool(try mt.parseCBitAndExpr(scope)); - node = try ZigTag.bit_xor.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); - } - return node; -} - -fn parseCBitAndExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { - var node = try mt.parseCEqExpr(scope); - while (mt.eat(.ampersand)) { - const lhs = try mt.macroIntFromBool(node); - const rhs = try mt.macroIntFromBool(try mt.parseCEqExpr(scope)); - node = try ZigTag.bit_and.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); - } - return node; -} - -fn parseCEqExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { - var node = try mt.parseCRelExpr(scope); - while (true) { - switch (mt.peek()) { - .bang_equal => { - mt.i += 1; - const lhs = try mt.macroIntFromBool(node); - const rhs = try mt.macroIntFromBool(try mt.parseCRelExpr(scope)); - node = try ZigTag.not_equal.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); - }, - .equal_equal => { - mt.i += 1; - const lhs = try mt.macroIntFromBool(node); - const rhs = try mt.macroIntFromBool(try mt.parseCRelExpr(scope)); - node = try ZigTag.equal.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); - }, - else => return node, - } - } -} - -fn parseCRelExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { - var node = try mt.parseCShiftExpr(scope); - while (true) { - switch (mt.peek()) { - .angle_bracket_right => { - mt.i += 1; - const lhs = try mt.macroIntFromBool(node); - const rhs = try mt.macroIntFromBool(try mt.parseCShiftExpr(scope)); - node = try ZigTag.greater_than.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); - }, - .angle_bracket_right_equal => { - mt.i += 1; - const lhs = try mt.macroIntFromBool(node); - const rhs = try mt.macroIntFromBool(try mt.parseCShiftExpr(scope)); - node = try ZigTag.greater_than_equal.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); - }, - .angle_bracket_left => { - mt.i += 1; - const lhs = try mt.macroIntFromBool(node); - const rhs = try mt.macroIntFromBool(try mt.parseCShiftExpr(scope)); - node = try ZigTag.less_than.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); - }, - .angle_bracket_left_equal => { - mt.i += 1; - const lhs = try mt.macroIntFromBool(node); - const rhs = try mt.macroIntFromBool(try mt.parseCShiftExpr(scope)); - node = try ZigTag.less_than_equal.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); - }, - else => return node, - } - } -} - -fn parseCShiftExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { - var node = try mt.parseCAddSubExpr(scope); - while (true) { - switch (mt.peek()) { - .angle_bracket_angle_bracket_left => { - mt.i += 1; - const lhs = try mt.macroIntFromBool(node); - const rhs = try mt.macroIntFromBool(try mt.parseCAddSubExpr(scope)); - node = try ZigTag.shl.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); - }, - .angle_bracket_angle_bracket_right => { - mt.i += 1; - const lhs = try mt.macroIntFromBool(node); - const rhs = try mt.macroIntFromBool(try mt.parseCAddSubExpr(scope)); - node = try ZigTag.shr.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); - }, - else => return node, - } - } -} - -fn parseCAddSubExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { - var node = try mt.parseCMulExpr(scope); - while (true) { - switch (mt.peek()) { - .plus => { - mt.i += 1; - const lhs = try mt.macroIntFromBool(node); - const rhs = try mt.macroIntFromBool(try mt.parseCMulExpr(scope)); - node = try ZigTag.add.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); - }, - .minus => { - mt.i += 1; - const lhs = try mt.macroIntFromBool(node); - const rhs = try mt.macroIntFromBool(try mt.parseCMulExpr(scope)); - node = try ZigTag.sub.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); - }, - else => return node, - } - } -} - -fn parseCMulExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { - var node = try mt.parseCCastExpr(scope); - while (true) { - switch (mt.peek()) { - .asterisk => { - mt.i += 1; - const lhs = try mt.macroIntFromBool(node); - const rhs = try mt.macroIntFromBool(try mt.parseCCastExpr(scope)); - node = try ZigTag.mul.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); - }, - .slash => { - mt.i += 1; - const lhs = try mt.macroIntFromBool(node); - const rhs = try mt.macroIntFromBool(try mt.parseCCastExpr(scope)); - node = try mt.t.createHelperCallNode(.div, &.{ lhs, rhs }); - }, - .percent => { - mt.i += 1; - const lhs = try mt.macroIntFromBool(node); - const rhs = try mt.macroIntFromBool(try mt.parseCCastExpr(scope)); - node = try mt.t.createHelperCallNode(.rem, &.{ lhs, rhs }); - }, - else => return node, - } - } -} - -fn parseCCastExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { - if (mt.eat(.l_paren)) { - if (try mt.parseCTypeName(scope, true)) |type_name| { - while (true) { - const next_tok = mt.peek(); - if (next_tok == .r_paren) { - mt.i += 1; - break; - } - // Skip trailing blank defined before the RParen. - if ((next_tok == .identifier or next_tok == .extended_identifier) and - mt.t.global_scope.blank_macros.contains(mt.tokSlice())) - { - mt.i += 1; - continue; - } - - try mt.fail( - "unable to translate C expr: expected ')' instead got '{s}'", - .{next_tok.symbol()}, - ); - return error.ParseError; - } - if (mt.peek() == .l_brace) { - // initializer list - return mt.parseCPostfixExpr(scope, type_name); - } - const node_to_cast = try mt.parseCCastExpr(scope); - return mt.t.createHelperCallNode(.cast, &.{ type_name, node_to_cast }); - } - mt.i -= 1; // l_paren - } - return mt.parseCUnaryExpr(scope); -} - -// allow_fail is set when unsure if we are parsing a type-name -fn parseCTypeName(mt: *MacroTranslator, scope: *Scope, allow_fail: bool) ParseError!?ZigNode { - if (try mt.parseCSpecifierQualifierList(scope, allow_fail)) |node| { - return try mt.parseCAbstractDeclarator(node); - } - return null; -} - -fn parseCSpecifierQualifierList(mt: *MacroTranslator, scope: *Scope, allow_fail: bool) ParseError!?ZigNode { - const tok = mt.peek(); - switch (tok) { - .macro_param, .macro_param_no_expand => { - const param = mt.macro.params[mt.tokens[mt.i].end]; - - // Assume that this is only a cast if the next token is ')' - // e.g. param)identifier - if (allow_fail and (mt.macro.tokens.len < mt.i + 3 or - mt.macro.tokens[mt.i + 1].id != .r_paren or - mt.macro.tokens[mt.i + 2].id != .identifier)) - return null; - - mt.i += 1; - const mangled_name = scope.getAlias(param) orelse param; - return try ZigTag.identifier.create(mt.t.arena, mangled_name); - }, - .identifier, .extended_identifier => { - const slice = mt.tokSlice(); - const mangled_name = scope.getAlias(slice) orelse slice; - - if (mt.t.global_scope.blank_macros.contains(slice)) { - mt.i += 1; - return try mt.parseCSpecifierQualifierList(scope, allow_fail); - } - - if (!allow_fail or mt.t.typedefs.contains(mangled_name)) { - mt.i += 1; - if (Translator.builtin_typedef_map.get(mangled_name)) |ty| { - return try ZigTag.type.create(mt.t.arena, ty); - } - if (builtins.map.get(mangled_name)) |builtin| { - const builtin_identifier = try ZigTag.identifier.create(mt.t.arena, "__builtin"); - return try ZigTag.field_access.create(mt.t.arena, .{ - .lhs = builtin_identifier, - .field_name = builtin.name, - }); - } - - return try ZigTag.identifier.create(mt.t.arena, mangled_name); - } - }, - .keyword_void => { - mt.i += 1; - return try ZigTag.type.create(mt.t.arena, "anyopaque"); - }, - .keyword_bool => { - mt.i += 1; - return try ZigTag.type.create(mt.t.arena, "bool"); - }, - .keyword_char, - .keyword_int, - .keyword_short, - .keyword_long, - .keyword_float, - .keyword_double, - .keyword_signed, - .keyword_unsigned, - .keyword_complex, - => return try mt.parseCNumericType(), - .keyword_enum, .keyword_struct, .keyword_union => { - const tag_name = mt.tokSlice(); - mt.i += 1; - - // struct Foo will be declared as struct_Foo by transRecordDecl - const identifier = mt.tokSlice(); - try mt.expect(.identifier); - - const name = try std.fmt.allocPrint(mt.t.arena, "{s}_{s}", .{ tag_name, identifier }); - return try ZigTag.identifier.create(mt.t.arena, name); - }, - else => {}, - } - - if (allow_fail) return null; - - try mt.fail("unable to translate C expr: unexpected token '{s}'", .{tok.symbol()}); - return error.ParseError; -} - -fn parseCNumericType(mt: *MacroTranslator) ParseError!ZigNode { - const KwCounter = struct { - double: u8 = 0, - long: u8 = 0, - int: u8 = 0, - float: u8 = 0, - short: u8 = 0, - char: u8 = 0, - unsigned: u8 = 0, - signed: u8 = 0, - complex: u8 = 0, - - fn eql(self: @This(), other: @This()) bool { - return std.meta.eql(self, other); - } - }; - - // Yes, these can be in *any* order - // This still doesn't cover cases where for example volatile is intermixed - - var kw = KwCounter{}; - // prevent overflow - var i: u8 = 0; - while (i < math.maxInt(u8)) : (i += 1) { - switch (mt.peek()) { - .keyword_double => kw.double += 1, - .keyword_long => kw.long += 1, - .keyword_int => kw.int += 1, - .keyword_float => kw.float += 1, - .keyword_short => kw.short += 1, - .keyword_char => kw.char += 1, - .keyword_unsigned => kw.unsigned += 1, - .keyword_signed => kw.signed += 1, - .keyword_complex => kw.complex += 1, - else => break, - } - mt.i += 1; - } - - if (kw.eql(.{ .int = 1 }) or kw.eql(.{ .signed = 1 }) or kw.eql(.{ .signed = 1, .int = 1 })) - return ZigTag.type.create(mt.t.arena, "c_int"); - - if (kw.eql(.{ .unsigned = 1 }) or kw.eql(.{ .unsigned = 1, .int = 1 })) - return ZigTag.type.create(mt.t.arena, "c_uint"); - - if (kw.eql(.{ .long = 1 }) or kw.eql(.{ .signed = 1, .long = 1 }) or kw.eql(.{ .long = 1, .int = 1 }) or kw.eql(.{ .signed = 1, .long = 1, .int = 1 })) - return ZigTag.type.create(mt.t.arena, "c_long"); - - if (kw.eql(.{ .unsigned = 1, .long = 1 }) or kw.eql(.{ .unsigned = 1, .long = 1, .int = 1 })) - return ZigTag.type.create(mt.t.arena, "c_ulong"); - - if (kw.eql(.{ .long = 2 }) or kw.eql(.{ .signed = 1, .long = 2 }) or kw.eql(.{ .long = 2, .int = 1 }) or kw.eql(.{ .signed = 1, .long = 2, .int = 1 })) - return ZigTag.type.create(mt.t.arena, "c_longlong"); - - if (kw.eql(.{ .unsigned = 1, .long = 2 }) or kw.eql(.{ .unsigned = 1, .long = 2, .int = 1 })) - return ZigTag.type.create(mt.t.arena, "c_ulonglong"); - - if (kw.eql(.{ .signed = 1, .char = 1 })) - return ZigTag.type.create(mt.t.arena, "i8"); - - if (kw.eql(.{ .char = 1 }) or kw.eql(.{ .unsigned = 1, .char = 1 })) - return ZigTag.type.create(mt.t.arena, "u8"); - - if (kw.eql(.{ .short = 1 }) or kw.eql(.{ .signed = 1, .short = 1 }) or kw.eql(.{ .short = 1, .int = 1 }) or kw.eql(.{ .signed = 1, .short = 1, .int = 1 })) - return ZigTag.type.create(mt.t.arena, "c_short"); - - if (kw.eql(.{ .unsigned = 1, .short = 1 }) or kw.eql(.{ .unsigned = 1, .short = 1, .int = 1 })) - return ZigTag.type.create(mt.t.arena, "c_ushort"); - - if (kw.eql(.{ .float = 1 })) - return ZigTag.type.create(mt.t.arena, "f32"); - - if (kw.eql(.{ .double = 1 })) - return ZigTag.type.create(mt.t.arena, "f64"); - - if (kw.eql(.{ .long = 1, .double = 1 })) { - try mt.fail("unable to translate: TODO long double", .{}); - return error.ParseError; - } - - if (kw.eql(.{ .float = 1, .complex = 1 })) { - try mt.fail("unable to translate: TODO _Complex", .{}); - return error.ParseError; - } - - if (kw.eql(.{ .double = 1, .complex = 1 })) { - try mt.fail("unable to translate: TODO _Complex", .{}); - return error.ParseError; - } - - if (kw.eql(.{ .long = 1, .double = 1, .complex = 1 })) { - try mt.fail("unable to translate: TODO _Complex", .{}); - return error.ParseError; - } - - try mt.fail("unable to translate: invalid numeric type", .{}); - return error.ParseError; -} - -fn parseCAbstractDeclarator(mt: *MacroTranslator, node: ZigNode) ParseError!ZigNode { - if (mt.eat(.asterisk)) { - if (node.castTag(.type)) |some| { - if (std.mem.eql(u8, some.data, "anyopaque")) { - const ptr = try ZigTag.single_pointer.create(mt.t.arena, .{ - .is_const = false, - .is_volatile = false, - .is_allowzero = false, - .elem_type = node, - }); - return ZigTag.optional_type.create(mt.t.arena, ptr); - } - } - return ZigTag.c_pointer.create(mt.t.arena, .{ - .is_const = false, - .is_volatile = false, - .is_allowzero = false, - .elem_type = node, - }); - } - return node; -} - -fn parseCPostfixExpr(mt: *MacroTranslator, scope: *Scope, type_name: ?ZigNode) ParseError!ZigNode { - var node = try mt.parseCPostfixExprInner(scope, type_name); - // In C the preprocessor would handle concatting strings while expanding macros. - // This should do approximately the same by concatting any strings and identifiers - // after a primary or postfix expression. - while (true) { - switch (mt.peek()) { - .string_literal, - .string_literal_utf_16, - .string_literal_utf_8, - .string_literal_utf_32, - .string_literal_wide, - => {}, - .identifier, .extended_identifier => { - if (mt.t.global_scope.blank_macros.contains(mt.tokSlice())) { - mt.i += 1; - continue; - } - }, - else => break, - } - const rhs = try mt.parseCPostfixExprInner(scope, type_name); - node = try ZigTag.array_cat.create(mt.t.arena, .{ .lhs = node, .rhs = rhs }); - } - return node; -} - -fn parseCPostfixExprInner(mt: *MacroTranslator, scope: *Scope, type_name: ?ZigNode) ParseError!ZigNode { - var node = type_name orelse try mt.parseCPrimaryExpr(scope); - while (true) { - switch (mt.peek()) { - .period => { - mt.i += 1; - const field_name = mt.tokSlice(); - try mt.expect(.identifier); - - node = try ZigTag.field_access.create(mt.t.arena, .{ .lhs = node, .field_name = field_name }); - }, - .arrow => { - mt.i += 1; - const field_name = mt.tokSlice(); - try mt.expect(.identifier); - - const deref = try ZigTag.deref.create(mt.t.arena, node); - node = try ZigTag.field_access.create(mt.t.arena, .{ .lhs = deref, .field_name = field_name }); - }, - .l_bracket => { - mt.i += 1; - - const index_val = try mt.macroIntFromBool(try mt.parseCExpr(scope)); - const index = try ZigTag.as.create(mt.t.arena, .{ - .lhs = try ZigTag.type.create(mt.t.arena, "usize"), - .rhs = try ZigTag.int_cast.create(mt.t.arena, index_val), - }); - node = try ZigTag.array_access.create(mt.t.arena, .{ .lhs = node, .rhs = index }); - try mt.expect(.r_bracket); - }, - .l_paren => { - mt.i += 1; - - if (mt.eat(.r_paren)) { - node = try ZigTag.call.create(mt.t.arena, .{ .lhs = node, .args = &.{} }); - } else { - var args = std.ArrayList(ZigNode).init(mt.t.gpa); - defer args.deinit(); - - while (true) { - const arg = try mt.parseCCondExpr(scope); - try args.append(arg); - - const next_id = mt.peek(); - switch (next_id) { - .comma => { - mt.i += 1; - }, - .r_paren => { - mt.i += 1; - break; - }, - else => { - try mt.fail("unable to translate C expr: expected ',' or ')' instead got '{s}'", .{next_id.symbol()}); - return error.ParseError; - }, - } - } - node = try ZigTag.call.create(mt.t.arena, .{ .lhs = node, .args = try mt.t.arena.dupe(ZigNode, args.items) }); - } - }, - .l_brace => { - mt.i += 1; - - // Check for designated field initializers - if (mt.peek() == .period) { - var init_vals = std.ArrayList(ast.Payload.ContainerInitDot.Initializer).init(mt.t.gpa); - defer init_vals.deinit(); - - while (true) { - try mt.expect(.period); - const name = mt.tokSlice(); - try mt.expect(.identifier); - try mt.expect(.equal); - - const val = try mt.parseCCondExpr(scope); - try init_vals.append(.{ .name = name, .value = val }); - - const next_id = mt.peek(); - switch (next_id) { - .comma => { - mt.i += 1; - }, - .r_brace => { - mt.i += 1; - break; - }, - else => { - try mt.fail("unable to translate C expr: expected ',' or '}}' instead got '{s}'", .{next_id.symbol()}); - return error.ParseError; - }, - } - } - const tuple_node = try ZigTag.container_init_dot.create(mt.t.arena, try mt.t.arena.dupe(ast.Payload.ContainerInitDot.Initializer, init_vals.items)); - node = try ZigTag.std_mem_zeroinit.create(mt.t.arena, .{ .lhs = node, .rhs = tuple_node }); - continue; - } - - var init_vals = std.ArrayList(ZigNode).init(mt.t.gpa); - defer init_vals.deinit(); - - while (true) { - const val = try mt.parseCCondExpr(scope); - try init_vals.append(val); - - const next_id = mt.peek(); - switch (next_id) { - .comma => { - mt.i += 1; - }, - .r_brace => { - mt.i += 1; - break; - }, - else => { - try mt.fail("unable to translate C expr: expected ',' or '}}' instead got '{s}'", .{next_id.symbol()}); - return error.ParseError; - }, - } - } - const tuple_node = try ZigTag.tuple.create(mt.t.arena, try mt.t.arena.dupe(ZigNode, init_vals.items)); - node = try ZigTag.std_mem_zeroinit.create(mt.t.arena, .{ .lhs = node, .rhs = tuple_node }); - }, - .plus_plus, .minus_minus => { - try mt.fail("TODO postfix inc/dec expr", .{}); - return error.ParseError; - }, - else => return node, - } - } -} - -fn parseCUnaryExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { - switch (mt.peek()) { - .bang => { - mt.i += 1; - const operand = try mt.macroIntToBool(try mt.parseCCastExpr(scope)); - return ZigTag.not.create(mt.t.arena, operand); - }, - .minus => { - mt.i += 1; - const operand = try mt.macroIntFromBool(try mt.parseCCastExpr(scope)); - return ZigTag.negate.create(mt.t.arena, operand); - }, - .plus => { - mt.i += 1; - return try mt.parseCCastExpr(scope); - }, - .tilde => { - mt.i += 1; - const operand = try mt.macroIntFromBool(try mt.parseCCastExpr(scope)); - return ZigTag.bit_not.create(mt.t.arena, operand); - }, - .asterisk => { - mt.i += 1; - const operand = try mt.parseCCastExpr(scope); - return ZigTag.deref.create(mt.t.arena, operand); - }, - .ampersand => { - mt.i += 1; - const operand = try mt.parseCCastExpr(scope); - return ZigTag.address_of.create(mt.t.arena, operand); - }, - .keyword_sizeof => { - mt.i += 1; - const operand = if (mt.eat(.l_paren)) blk: { - const inner = (try mt.parseCTypeName(scope, false)).?; - try mt.expect(.r_paren); - break :blk inner; - } else try mt.parseCUnaryExpr(scope); - - return mt.t.createHelperCallNode(.sizeof, &.{operand}); - }, - .keyword_alignof => { - mt.i += 1; - // TODO this won't work if using 's - // #define alignof _Alignof - try mt.expect(.l_paren); - const operand = (try mt.parseCTypeName(scope, false)).?; - try mt.expect(.r_paren); - - return ZigTag.alignof.create(mt.t.arena, operand); - }, - .plus_plus, .minus_minus => { - try mt.fail("TODO unary inc/dec expr", .{}); - return error.ParseError; - }, - else => {}, - } - - return try mt.parseCPostfixExpr(scope, null); -} diff --git a/lib/compiler/translate-c/src/PatternList.zig b/lib/compiler/translate-c/src/PatternList.zig deleted file mode 100644 index 6d8ee4ed9c2140a49b551def73633265931e60e4..0000000000000000000000000000000000000000 --- a/lib/compiler/translate-c/src/PatternList.zig +++ /dev/null @@ -1,288 +0,0 @@ -const std = @import("std"); -const mem = std.mem; -const assert = std.debug.assert; - -const aro = @import("aro"); -const CToken = aro.Tokenizer.Token; - -const helpers = @import("helpers.zig"); -const Translator = @import("Translator.zig"); -const Error = Translator.Error; -pub const MacroProcessingError = Error || error{UnexpectedMacroToken}; - -const Impl = std.meta.DeclEnum(@import("helpers")); -const Template = struct { []const u8, Impl }; - -/// Templates must be function-like macros -/// first element is macro source, second element is the name of the function -/// in __helpers which implements it -const templates = [_]Template{ - .{ "f_SUFFIX(X) (X ## f)", .F_SUFFIX }, - .{ "F_SUFFIX(X) (X ## F)", .F_SUFFIX }, - - .{ "u_SUFFIX(X) (X ## u)", .U_SUFFIX }, - .{ "U_SUFFIX(X) (X ## U)", .U_SUFFIX }, - - .{ "l_SUFFIX(X) (X ## l)", .L_SUFFIX }, - .{ "L_SUFFIX(X) (X ## L)", .L_SUFFIX }, - - .{ "ul_SUFFIX(X) (X ## ul)", .UL_SUFFIX }, - .{ "uL_SUFFIX(X) (X ## uL)", .UL_SUFFIX }, - .{ "Ul_SUFFIX(X) (X ## Ul)", .UL_SUFFIX }, - .{ "UL_SUFFIX(X) (X ## UL)", .UL_SUFFIX }, - - .{ "ll_SUFFIX(X) (X ## ll)", .LL_SUFFIX }, - .{ "LL_SUFFIX(X) (X ## LL)", .LL_SUFFIX }, - - .{ "ull_SUFFIX(X) (X ## ull)", .ULL_SUFFIX }, - .{ "uLL_SUFFIX(X) (X ## uLL)", .ULL_SUFFIX }, - .{ "Ull_SUFFIX(X) (X ## Ull)", .ULL_SUFFIX }, - .{ "ULL_SUFFIX(X) (X ## ULL)", .ULL_SUFFIX }, - - .{ "f_SUFFIX(X) X ## f", .F_SUFFIX }, - .{ "F_SUFFIX(X) X ## F", .F_SUFFIX }, - - .{ "u_SUFFIX(X) X ## u", .U_SUFFIX }, - .{ "U_SUFFIX(X) X ## U", .U_SUFFIX }, - - .{ "l_SUFFIX(X) X ## l", .L_SUFFIX }, - .{ "L_SUFFIX(X) X ## L", .L_SUFFIX }, - - .{ "ul_SUFFIX(X) X ## ul", .UL_SUFFIX }, - .{ "uL_SUFFIX(X) X ## uL", .UL_SUFFIX }, - .{ "Ul_SUFFIX(X) X ## Ul", .UL_SUFFIX }, - .{ "UL_SUFFIX(X) X ## UL", .UL_SUFFIX }, - - .{ "ll_SUFFIX(X) X ## ll", .LL_SUFFIX }, - .{ "LL_SUFFIX(X) X ## LL", .LL_SUFFIX }, - - .{ "ull_SUFFIX(X) X ## ull", .ULL_SUFFIX }, - .{ "uLL_SUFFIX(X) X ## uLL", .ULL_SUFFIX }, - .{ "Ull_SUFFIX(X) X ## Ull", .ULL_SUFFIX }, - .{ "ULL_SUFFIX(X) X ## ULL", .ULL_SUFFIX }, - - .{ "CAST_OR_CALL(X, Y) (X)(Y)", .CAST_OR_CALL }, - .{ "CAST_OR_CALL(X, Y) ((X)(Y))", .CAST_OR_CALL }, - - .{ - \\wl_container_of(ptr, sample, member) \ - \\(__typeof__(sample))((char *)(ptr) - \ - \\ offsetof(__typeof__(*sample), member)) - , - .WL_CONTAINER_OF, - }, - - .{ "IGNORE_ME(X) ((void)(X))", .DISCARD }, - .{ "IGNORE_ME(X) (void)(X)", .DISCARD }, - .{ "IGNORE_ME(X) ((const void)(X))", .DISCARD }, - .{ "IGNORE_ME(X) (const void)(X)", .DISCARD }, - .{ "IGNORE_ME(X) ((volatile void)(X))", .DISCARD }, - .{ "IGNORE_ME(X) (volatile void)(X)", .DISCARD }, - .{ "IGNORE_ME(X) ((const volatile void)(X))", .DISCARD }, - .{ "IGNORE_ME(X) (const volatile void)(X)", .DISCARD }, - .{ "IGNORE_ME(X) ((volatile const void)(X))", .DISCARD }, - .{ "IGNORE_ME(X) (volatile const void)(X)", .DISCARD }, -}; - -const Pattern = struct { - slicer: MacroSlicer, - impl: Impl, - - fn init(pl: *Pattern, allocator: mem.Allocator, template: Template) Error!void { - const source = template[0]; - const impl = template[1]; - var tok_list = std.ArrayList(CToken).init(allocator); - defer tok_list.deinit(); - - pl.* = .{ - .slicer = try tokenizeMacro(source, &tok_list), - .impl = impl, - }; - } - - fn deinit(pl: *Pattern, allocator: mem.Allocator) void { - allocator.free(pl.slicer.tokens); - pl.* = undefined; - } - - /// This function assumes that `ms` has already been validated to contain a function-like - /// macro, and that the parsed template macro in `pl` also contains a function-like - /// macro. Please review this logic carefully if changing that assumption. Two - /// function-like macros are considered equivalent if and only if they contain the same - /// list of tokens, modulo parameter names. - fn matches(pat: Pattern, ms: MacroSlicer) bool { - if (ms.params != pat.slicer.params) return false; - if (ms.tokens.len != pat.slicer.tokens.len) return false; - - for (ms.tokens, pat.slicer.tokens) |macro_tok, pat_tok| { - if (macro_tok.id != pat_tok.id) return false; - switch (macro_tok.id) { - .macro_param, .macro_param_no_expand => { - // `.end` is the parameter index. - if (macro_tok.end != pat_tok.end) return false; - }, - .identifier, .extended_identifier, .string_literal, .char_literal, .pp_num => { - const macro_bytes = ms.slice(macro_tok); - const pattern_bytes = pat.slicer.slice(pat_tok); - - if (!mem.eql(u8, pattern_bytes, macro_bytes)) return false; - }, - else => { - // other tags correspond to keywords and operators that do not contain a "payload" - // that can vary - }, - } - } - return true; - } -}; - -const PatternList = @This(); - -patterns: []Pattern, - -pub const MacroSlicer = struct { - source: []const u8, - tokens: []const CToken, - params: u32, - - fn slice(pl: MacroSlicer, token: CToken) []const u8 { - return pl.source[token.start..token.end]; - } -}; - -pub fn init(allocator: mem.Allocator) Error!PatternList { - const patterns = try allocator.alloc(Pattern, templates.len); - for (patterns, templates) |*pattern, template| { - try pattern.init(allocator, template); - } - return .{ .patterns = patterns }; -} - -pub fn deinit(pl: *PatternList, allocator: mem.Allocator) void { - for (pl.patterns) |*pattern| pattern.deinit(allocator); - allocator.free(pl.patterns); - pl.* = undefined; -} - -pub fn match(pl: PatternList, ms: MacroSlicer) Error!?Impl { - for (pl.patterns) |pattern| if (pattern.matches(ms)) return pattern.impl; - return null; -} - -fn tokenizeMacro(source: []const u8, tok_list: *std.ArrayList(CToken)) Error!MacroSlicer { - var param_count: u32 = 0; - var param_buf: [8][]const u8 = undefined; - - var tokenizer: aro.Tokenizer = .{ - .buf = source, - .source = .unused, - .langopts = .{}, - }; - { - const name_tok = tokenizer.nextNoWS(); - assert(name_tok.id == .identifier); - const l_paren = tokenizer.nextNoWS(); - assert(l_paren.id == .l_paren); - } - - while (true) { - const param = tokenizer.nextNoWS(); - if (param.id == .r_paren) break; - assert(param.id == .identifier); - const slice = source[param.start..param.end]; - param_buf[param_count] = slice; - param_count += 1; - - const comma = tokenizer.nextNoWS(); - if (comma.id == .r_paren) break; - assert(comma.id == .comma); - } - - outer: while (true) { - const tok = tokenizer.next(); - switch (tok.id) { - .whitespace, .comment => continue, - .identifier => { - const slice = source[tok.start..tok.end]; - for (param_buf[0..param_count], 0..) |param, i| { - if (std.mem.eql(u8, param, slice)) { - try tok_list.append(.{ - .id = .macro_param, - .source = .unused, - .end = @intCast(i), - }); - continue :outer; - } - } - }, - .hash_hash => { - if (tok_list.items[tok_list.items.len - 1].id == .macro_param) { - tok_list.items[tok_list.items.len - 1].id = .macro_param_no_expand; - } - }, - .nl, .eof => break, - else => {}, - } - try tok_list.append(tok); - } - - return .{ - .source = source, - .tokens = try tok_list.toOwnedSlice(), - .params = param_count, - }; -} - -test "Macro matching" { - const testing = std.testing; - const helper = struct { - fn checkMacro( - allocator: mem.Allocator, - pattern_list: PatternList, - source: []const u8, - comptime expected_match: ?Impl, - ) !void { - var tok_list = std.ArrayList(CToken).init(allocator); - defer tok_list.deinit(); - const ms = try tokenizeMacro(source, &tok_list); - defer allocator.free(ms.tokens); - - const matched = try pattern_list.match(ms); - if (expected_match) |expected| { - try testing.expectEqual(expected, matched); - } else { - try testing.expectEqual(@as(@TypeOf(matched), null), matched); - } - } - }; - const allocator = std.testing.allocator; - var pattern_list = try PatternList.init(allocator); - defer pattern_list.deinit(allocator); - - try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## F)", .F_SUFFIX); - try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## U)", .U_SUFFIX); - try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## L)", .L_SUFFIX); - try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## LL)", .LL_SUFFIX); - try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## UL)", .UL_SUFFIX); - try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## ULL)", .ULL_SUFFIX); - try helper.checkMacro(allocator, pattern_list, - \\container_of(a, b, c) \ - \\(__typeof__(b))((char *)(a) - \ - \\ offsetof(__typeof__(*b), c)) - , .WL_CONTAINER_OF); - - try helper.checkMacro(allocator, pattern_list, "NO_MATCH(X, Y) (X + Y)", null); - try helper.checkMacro(allocator, pattern_list, "CAST_OR_CALL(X, Y) (X)(Y)", .CAST_OR_CALL); - try helper.checkMacro(allocator, pattern_list, "CAST_OR_CALL(X, Y) ((X)(Y))", .CAST_OR_CALL); - try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (void)(X)", .DISCARD); - try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((void)(X))", .DISCARD); - try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (const void)(X)", .DISCARD); - try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((const void)(X))", .DISCARD); - try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (volatile void)(X)", .DISCARD); - try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((volatile void)(X))", .DISCARD); - try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (const volatile void)(X)", .DISCARD); - try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((const volatile void)(X))", .DISCARD); - try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (volatile const void)(X)", .DISCARD); - try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((volatile const void)(X))", .DISCARD); -} diff --git a/lib/compiler/translate-c/src/Scope.zig b/lib/compiler/translate-c/src/Scope.zig deleted file mode 100644 index 0317adb7d21f4b9b61685316baaed78beaaa9dff..0000000000000000000000000000000000000000 --- a/lib/compiler/translate-c/src/Scope.zig +++ /dev/null @@ -1,399 +0,0 @@ -const std = @import("std"); - -const aro = @import("aro"); - -const ast = @import("ast.zig"); -const Translator = @import("Translator.zig"); - -const Scope = @This(); - -pub const SymbolTable = std.StringArrayHashMapUnmanaged(ast.Node); -pub const AliasList = std.ArrayListUnmanaged(struct { - alias: []const u8, - name: []const u8, -}); - -/// Associates a container (structure or union) with its relevant member functions. -pub const ContainerMemberFns = struct { - container_decl_ptr: *ast.Node, - member_fns: std.ArrayListUnmanaged(*ast.Payload.Func) = .empty, -}; -pub const ContainerMemberFnsHashMap = std.AutoArrayHashMapUnmanaged(aro.QualType, ContainerMemberFns); - -id: Id, -parent: ?*Scope, - -pub const Id = enum { - block, - root, - condition, - loop, - do_loop, -}; - -/// Used for the scope of condition expressions, for example `if (cond)`. -/// The block is lazily initialized because it is only needed for rare -/// cases of comma operators being used. -pub const Condition = struct { - base: Scope, - block: ?Block = null, - - fn getBlockScope(cond: *Condition, t: *Translator) !*Block { - if (cond.block) |*b| return b; - cond.block = try Block.init(t, &cond.base, true); - return &cond.block.?; - } - - pub fn deinit(cond: *Condition) void { - if (cond.block) |*b| b.deinit(); - } -}; - -/// Represents an in-progress Node.Block. This struct is stack-allocated. -/// When it is deinitialized, it produces an Node.Block which is allocated -/// into the main arena. -pub const Block = struct { - base: Scope, - translator: *Translator, - statements: std.ArrayListUnmanaged(ast.Node), - variables: AliasList, - mangle_count: u32 = 0, - label: ?[]const u8 = null, - - /// By default all variables are discarded, since we do not know in advance if they - /// will be used. This maps the variable's name to the Discard payload, so that if - /// the variable is subsequently referenced we can indicate that the discard should - /// be skipped during the intermediate AST -> Zig AST render step. - variable_discards: std.StringArrayHashMapUnmanaged(*ast.Payload.Discard), - - /// When the block corresponds to a function, keep track of the return type - /// so that the return expression can be cast, if necessary - return_type: ?aro.QualType = null, - - /// C static local variables are wrapped in a block-local struct. The struct - /// is named `mangle(static_local_ + name)` and the Zig variable within the - /// struct keeps the name of the C variable. - pub const static_local_prefix = "static_local"; - - /// C extern local variables are wrapped in a block-local struct. The struct - /// is named `mangle(extern_local + name)` and the Zig variable within the - /// struct keeps the name of the C variable. - pub const extern_local_prefix = "extern_local"; - - pub fn init(t: *Translator, parent: *Scope, labeled: bool) !Block { - var blk: Block = .{ - .base = .{ - .id = .block, - .parent = parent, - }, - .translator = t, - .statements = .empty, - .variables = .empty, - .variable_discards = .empty, - }; - if (labeled) { - blk.label = try blk.makeMangledName("blk"); - } - return blk; - } - - pub fn deinit(block: *Block) void { - block.statements.deinit(block.translator.gpa); - block.variables.deinit(block.translator.gpa); - block.variable_discards.deinit(block.translator.gpa); - block.* = undefined; - } - - pub fn complete(block: *Block) !ast.Node { - const arena = block.translator.arena; - if (block.base.parent.?.id == .do_loop) { - // We reserve 1 extra statement if the parent is a do_loop. This is in case of - // do while, we want to put `if (cond) break;` at the end. - const alloc_len = block.statements.items.len + @intFromBool(block.base.parent.?.id == .do_loop); - var stmts = try arena.alloc(ast.Node, alloc_len); - stmts.len = block.statements.items.len; - @memcpy(stmts[0..block.statements.items.len], block.statements.items); - return ast.Node.Tag.block.create(arena, .{ - .label = block.label, - .stmts = stmts, - }); - } - if (block.statements.items.len == 0) return ast.Node.Tag.empty_block.init(); - return ast.Node.Tag.block.create(arena, .{ - .label = block.label, - .stmts = try arena.dupe(ast.Node, block.statements.items), - }); - } - - /// Given the desired name, return a name that does not shadow anything from outer scopes. - /// Inserts the returned name into the scope. - /// The name will not be visible to callers of getAlias. - pub fn reserveMangledName(block: *Block, name: []const u8) ![]const u8 { - return block.createMangledName(name, true, null); - } - - /// Same as reserveMangledName, but enables the alias immediately. - pub fn makeMangledName(block: *Block, name: []const u8) ![]const u8 { - return block.createMangledName(name, false, null); - } - - pub fn createMangledName(block: *Block, name: []const u8, reservation: bool, prefix_opt: ?[]const u8) ![]const u8 { - const arena = block.translator.arena; - const name_copy = try arena.dupe(u8, name); - const alias_base = if (prefix_opt) |prefix| - try std.fmt.allocPrint(arena, "{s}_{s}", .{ prefix, name }) - else - name; - var proposed_name = alias_base; - while (block.contains(proposed_name)) { - block.mangle_count += 1; - proposed_name = try std.fmt.allocPrint(arena, "{s}_{d}", .{ alias_base, block.mangle_count }); - } - const new_mangle = try block.variables.addOne(block.translator.gpa); - if (reservation) { - new_mangle.* = .{ .name = name_copy, .alias = name_copy }; - } else { - new_mangle.* = .{ .name = name_copy, .alias = proposed_name }; - } - return proposed_name; - } - - fn getAlias(block: *Block, name: []const u8) ?[]const u8 { - for (block.variables.items) |p| { - if (std.mem.eql(u8, p.name, name)) - return p.alias; - } - return block.base.parent.?.getAlias(name); - } - - fn localContains(block: *Block, name: []const u8) bool { - for (block.variables.items) |p| { - if (std.mem.eql(u8, p.alias, name)) - return true; - } - return false; - } - - fn contains(block: *Block, name: []const u8) bool { - if (block.localContains(name)) - return true; - return block.base.parent.?.contains(name); - } - - pub fn discardVariable(block: *Block, name: []const u8) Translator.Error!void { - const gpa = block.translator.gpa; - const arena = block.translator.arena; - const name_node = try ast.Node.Tag.identifier.create(arena, name); - const discard = try ast.Node.Tag.discard.create(arena, .{ .should_skip = false, .value = name_node }); - try block.statements.append(gpa, discard); - try block.variable_discards.putNoClobber(gpa, name, discard.castTag(.discard).?); - } -}; - -pub const Root = struct { - base: Scope, - translator: *Translator, - sym_table: SymbolTable, - blank_macros: std.StringArrayHashMapUnmanaged(void), - nodes: std.ArrayListUnmanaged(ast.Node), - container_member_fns_map: ContainerMemberFnsHashMap, - - pub fn init(t: *Translator) Root { - return .{ - .base = .{ - .id = .root, - .parent = null, - }, - .translator = t, - .sym_table = .empty, - .blank_macros = .empty, - .nodes = .empty, - .container_member_fns_map = .empty, - }; - } - - pub fn deinit(root: *Root) void { - root.sym_table.deinit(root.translator.gpa); - root.blank_macros.deinit(root.translator.gpa); - root.nodes.deinit(root.translator.gpa); - for (root.container_member_fns_map.values()) |*members| { - members.member_fns.deinit(root.translator.gpa); - } - root.container_member_fns_map.deinit(root.translator.gpa); - } - - /// Check if the global scope contains this name, without looking into the "future", e.g. - /// ignore the preprocessed decl and macro names. - pub fn containsNow(root: *Root, name: []const u8) bool { - return root.sym_table.contains(name); - } - - /// Check if the global scope contains the name, includes all decls that haven't been translated yet. - pub fn contains(root: *Root, name: []const u8) bool { - return root.containsNow(name) or root.translator.global_names.contains(name) or root.translator.weak_global_names.contains(name); - } - - pub fn addMemberFunction(root: *Root, func_ty: aro.Type.Func, func: *ast.Payload.Func) !void { - std.debug.assert(func.data.name != null); - if (func_ty.params.len == 0) return; - - const param1_base = func_ty.params[0].qt.base(root.translator.comp); - const container_qt = if (param1_base.type == .pointer) - param1_base.type.pointer.child.base(root.translator.comp).qt - else - param1_base.qt; - - if (root.container_member_fns_map.getPtr(container_qt)) |members| { - try members.member_fns.append(root.translator.gpa, func); - } - } - - pub fn processContainerMemberFns(root: *Root) !void { - const gpa = root.translator.gpa; - const arena = root.translator.arena; - - var member_names: std.StringArrayHashMapUnmanaged(u32) = .empty; - defer member_names.deinit(gpa); - for (root.container_member_fns_map.values()) |members| { - member_names.clearRetainingCapacity(); - const decls_ptr = switch (members.container_decl_ptr.tag()) { - .@"struct", .@"union" => blk_record: { - const payload: *ast.Payload.Container = @alignCast(@fieldParentPtr("base", members.container_decl_ptr.ptr_otherwise)); - // Avoid duplication with field names - for (payload.data.fields) |field| { - try member_names.put(gpa, field.name, 0); - } - break :blk_record &payload.data.decls; - }, - .opaque_literal => blk_opaque: { - const container_decl = try ast.Node.Tag.@"opaque".create(arena, .{ - .layout = .none, - .fields = &.{}, - .decls = &.{}, - }); - members.container_decl_ptr.* = container_decl; - break :blk_opaque &container_decl.castTag(.@"opaque").?.data.decls; - }, - else => return, - }; - - const old_decls = decls_ptr.*; - const new_decls = try arena.alloc(ast.Node, old_decls.len + members.member_fns.items.len); - @memcpy(new_decls[0..old_decls.len], old_decls); - // Assume the allocator of payload.data.decls is arena, - // so don't add arena.free(old_variables). - const func_ref_vars = new_decls[old_decls.len..]; - var count: u32 = 0; - for (members.member_fns.items) |func| { - const func_name = func.data.name.?; - - const last_index = std.mem.lastIndexOf(u8, func_name, "_"); - const last_name = if (last_index) |index| func_name[index + 1 ..] else continue; - var same_count: u32 = 0; - const gop = try member_names.getOrPutValue(gpa, last_name, same_count); - if (gop.found_existing) { - gop.value_ptr.* += 1; - same_count = gop.value_ptr.*; - } - const var_name = if (same_count == 0) - last_name - else - try std.fmt.allocPrint(arena, "{s}{d}", .{ last_name, same_count }); - - func_ref_vars[count] = try ast.Node.Tag.pub_var_simple.create(arena, .{ - .name = var_name, - .init = try ast.Node.Tag.identifier.create(arena, func_name), - }); - count += 1; - } - decls_ptr.* = new_decls[0 .. old_decls.len + count]; - } - } -}; - -pub fn findBlockScope(inner: *Scope, t: *Translator) !*Block { - var scope = inner; - while (true) { - switch (scope.id) { - .root => unreachable, - .block => return @fieldParentPtr("base", scope), - .condition => return @as(*Condition, @fieldParentPtr("base", scope)).getBlockScope(t), - else => scope = scope.parent.?, - } - } -} - -pub fn findBlockReturnType(inner: *Scope) aro.QualType { - var scope = inner; - while (true) { - switch (scope.id) { - .root => unreachable, - .block => { - const block: *Block = @fieldParentPtr("base", scope); - if (block.return_type) |qt| return qt; - scope = scope.parent.?; - }, - else => scope = scope.parent.?, - } - } -} - -pub fn getAlias(scope: *Scope, name: []const u8) ?[]const u8 { - return switch (scope.id) { - .root => null, - .block => @as(*Block, @fieldParentPtr("base", scope)).getAlias(name), - .loop, .do_loop, .condition => scope.parent.?.getAlias(name), - }; -} - -fn contains(scope: *Scope, name: []const u8) bool { - return switch (scope.id) { - .root => @as(*Root, @fieldParentPtr("base", scope)).contains(name), - .block => @as(*Block, @fieldParentPtr("base", scope)).contains(name), - .loop, .do_loop, .condition => scope.parent.?.contains(name), - }; -} - -/// Appends a node to the first block scope if inside a function, or to the root tree if not. -pub fn appendNode(inner: *Scope, node: ast.Node) !void { - var scope = inner; - while (true) { - switch (scope.id) { - .root => { - const root: *Root = @fieldParentPtr("base", scope); - return root.nodes.append(root.translator.gpa, node); - }, - .block => { - const block: *Block = @fieldParentPtr("base", scope); - return block.statements.append(block.translator.gpa, node); - }, - else => scope = scope.parent.?, - } - } -} - -pub fn skipVariableDiscard(inner: *Scope, name: []const u8) void { - if (true) { - // TODO: due to 'local variable is never mutated' errors, we can - // only skip discards if a variable is used as an lvalue, which - // we don't currently have detection for in translate-c. - // Once #17584 is completed, perhaps we can do away with this - // logic entirely, and instead rely on render to fixup code. - return; - } - var scope = inner; - while (true) { - switch (scope.id) { - .root => return, - .block => { - const block: *Block = @fieldParentPtr("base", scope); - if (block.variable_discards.get(name)) |discard| { - discard.data.should_skip = true; - return; - } - }, - else => {}, - } - scope = scope.parent.?; - } -} diff --git a/lib/compiler/translate-c/src/Translator.zig b/lib/compiler/translate-c/src/Translator.zig deleted file mode 100644 index 202ca5b3ca64df3d9ba14c335f02aa85705255b6..0000000000000000000000000000000000000000 --- a/lib/compiler/translate-c/src/Translator.zig +++ /dev/null @@ -1,4183 +0,0 @@ -const std = @import("std"); -const mem = std.mem; -const assert = std.debug.assert; -const CallingConvention = std.builtin.CallingConvention; - -const aro = @import("aro"); -const CToken = aro.Tokenizer.Token; -const Tree = aro.Tree; -const Node = Tree.Node; -const TokenIndex = Tree.TokenIndex; -const QualType = aro.QualType; - -const ast = @import("ast.zig"); -const ZigNode = ast.Node; -const ZigTag = ZigNode.Tag; -const builtins = @import("builtins.zig"); -const helpers = @import("helpers.zig"); -const MacroTranslator = @import("MacroTranslator.zig"); -const PatternList = @import("PatternList.zig"); -const Scope = @import("Scope.zig"); - -pub const Error = std.mem.Allocator.Error; -pub const MacroProcessingError = Error || error{UnexpectedMacroToken}; -pub const TypeError = Error || error{UnsupportedType}; -pub const TransError = TypeError || error{UnsupportedTranslation}; - -const Translator = @This(); - -/// The C AST to be translated. -tree: *const Tree, -/// The compilation corresponding to the AST. -comp: *aro.Compilation, -/// The Preprocessor that produced the source for `tree`. -pp: *const aro.Preprocessor, - -gpa: mem.Allocator, -arena: mem.Allocator, - -alias_list: Scope.AliasList, -global_scope: *Scope.Root, -/// Running number used for creating new unique identifiers. -mangle_count: u32 = 0, - -/// Table of declarations for enum, struct, union and typedef types. -type_decls: std.AutoArrayHashMapUnmanaged(Node.Index, []const u8) = .empty, -/// Table of record decls that have been demoted to opaques. -opaque_demotes: std.AutoHashMapUnmanaged(QualType, void) = .empty, -/// Table of unnamed enums and records that are child types of typedefs. -unnamed_typedefs: std.AutoHashMapUnmanaged(QualType, []const u8) = .empty, -/// Table of anonymous record to generated field names. -anonymous_record_field_names: std.AutoHashMapUnmanaged(struct { - parent: QualType, - field: QualType, -}, []const u8) = .empty, - -/// This one is different than the root scope's name table. This contains -/// a list of names that we found by visiting all the top level decls without -/// translating them. The other maps are updated as we translate; this one is updated -/// up front in a pre-processing step. -global_names: std.StringArrayHashMapUnmanaged(void) = .empty, - -/// This is similar to `global_names`, but contains names which we would -/// *like* to use, but do not strictly *have* to if they are unavailable. -/// These are relevant to types, which ideally we would name like -/// 'struct_foo' with an alias 'foo', but if either of those names is taken, -/// may be mangled. -/// This is distinct from `global_names` so we can detect at a type -/// declaration whether or not the name is available. -weak_global_names: std.StringArrayHashMapUnmanaged(void) = .empty, - -/// Set of identifiers known to refer to typedef declarations. -/// Used when parsing macros. -typedefs: std.StringArrayHashMapUnmanaged(void) = .empty, - -/// The lhs lval of a compound assignment expression. -compound_assign_dummy: ?ZigNode = null, - -pub fn getMangle(t: *Translator) u32 { - t.mangle_count += 1; - return t.mangle_count; -} - -/// Convert an `aro.Source.Location` to a 'file:line:column' string. -pub fn locStr(t: *Translator, loc: aro.Source.Location) ![]const u8 { - const source = t.comp.getSource(loc.id); - const line_col = source.lineCol(loc); - const filename = source.path; - - const line = source.physicalLine(loc); - const col = line_col.col; - - return std.fmt.allocPrint(t.arena, "{s}:{d}:{d}", .{ filename, line, col }); -} - -fn maybeSuppressResult(t: *Translator, used: ResultUsed, result: ZigNode) TransError!ZigNode { - if (used == .used) return result; - return ZigTag.discard.create(t.arena, .{ .should_skip = false, .value = result }); -} - -pub fn addTopLevelDecl(t: *Translator, name: []const u8, decl_node: ZigNode) !void { - const gop = try t.global_scope.sym_table.getOrPut(t.gpa, name); - if (!gop.found_existing) { - gop.value_ptr.* = decl_node; - try t.global_scope.nodes.append(t.gpa, decl_node); - } -} - -fn fail( - t: *Translator, - err: anytype, - source_loc: TokenIndex, - comptime format: []const u8, - args: anytype, -) (@TypeOf(err) || error{OutOfMemory}) { - try t.warn(&t.global_scope.base, source_loc, format, args); - return err; -} - -pub fn failDecl( - t: *Translator, - scope: *Scope, - tok_idx: TokenIndex, - name: []const u8, - comptime format: []const u8, - args: anytype, -) Error!void { - const loc = t.tree.tokens.items(.loc)[tok_idx]; - return t.failDeclExtra(scope, loc, name, format, args); -} - -pub fn failDeclExtra( - t: *Translator, - scope: *Scope, - loc: aro.Source.Location, - name: []const u8, - comptime format: []const u8, - args: anytype, -) Error!void { - // location - // pub const name = @compileError(msg); - const fail_msg = try std.fmt.allocPrint(t.arena, format, args); - const fail_decl = try ZigTag.fail_decl.create(t.arena, .{ .actual = name, .mangled = fail_msg }); - - const str = try t.locStr(loc); - const location_comment = try std.fmt.allocPrint(t.arena, "// {s}", .{str}); - const loc_node = try ZigTag.warning.create(t.arena, location_comment); - - if (scope.id == .root) { - try t.addTopLevelDecl(name, fail_decl); - try scope.appendNode(loc_node); - } else { - try scope.appendNode(fail_decl); - try scope.appendNode(loc_node); - - const bs = try scope.findBlockScope(t); - try bs.discardVariable(name); - } -} - -fn warn(t: *Translator, scope: *Scope, tok_idx: TokenIndex, comptime format: []const u8, args: anytype) !void { - const loc = t.tree.tokens.items(.loc)[tok_idx]; - const str = try t.locStr(loc); - const value = try std.fmt.allocPrint(t.arena, "// {s}: warning: " ++ format, .{str} ++ args); - try scope.appendNode(try ZigTag.warning.create(t.arena, value)); -} - -pub const Options = struct { - gpa: mem.Allocator, - comp: *aro.Compilation, - pp: *const aro.Preprocessor, - tree: *const aro.Tree, - module_libs: bool, -}; - -pub fn translate(options: Options) ![]u8 { - const gpa = options.gpa; - var arena_allocator = std.heap.ArenaAllocator.init(gpa); - defer arena_allocator.deinit(); - const arena = arena_allocator.allocator(); - - var translator: Translator = .{ - .gpa = gpa, - .arena = arena, - .alias_list = .empty, - .global_scope = try arena.create(Scope.Root), - .comp = options.comp, - .pp = options.pp, - .tree = options.tree, - }; - translator.global_scope.* = Scope.Root.init(&translator); - defer { - translator.type_decls.deinit(gpa); - translator.alias_list.deinit(gpa); - translator.global_names.deinit(gpa); - translator.weak_global_names.deinit(gpa); - translator.opaque_demotes.deinit(gpa); - translator.unnamed_typedefs.deinit(gpa); - translator.anonymous_record_field_names.deinit(gpa); - translator.typedefs.deinit(gpa); - translator.global_scope.deinit(); - } - - try translator.prepopulateGlobalNameTable(); - try translator.transTopLevelDecls(); - - // Insert empty line before macros. - try translator.global_scope.nodes.append(gpa, try ZigTag.warning.create(arena, "\n")); - - try translator.transMacros(); - - for (translator.alias_list.items) |alias| { - if (!translator.global_scope.sym_table.contains(alias.alias)) { - const node = try ZigTag.alias.create(arena, .{ .actual = alias.alias, .mangled = alias.name }); - try translator.addTopLevelDecl(alias.alias, node); - } - } - - try translator.global_scope.processContainerMemberFns(); - - var buf: std.ArrayList(u8) = .init(gpa); - defer buf.deinit(); - - if (options.module_libs) { - try buf.appendSlice( - \\pub const __builtin = @import("c_builtins"); - \\pub const __helpers = @import("helpers"); - \\ - \\ - ); - } else { - try buf.appendSlice( - \\pub const __builtin = @import("c_builtins.zig"); - \\pub const __helpers = @import("helpers.zig"); - \\ - \\ - ); - } - - var zig_ast = try ast.render(gpa, translator.global_scope.nodes.items); - defer { - gpa.free(zig_ast.source); - zig_ast.deinit(gpa); - } - try zig_ast.renderToArrayList(&buf, .{}); - return buf.toOwnedSlice(); -} - -fn prepopulateGlobalNameTable(t: *Translator) !void { - for (t.tree.root_decls.items) |decl| { - switch (decl.get(t.tree)) { - .typedef => |typedef_decl| { - const decl_name = t.tree.tokSlice(typedef_decl.name_tok); - try t.global_names.put(t.gpa, decl_name, {}); - - // Check for typedefs with unnamed enum/record child types. - const base = typedef_decl.qt.base(t.comp); - switch (base.type) { - .@"enum" => |enum_ty| { - if (enum_ty.name.lookup(t.comp)[0] != '(') continue; - }, - .@"struct", .@"union" => |record_ty| { - if (record_ty.name.lookup(t.comp)[0] != '(') continue; - }, - else => continue, - } - - const gop = try t.unnamed_typedefs.getOrPut(t.gpa, base.qt); - if (gop.found_existing) { - // One typedef can declare multiple names. - // TODO Don't put this one in `decl_table` so it's processed later. - continue; - } - gop.value_ptr.* = decl_name; - }, - - .struct_decl, - .union_decl, - .struct_forward_decl, - .union_forward_decl, - .enum_decl, - .enum_forward_decl, - => { - const decl_qt = decl.qt(t.tree); - const prefix, const name = switch (decl_qt.base(t.comp).type) { - .@"struct" => |struct_ty| .{ "struct", struct_ty.name.lookup(t.comp) }, - .@"union" => |union_ty| .{ "union", union_ty.name.lookup(t.comp) }, - .@"enum" => |enum_ty| .{ "enum", enum_ty.name.lookup(t.comp) }, - else => unreachable, - }; - const prefixed_name = try std.fmt.allocPrint(t.arena, "{s}_{s}", .{ prefix, name }); - // `name` and `prefixed_name` are the preferred names for this type. - // However, we can name it anything else if necessary, so these are "weak names". - try t.weak_global_names.ensureUnusedCapacity(t.gpa, 2); - t.weak_global_names.putAssumeCapacity(name, {}); - t.weak_global_names.putAssumeCapacity(prefixed_name, {}); - }, - - .function, .variable => { - const decl_name = t.tree.tokSlice(decl.tok(t.tree)); - try t.global_names.put(t.gpa, decl_name, {}); - }, - .static_assert => {}, - .empty_decl => {}, - .global_asm => {}, - else => unreachable, - } - } - - for (t.pp.defines.keys(), t.pp.defines.values()) |name, macro| { - if (macro.is_builtin) continue; - if (!t.isSelfDefinedMacro(name, macro)) { - try t.global_names.put(t.gpa, name, {}); - } - } -} - -/// Determines whether macro is of the form: `#define FOO FOO` (Possibly with trailing tokens) -/// Macros of this form will not be translated. -fn isSelfDefinedMacro(t: *Translator, name: []const u8, macro: aro.Preprocessor.Macro) bool { - if (macro.is_func) return false; - - if (macro.tokens.len < 1) return false; - const first_tok = macro.tokens[0]; - - const source = t.comp.getSource(macro.loc.id); - const slice = source.buf[first_tok.start..first_tok.end]; - - return std.mem.eql(u8, name, slice); -} - -// ======================= -// Declaration translation -// ======================= - -fn transTopLevelDecls(t: *Translator) !void { - for (t.tree.root_decls.items) |decl| { - try t.transDecl(&t.global_scope.base, decl); - } -} - -fn transDecl(t: *Translator, scope: *Scope, decl: Node.Index) !void { - switch (decl.get(t.tree)) { - .typedef => |typedef_decl| { - // Implicit typedefs are translated only if referenced. - if (typedef_decl.implicit) return; - try t.transTypeDef(scope, decl); - }, - - .struct_decl, .union_decl => |record_decl| { - try t.transRecordDecl(scope, record_decl.container_qt); - }, - - .enum_decl => |enum_decl| { - try t.transEnumDecl(scope, enum_decl.container_qt); - }, - - .enum_field, - .record_field, - .struct_forward_decl, - .union_forward_decl, - .enum_forward_decl, - => return, - - .function => |function| { - if (function.definition) |definition| { - return t.transFnDecl(scope, definition.get(t.tree).function); - } - try t.transFnDecl(scope, function); - }, - - .variable => |variable| { - if (variable.definition != null) return; - try t.transVarDecl(scope, variable); - }, - .static_assert => |static_assert| { - try t.transStaticAssert(&t.global_scope.base, static_assert); - }, - .global_asm => |global_asm| { - try t.transGlobalAsm(&t.global_scope.base, global_asm); - }, - .empty_decl => {}, - else => unreachable, - } -} - -pub const builtin_typedef_map = std.StaticStringMap([]const u8).initComptime(.{ - .{ "uint8_t", "u8" }, - .{ "int8_t", "i8" }, - .{ "uint16_t", "u16" }, - .{ "int16_t", "i16" }, - .{ "uint32_t", "u32" }, - .{ "int32_t", "i32" }, - .{ "uint64_t", "u64" }, - .{ "int64_t", "i64" }, - .{ "intptr_t", "isize" }, - .{ "uintptr_t", "usize" }, - .{ "ssize_t", "isize" }, - .{ "size_t", "usize" }, -}); - -fn transTypeDef(t: *Translator, scope: *Scope, typedef_node: Node.Index) Error!void { - const typedef_decl = typedef_node.get(t.tree).typedef; - if (t.type_decls.get(typedef_node)) |_| - return; // Avoid processing this decl twice - - const toplevel = scope.id == .root; - const bs: *Scope.Block = if (!toplevel) try scope.findBlockScope(t) else undefined; - - var name: []const u8 = t.tree.tokSlice(typedef_decl.name_tok); - try t.typedefs.put(t.gpa, name, {}); - - if (builtin_typedef_map.get(name)) |builtin| { - return t.type_decls.putNoClobber(t.gpa, typedef_node, builtin); - } - if (!toplevel) name = try bs.makeMangledName(name); - try t.type_decls.putNoClobber(t.gpa, typedef_node, name); - - const typedef_loc = typedef_decl.name_tok; - const init_node = t.transType(scope, typedef_decl.qt, typedef_loc) catch |err| switch (err) { - error.UnsupportedType => { - return t.failDecl(scope, typedef_loc, name, "unable to resolve typedef child type", .{}); - }, - error.OutOfMemory => |e| return e, - }; - - const payload = try t.arena.create(ast.Payload.SimpleVarDecl); - payload.* = .{ - .base = .{ .tag = if (toplevel) .pub_var_simple else .var_simple }, - .data = .{ - .name = name, - .init = init_node, - }, - }; - const node = ZigNode.initPayload(&payload.base); - - if (toplevel) { - try t.addTopLevelDecl(name, node); - } else { - try scope.appendNode(node); - try bs.discardVariable(name); - } -} - -fn mangleWeakGlobalName(t: *Translator, want_name: []const u8) Error![]const u8 { - var cur_name = want_name; - - if (!t.weak_global_names.contains(want_name)) { - // This type wasn't noticed by the name detection pass, so nothing has been treating this as - // a weak global name. We must mangle it to avoid conflicts with locals. - cur_name = try std.fmt.allocPrint(t.arena, "{s}_{d}", .{ want_name, t.getMangle() }); - } - - while (t.global_names.contains(cur_name)) { - cur_name = try std.fmt.allocPrint(t.arena, "{s}_{d}", .{ want_name, t.getMangle() }); - } - return cur_name; -} - -fn transRecordDecl(t: *Translator, scope: *Scope, record_qt: QualType) Error!void { - const base = record_qt.base(t.comp); - const record_ty = switch (base.type) { - .@"struct", .@"union" => |record_ty| record_ty, - else => unreachable, - }; - - if (t.type_decls.get(record_ty.decl_node)) |_| - return; // Avoid processing this decl twice - - const toplevel = scope.id == .root; - const bs: *Scope.Block = if (!toplevel) try scope.findBlockScope(t) else undefined; - - const container_kind: ZigTag = if (base.type == .@"union") .@"union" else .@"struct"; - const container_kind_name = @tagName(container_kind); - - var bare_name = record_ty.name.lookup(t.comp); - var is_unnamed = false; - var name = bare_name; - - if (t.unnamed_typedefs.get(base.qt)) |typedef_name| { - bare_name = typedef_name; - name = typedef_name; - } else { - if (record_ty.isAnonymous(t.comp)) { - bare_name = try std.fmt.allocPrint(t.arena, "unnamed_{d}", .{t.getMangle()}); - is_unnamed = true; - } - name = try std.fmt.allocPrint(t.arena, "{s}_{s}", .{ container_kind_name, bare_name }); - if (toplevel and !is_unnamed) { - name = try t.mangleWeakGlobalName(name); - } - } - if (!toplevel) name = try bs.makeMangledName(name); - try t.type_decls.putNoClobber(t.gpa, record_ty.decl_node, name); - - const is_pub = toplevel and !is_unnamed; - const init_node = init: { - if (record_ty.layout == null) { - try t.opaque_demotes.put(t.gpa, base.qt, {}); - break :init ZigTag.opaque_literal.init(); - } - - var fields = try std.ArrayList(ast.Payload.Container.Field).initCapacity(t.gpa, record_ty.fields.len); - defer fields.deinit(); - - var functions = std.ArrayList(ZigNode).init(t.gpa); - defer functions.deinit(); - - var unnamed_field_count: u32 = 0; - - // If a record doesn't have any attributes that would affect the alignment and - // layout, then we can just use a simple `extern` type. If it does have attributes, - // then we need to inspect the layout and assign an `align` value for each field. - const has_alignment_attributes = aligned: { - if (record_qt.hasAttribute(t.comp, .@"packed")) break :aligned true; - if (record_qt.hasAttribute(t.comp, .aligned)) break :aligned true; - for (record_ty.fields) |field| { - const field_attrs = field.attributes(t.comp); - for (field_attrs) |field_attr| { - switch (field_attr.tag) { - .@"packed", .aligned => break :aligned true, - else => {}, - } - } - } - break :aligned false; - }; - const head_field_alignment: ?c_uint = if (has_alignment_attributes) t.headFieldAlignment(record_ty) else null; - - for (record_ty.fields, 0..) |field, field_index| { - const field_loc = field.name_tok; - - // Demote record to opaque if it contains a bitfield - if (field.bit_width != .null) { - try t.opaque_demotes.put(t.gpa, base.qt, {}); - try t.warn(scope, field_loc, "{s} demoted to opaque type - has bitfield", .{container_kind_name}); - break :init ZigTag.opaque_literal.init(); - } - - var field_name = field.name.lookup(t.comp); - if (field.name_tok == 0) { - field_name = try std.fmt.allocPrint(t.arena, "unnamed_{d}", .{unnamed_field_count}); - unnamed_field_count += 1; - try t.anonymous_record_field_names.put(t.gpa, .{ - .parent = base.qt, - .field = field.qt, - }, field_name); - } - - const field_alignment = if (has_alignment_attributes) - t.alignmentForField(record_ty, head_field_alignment, field_index) - else - null; - - const field_type = field_type: { - // Check if this is a flexible array member. - flexible: { - if (field_index != record_ty.fields.len - 1 and container_kind != .@"union") break :flexible; - const array_ty = field.qt.get(t.comp, .array) orelse break :flexible; - if (array_ty.len != .incomplete and (array_ty.len != .fixed or array_ty.len.fixed != 0)) break :flexible; - - const elem_type = t.transType(scope, array_ty.elem, field_loc) catch |err| switch (err) { - error.UnsupportedType => break :flexible, - else => |e| return e, - }; - const zero_array = try ZigTag.array_type.create(t.arena, .{ .len = 0, .elem_type = elem_type }); - - const member_name = field_name; - field_name = try std.fmt.allocPrint(t.arena, "_{s}", .{field_name}); - - const member = try t.createFlexibleMemberFn(member_name, field_name); - try functions.append(member); - - break :field_type zero_array; - } - - break :field_type t.transType(scope, field.qt, field_loc) catch |err| switch (err) { - error.UnsupportedType => { - try t.opaque_demotes.put(t.gpa, base.qt, {}); - try t.warn(scope, field.name_tok, "{s} demoted to opaque type - unable to translate type of field {s}", .{ - container_kind_name, - field_name, - }); - break :init ZigTag.opaque_literal.init(); - }, - else => |e| return e, - }; - }; - - // C99 introduced designated initializers for structs. Omitted fields are implicitly - // initialized to zero. Some C APIs are designed with this in mind. Defaulting to zero - // values for translated struct fields permits Zig code to comfortably use such an API. - const default_value = if (container_kind == .@"struct") - try t.createZeroValueNode(field.qt, field_type, .no_as) - else - null; - - fields.appendAssumeCapacity(.{ - .name = field_name, - .type = field_type, - .alignment = field_alignment, - .default_value = default_value, - }); - } - - // A record is empty if it has no fields or only flexible array fields. - if (record_ty.fields.len == functions.items.len and - t.comp.target.os.tag == .windows and t.comp.target.abi == .msvc) - { - // In MSVC empty records have the same size as their alignment. - const padding_bits = record_ty.layout.?.size_bits; - const alignment_bits = record_ty.layout.?.field_alignment_bits; - - try fields.append(.{ - .name = "_padding", - .type = try ZigTag.type.create(t.arena, try std.fmt.allocPrint(t.arena, "u{d}", .{padding_bits})), - .alignment = @divExact(alignment_bits, 8), - .default_value = if (container_kind == .@"struct") - ZigTag.zero_literal.init() - else - null, - }); - } - - const container_payload = try t.arena.create(ast.Payload.Container); - container_payload.* = .{ - .base = .{ .tag = container_kind }, - .data = .{ - .layout = .@"extern", - .fields = try t.arena.dupe(ast.Payload.Container.Field, fields.items), - .decls = try t.arena.dupe(ZigNode, functions.items), - }, - }; - break :init ZigNode.initPayload(&container_payload.base); - }; - - const payload = try t.arena.create(ast.Payload.SimpleVarDecl); - payload.* = .{ - .base = .{ .tag = if (is_pub) .pub_var_simple else .var_simple }, - .data = .{ - .name = name, - .init = init_node, - }, - }; - const node = ZigNode.initPayload(&payload.base); - if (toplevel) { - try t.addTopLevelDecl(name, node); - // Only add the alias if the name is available *and* it was caught by - // name detection. Don't bother performing a weak mangle, since a - // mangled name is of no real use here. - if (!is_unnamed and !t.global_names.contains(bare_name) and t.weak_global_names.contains(bare_name)) - try t.alias_list.append(t.gpa, .{ .alias = bare_name, .name = name }); - try t.global_scope.container_member_fns_map.put(t.gpa, record_qt, .{ - .container_decl_ptr = &payload.data.init, - }); - } else { - try scope.appendNode(node); - try bs.discardVariable(name); - } -} - -fn transFnDecl(t: *Translator, scope: *Scope, function: Node.Function) Error!void { - const func_ty = function.qt.get(t.comp, .func).?; - - const is_pub = scope.id == .root; - - const fn_name = t.tree.tokSlice(function.name_tok); - if (scope.getAlias(fn_name) != null or t.global_scope.containsNow(fn_name)) - return; // Avoid processing this decl twice - - const fn_decl_loc = function.name_tok; - const has_body = function.body != null and func_ty.kind != .variadic; - if (function.body != null and func_ty.kind == .variadic) { - try t.warn(scope, function.name_tok, "TODO unable to translate variadic function, demoted to extern", .{}); - } - - const is_always_inline = has_body and function.qt.getAttribute(t.comp, .always_inline) != null; - const proto_ctx: FnProtoContext = .{ - .fn_name = fn_name, - .is_always_inline = is_always_inline, - .is_extern = !has_body, - .is_export = !function.static and has_body and !is_always_inline and !function.@"inline", - .is_pub = is_pub, - .has_body = has_body, - .cc = if (function.qt.getAttribute(t.comp, .calling_convention)) |some| switch (some.cc) { - .c => .c, - .stdcall => .x86_stdcall, - .thiscall => .x86_thiscall, - .fastcall => .x86_fastcall, - .regcall => .x86_regcall, - .riscv_vector => .riscv_vector, - .aarch64_sve_pcs => .aarch64_sve_pcs, - .aarch64_vector_pcs => .aarch64_vfabi, - .arm_aapcs => .arm_aapcs, - .arm_aapcs_vfp => .arm_aapcs_vfp, - .vectorcall => switch (t.comp.target.cpu.arch) { - .x86 => .x86_vectorcall, - .aarch64, .aarch64_be => .aarch64_vfabi, - else => .c, - }, - .x86_64_sysv => .x86_64_sysv, - .x86_64_win => .x86_64_win, - } else .c, - }; - - const proto_node = t.transFnType(&t.global_scope.base, function.qt, func_ty, fn_decl_loc, proto_ctx) catch |err| switch (err) { - error.UnsupportedType => { - return t.failDecl(scope, fn_decl_loc, fn_name, "unable to resolve prototype of function", .{}); - }, - error.OutOfMemory => |e| return e, - }; - - const proto_payload = proto_node.castTag(.func).?; - if (!has_body) { - if (scope.id != .root) { - const bs: *Scope.Block = try scope.findBlockScope(t); - const mangled_name = try bs.createMangledName(fn_name, false, Scope.Block.extern_local_prefix); - const wrapped = try ZigTag.wrapped_local.create(t.arena, .{ .name = mangled_name, .init = proto_node }); - try scope.appendNode(wrapped); - try bs.discardVariable(mangled_name); - return; - } - try t.global_scope.addMemberFunction(func_ty, proto_payload); - return t.addTopLevelDecl(fn_name, proto_node); - } - - // actual function definition with body - const body_stmt = function.body.?.get(t.tree).compound_stmt; - var block_scope = try Scope.Block.init(t, &t.global_scope.base, false); - block_scope.return_type = func_ty.return_type; - defer block_scope.deinit(); - - var param_id: c_uint = 0; - for (proto_payload.data.params, func_ty.params) |*param, param_info| { - const param_name = param.name orelse { - proto_payload.data.is_extern = true; - proto_payload.data.is_export = false; - proto_payload.data.is_inline = false; - try t.warn(&t.global_scope.base, fn_decl_loc, "function {s} parameter has no name, demoted to extern", .{fn_name}); - return t.addTopLevelDecl(fn_name, proto_node); - }; - - const is_const = param_info.qt.@"const"; - - const mangled_param_name = try block_scope.makeMangledName(param_name); - param.name = mangled_param_name; - - if (!is_const) { - const bare_arg_name = try std.fmt.allocPrint(t.arena, "arg_{s}", .{mangled_param_name}); - const arg_name = try block_scope.makeMangledName(bare_arg_name); - param.name = arg_name; - - const redecl_node = try ZigTag.arg_redecl.create(t.arena, .{ .actual = mangled_param_name, .mangled = arg_name }); - try block_scope.statements.append(t.gpa, redecl_node); - } - try block_scope.discardVariable(mangled_param_name); - - param_id += 1; - } - - t.transCompoundStmtInline(body_stmt, &block_scope) catch |err| switch (err) { - error.OutOfMemory => |e| return e, - error.UnsupportedTranslation, - error.UnsupportedType, - => { - proto_payload.data.is_extern = true; - proto_payload.data.is_export = false; - proto_payload.data.is_inline = false; - try t.warn(&t.global_scope.base, fn_decl_loc, "unable to translate function, demoted to extern", .{}); - return t.addTopLevelDecl(fn_name, proto_node); - }, - }; - - try t.global_scope.addMemberFunction(func_ty, proto_payload); - proto_payload.data.body = try block_scope.complete(); - return t.addTopLevelDecl(fn_name, proto_node); -} - -fn transVarDecl(t: *Translator, scope: *Scope, variable: Node.Variable) Error!void { - const base_name = t.tree.tokSlice(variable.name_tok); - const toplevel = scope.id == .root; - const bs: *Scope.Block = if (!toplevel) try scope.findBlockScope(t) else undefined; - const name, const use_base_name = blk: { - if (toplevel) break :blk .{ base_name, false }; - - // Local extern and static variables are wrapped in a struct. - const prefix: ?[]const u8 = switch (variable.storage_class) { - .@"extern" => Scope.Block.extern_local_prefix, - .static => Scope.Block.static_local_prefix, - else => null, - }; - break :blk .{ try bs.createMangledName(base_name, false, prefix), prefix != null }; - }; - - if (t.typeWasDemotedToOpaque(variable.qt)) { - if (variable.storage_class != .@"extern" and scope.id == .root) { - return t.failDecl(scope, variable.name_tok, name, "non-extern variable has opaque type", .{}); - } else { - return t.failDecl(scope, variable.name_tok, name, "local variable has opaque type", .{}); - } - } - - const type_node = (if (variable.initializer) |init| - t.transTypeInit(scope, variable.qt, init, variable.name_tok) - else - t.transType(scope, variable.qt, variable.name_tok)) catch |err| switch (err) { - error.UnsupportedType => { - return t.failDecl(scope, variable.name_tok, name, "unable to translate variable declaration type", .{}); - }, - else => |e| return e, - }; - - const array_ty = variable.qt.get(t.comp, .array); - var is_const = variable.qt.@"const" or (array_ty != null and array_ty.?.elem.@"const"); - var is_extern = variable.storage_class == .@"extern"; - - const init_node = init: { - if (variable.initializer) |init| { - const maybe_literal = init.get(t.tree); - const init_node = (if (maybe_literal == .string_literal_expr) - t.transStringLiteralInitializer(init, maybe_literal.string_literal_expr, type_node) - else - t.transExprCoercing(scope, init, .used)) catch |err| switch (err) { - error.UnsupportedTranslation, error.UnsupportedType => { - return t.failDecl(scope, variable.name_tok, name, "unable to resolve var init expr", .{}); - }, - else => |e| return e, - }; - - if (!variable.qt.is(t.comp, .bool) and init_node.isBoolRes()) { - break :init try ZigTag.int_from_bool.create(t.arena, init_node); - } else { - break :init init_node; - } - } - if (variable.storage_class == .@"extern") { - if (array_ty != null and array_ty.?.len == .incomplete) { - // Oh no, an extern array of unknown size! These are really fun because there's no - // direct equivalent in Zig. To translate correctly, we'll have to create a C-pointer - // to the data initialized via @extern. - - // Since this is really a pointer to the underlying data, we tweak a few properties. - is_extern = false; - is_const = true; - - const name_str = try std.fmt.allocPrint(t.arena, "\"{s}\"", .{base_name}); - break :init try ZigTag.builtin_extern.create(t.arena, .{ - .type = type_node, - .name = try ZigTag.string_literal.create(t.arena, name_str), - }); - } - break :init null; - } - if (toplevel or variable.storage_class == .static or variable.thread_local) { - // The C language specification states that variables with static or threadlocal - // storage without an initializer are initialized to a zero value. - break :init try t.createZeroValueNode(variable.qt, type_node, .no_as); - } - break :init ZigTag.undefined_literal.init(); - }; - - const linksection_string = blk: { - if (variable.qt.getAttribute(t.comp, .section)) |section| { - break :blk t.comp.interner.get(section.name.ref()).bytes; - } - break :blk null; - }; - - const alignment: ?c_uint = variable.qt.requestedAlignment(t.comp) orelse null; - var node = try ZigTag.var_decl.create(t.arena, .{ - .is_pub = toplevel, - .is_const = is_const, - .is_extern = is_extern, - .is_export = toplevel and variable.storage_class == .auto, - .is_threadlocal = variable.thread_local, - .linksection_string = linksection_string, - .alignment = alignment, - .name = if (use_base_name) base_name else name, - .type = type_node, - .init = init_node, - }); - - if (toplevel) { - try t.addTopLevelDecl(name, node); - } else { - if (use_base_name) { - node = try ZigTag.wrapped_local.create(t.arena, .{ .name = name, .init = node }); - } - try scope.appendNode(node); - try bs.discardVariable(name); - - if (variable.qt.getAttribute(t.comp, .cleanup)) |cleanup_attr| { - const cleanup_fn_name = t.tree.tokSlice(cleanup_attr.function.tok); - const mangled_fn_name = scope.getAlias(cleanup_fn_name) orelse cleanup_fn_name; - const fn_id = try ZigTag.identifier.create(t.arena, mangled_fn_name); - - const varname = try ZigTag.identifier.create(t.arena, name); - const args = try t.arena.alloc(ZigNode, 1); - args[0] = try ZigTag.address_of.create(t.arena, varname); - - const cleanup_call = try ZigTag.call.create(t.arena, .{ .lhs = fn_id, .args = args }); - const discard = try ZigTag.discard.create(t.arena, .{ .should_skip = false, .value = cleanup_call }); - const deferred_cleanup = try ZigTag.@"defer".create(t.arena, discard); - - try bs.statements.append(t.gpa, deferred_cleanup); - } - } -} - -fn transEnumDecl(t: *Translator, scope: *Scope, enum_qt: QualType) Error!void { - const base = enum_qt.base(t.comp); - const enum_ty = base.type.@"enum"; - - if (t.type_decls.get(enum_ty.decl_node)) |_| - return; // Avoid processing this decl twice - - const toplevel = scope.id == .root; - const bs: *Scope.Block = if (!toplevel) try scope.findBlockScope(t) else undefined; - - var bare_name = enum_ty.name.lookup(t.comp); - var is_unnamed = false; - var name = bare_name; - if (t.unnamed_typedefs.get(base.qt)) |typedef_name| { - bare_name = typedef_name; - name = typedef_name; - } else { - if (enum_ty.isAnonymous(t.comp)) { - bare_name = try std.fmt.allocPrint(t.arena, "unnamed_{d}", .{t.getMangle()}); - is_unnamed = true; - } - name = try std.fmt.allocPrint(t.arena, "enum_{s}", .{bare_name}); - } - if (!toplevel) name = try bs.makeMangledName(name); - try t.type_decls.putNoClobber(t.gpa, enum_ty.decl_node, name); - - const enum_type_node = if (!base.qt.hasIncompleteSize(t.comp)) blk: { - const enum_decl = enum_ty.decl_node.get(t.tree).enum_decl; - for (enum_ty.fields, enum_decl.fields) |field, field_node| { - var enum_val_name = field.name.lookup(t.comp); - if (!toplevel) { - enum_val_name = try bs.makeMangledName(enum_val_name); - } - - const enum_const_type_node: ?ZigNode = t.transType(scope, field.qt, field.name_tok) catch |err| switch (err) { - error.UnsupportedType => null, - else => |e| return e, - }; - - const val = t.tree.value_map.get(field_node).?; - const enum_const_def = try ZigTag.enum_constant.create(t.arena, .{ - .name = enum_val_name, - .is_public = toplevel, - .type = enum_const_type_node, - .value = try t.createIntNode(val), - }); - if (toplevel) - try t.addTopLevelDecl(enum_val_name, enum_const_def) - else { - try scope.appendNode(enum_const_def); - try bs.discardVariable(enum_val_name); - } - } - - break :blk t.transType(scope, enum_ty.tag.?, enum_decl.name_or_kind_tok) catch |err| switch (err) { - error.UnsupportedType => { - return t.failDecl(scope, enum_decl.name_or_kind_tok, name, "unable to translate enum integer type", .{}); - }, - else => |e| return e, - }; - } else blk: { - try t.opaque_demotes.put(t.gpa, base.qt, {}); - break :blk ZigTag.opaque_literal.init(); - }; - - const is_pub = toplevel and !is_unnamed; - const payload = try t.arena.create(ast.Payload.SimpleVarDecl); - payload.* = .{ - .base = .{ .tag = if (is_pub) .pub_var_simple else .var_simple }, - .data = .{ - .init = enum_type_node, - .name = name, - }, - }; - const node = ZigNode.initPayload(&payload.base); - if (toplevel) { - try t.addTopLevelDecl(name, node); - if (!is_unnamed) - try t.alias_list.append(t.gpa, .{ .alias = bare_name, .name = name }); - } else { - try scope.appendNode(node); - try bs.discardVariable(name); - } -} - -fn transStaticAssert(t: *Translator, scope: *Scope, static_assert: Node.StaticAssert) Error!void { - const condition = t.transExpr(scope, static_assert.cond, .used) catch |err| switch (err) { - error.UnsupportedTranslation, error.UnsupportedType => { - return try t.warn(&t.global_scope.base, static_assert.cond.tok(t.tree), "unable to translate _Static_assert condition", .{}); - }, - error.OutOfMemory => |e| return e, - }; - - // generate @compileError message that matches C compiler output - const diagnostic = if (static_assert.message) |message| str: { - // Aro guarantees this to be a string literal. - const str_val = t.tree.value_map.get(message).?; - const str_qt = message.qt(t.tree); - - const bytes = t.comp.interner.get(str_val.ref()).bytes; - var allocating: std.Io.Writer.Allocating = .init(t.gpa); - defer allocating.deinit(); - - allocating.writer.writeAll("\"static assertion failed \\") catch return error.OutOfMemory; - - aro.Value.printString(bytes, str_qt, t.comp, &allocating.writer) catch return error.OutOfMemory; - allocating.writer.end -= 1; // printString adds a terminating " so we need to remove it - allocating.writer.writeAll("\\\"\"") catch return error.OutOfMemory; - - break :str try ZigTag.string_literal.create(t.arena, try t.arena.dupe(u8, allocating.getWritten())); - } else try ZigTag.string_literal.create(t.arena, "\"static assertion failed\""); - - const assert_node = try ZigTag.static_assert.create(t.arena, .{ .lhs = condition, .rhs = diagnostic }); - try scope.appendNode(assert_node); -} - -fn transGlobalAsm(t: *Translator, scope: *Scope, global_asm: Node.SimpleAsm) Error!void { - const asm_string = t.tree.value_map.get(global_asm.asm_str).?; - const bytes = t.comp.interner.get(asm_string.ref()).bytes; - - var allocating: std.Io.Writer.Allocating = try .initCapacity(t.gpa, bytes.len); - defer allocating.deinit(); - aro.Value.printString(bytes, global_asm.asm_str.qt(t.tree), t.comp, &allocating.writer) catch return error.OutOfMemory; - - const str_node = try ZigTag.string_literal.create(t.arena, try t.arena.dupe(u8, allocating.getWritten())); - - const asm_node = try ZigTag.asm_simple.create(t.arena, str_node); - const block = try ZigTag.block_single.create(t.arena, asm_node); - const comptime_node = try ZigTag.@"comptime".create(t.arena, block); - - try scope.appendNode(comptime_node); -} - -// ================ -// Type translation -// ================ - -fn getTypeStr(t: *Translator, qt: QualType) ![]const u8 { - var allocating: std.Io.Writer.Allocating = .init(t.gpa); - defer allocating.deinit(); - qt.print(t.comp, &allocating.writer) catch return error.OutOfMemory; - return t.arena.dupe(u8, allocating.getWritten()); -} - -fn transType(t: *Translator, scope: *Scope, qt: QualType, source_loc: TokenIndex) TypeError!ZigNode { - loop: switch (qt.type(t.comp)) { - .atomic => { - const type_name = try t.getTypeStr(qt); - return t.fail(error.UnsupportedType, source_loc, "TODO support atomic type: '{s}'", .{type_name}); - }, - .void => return ZigTag.type.create(t.arena, "anyopaque"), - .bool => return ZigTag.type.create(t.arena, "bool"), - .int => |int_ty| switch (int_ty) { - //.char => return ZigTag.type.create(t.arena, "c_char"), // TODO: this is the preferred translation - .char => return ZigTag.type.create(t.arena, "u8"), - .schar => return ZigTag.type.create(t.arena, "i8"), - .uchar => return ZigTag.type.create(t.arena, "u8"), - .short => return ZigTag.type.create(t.arena, "c_short"), - .ushort => return ZigTag.type.create(t.arena, "c_ushort"), - .int => return ZigTag.type.create(t.arena, "c_int"), - .uint => return ZigTag.type.create(t.arena, "c_uint"), - .long => return ZigTag.type.create(t.arena, "c_long"), - .ulong => return ZigTag.type.create(t.arena, "c_ulong"), - .long_long => return ZigTag.type.create(t.arena, "c_longlong"), - .ulong_long => return ZigTag.type.create(t.arena, "c_ulonglong"), - .int128 => return ZigTag.type.create(t.arena, "i128"), - .uint128 => return ZigTag.type.create(t.arena, "u128"), - }, - .float => |float_ty| switch (float_ty) { - .fp16, .float16 => return ZigTag.type.create(t.arena, "f16"), - .float => return ZigTag.type.create(t.arena, "f32"), - .double => return ZigTag.type.create(t.arena, "f64"), - .long_double => return ZigTag.type.create(t.arena, "c_longdouble"), - .float128 => return ZigTag.type.create(t.arena, "f128"), - }, - .pointer => |pointer_ty| { - const child_qt = pointer_ty.child; - - const is_fn_proto = child_qt.is(t.comp, .func); - const is_const = is_fn_proto or child_qt.@"const"; - const is_volatile = child_qt.@"volatile"; - const elem_type = try t.transType(scope, child_qt, source_loc); - const ptr_info: @FieldType(ast.Payload.Pointer, "data") = .{ - .is_const = is_const, - .is_volatile = is_volatile, - .elem_type = elem_type, - .is_allowzero = false, - }; - if (is_fn_proto or - t.typeIsOpaque(child_qt) or - t.typeWasDemotedToOpaque(child_qt)) - { - const ptr = try ZigTag.single_pointer.create(t.arena, ptr_info); - return ZigTag.optional_type.create(t.arena, ptr); - } - - return ZigTag.c_pointer.create(t.arena, ptr_info); - }, - .array => |array_ty| { - const elem_qt = array_ty.elem; - switch (array_ty.len) { - .incomplete, .unspecified_variable => { - const elem_type = try t.transType(scope, elem_qt, source_loc); - return ZigTag.c_pointer.create(t.arena, .{ - .is_const = elem_qt.@"const", - .is_volatile = elem_qt.@"volatile", - .is_allowzero = false, - .elem_type = elem_type, - }); - }, - .fixed, .static => |len| { - const elem_type = try t.transType(scope, elem_qt, source_loc); - return ZigTag.array_type.create(t.arena, .{ .len = len, .elem_type = elem_type }); - }, - .variable => return t.fail(error.UnsupportedType, source_loc, "VLA unsupported '{s}'", .{try t.getTypeStr(qt)}), - } - }, - .func => |func_ty| return t.transFnType(scope, qt, func_ty, source_loc, .{}), - .@"struct", .@"union" => |record_ty| { - var trans_scope = scope; - if (!record_ty.isAnonymous(t.comp)) { - if (t.weak_global_names.contains(record_ty.name.lookup(t.comp))) trans_scope = &t.global_scope.base; - } - try t.transRecordDecl(trans_scope, qt); - const name = t.type_decls.get(record_ty.decl_node).?; - return ZigTag.identifier.create(t.arena, name); - }, - .@"enum" => |enum_ty| { - var trans_scope = scope; - const is_anonymous = enum_ty.isAnonymous(t.comp); - if (!is_anonymous) { - if (t.weak_global_names.contains(enum_ty.name.lookup(t.comp))) trans_scope = &t.global_scope.base; - } - try t.transEnumDecl(trans_scope, qt); - const name = t.type_decls.get(enum_ty.decl_node).?; - return ZigTag.identifier.create(t.arena, name); - }, - .typedef => |typedef_ty| { - var trans_scope = scope; - const typedef_name = typedef_ty.name.lookup(t.comp); - if (builtin_typedef_map.get(typedef_name)) |builtin| return ZigTag.type.create(t.arena, builtin); - if (t.global_names.contains(typedef_name)) trans_scope = &t.global_scope.base; - - try t.transTypeDef(trans_scope, typedef_ty.decl_node); - const name = t.type_decls.get(typedef_ty.decl_node).?; - return ZigTag.identifier.create(t.arena, name); - }, - .attributed => |attributed_ty| continue :loop attributed_ty.base.type(t.comp), - .typeof => |typeof_ty| continue :loop typeof_ty.base.type(t.comp), - .vector => |vector_ty| { - const len = try t.createNumberNode(vector_ty.len, .int); - const elem_type = try t.transType(scope, vector_ty.elem, source_loc); - return ZigTag.vector.create(t.arena, .{ .lhs = len, .rhs = elem_type }); - }, - else => return t.fail(error.UnsupportedType, source_loc, "unsupported type: '{s}'", .{try t.getTypeStr(qt)}), - } -} - -/// Look ahead through the fields of the record to determine what the alignment of the record -/// would be without any align/packed/etc. attributes. This helps us determine whether or not -/// the fields with 0 offset need an `align` qualifier. Strictly speaking, we could just -/// pedantically assign those fields the same alignment as the parent's pointer alignment, -/// but this helps the generated code to be a little less verbose. -fn headFieldAlignment(t: *Translator, record_decl: aro.Type.Record) ?c_uint { - const bits_per_byte = 8; - const parent_ptr_alignment_bits = record_decl.layout.?.pointer_alignment_bits; - const parent_ptr_alignment = parent_ptr_alignment_bits / bits_per_byte; - var max_field_alignment_bits: u64 = 0; - for (record_decl.fields) |field| { - if (field.qt.getRecord(t.comp)) |field_record_decl| { - const child_record_alignment = field_record_decl.layout.?.field_alignment_bits; - if (child_record_alignment > max_field_alignment_bits) - max_field_alignment_bits = child_record_alignment; - } else { - const field_size = field.layout.size_bits; - if (field_size > max_field_alignment_bits) - max_field_alignment_bits = field_size; - } - } - if (max_field_alignment_bits != parent_ptr_alignment_bits) { - return parent_ptr_alignment; - } else { - return null; - } -} - -/// This function inspects the generated layout of a record to determine the alignment for a -/// particular field. This approach is necessary because unlike Zig, a C compiler is not -/// required to fulfill the requested alignment, which means we'd risk generating different code -/// if we only look at the user-requested alignment. -/// -/// Returns a ?c_uint to match Clang's behavior of using c_uint. The return type can be changed -/// after the Clang frontend for translate-c is removed. A null value indicates that a field is -/// 'naturally aligned'. -fn alignmentForField( - t: *Translator, - record_decl: aro.Type.Record, - head_field_alignment: ?c_uint, - field_index: usize, -) ?c_uint { - const fields = record_decl.fields; - assert(fields.len != 0); - const field = fields[field_index]; - - const bits_per_byte = 8; - const parent_ptr_alignment_bits = record_decl.layout.?.pointer_alignment_bits; - const parent_ptr_alignment = parent_ptr_alignment_bits / bits_per_byte; - - // bitfields aren't supported yet. Until support is added, records with bitfields - // should be demoted to opaque, and this function shouldn't be called for them. - if (field.bit_width != .null) { - @panic("TODO: add bitfield support for records"); - } - - const field_offset_bits: u64 = field.layout.offset_bits; - const field_size_bits: u64 = field.layout.size_bits; - - // Fields with zero width always have an alignment of 1 - if (field_size_bits == 0) { - return 1; - } - - // Fields with 0 offset inherit the parent's pointer alignment. - if (field_offset_bits == 0) { - return head_field_alignment; - } - - // Records have a natural alignment when used as a field, and their size is - // a multiple of this alignment value. For all other types, the natural alignment - // is their size. - const field_natural_alignment_bits: u64 = if (field.qt.getRecord(t.comp)) |record| - record.layout.?.field_alignment_bits - else - field_size_bits; - const rem_bits = field_offset_bits % field_natural_alignment_bits; - - // If there's a remainder, then the alignment is smaller than the field's - // natural alignment - if (rem_bits > 0) { - const rem_alignment = rem_bits / bits_per_byte; - if (rem_alignment > 0 and std.math.isPowerOfTwo(rem_alignment)) { - const actual_alignment = @min(rem_alignment, parent_ptr_alignment); - return @as(c_uint, @truncate(actual_alignment)); - } else { - return 1; - } - } - - // A field may have an offset which positions it to be naturally aligned, but the - // parent's pointer alignment determines if this is actually true, so we take the minimum - // value. - // For example, a float field (4 bytes wide) with a 4 byte offset is positioned to have natural - // alignment, but if the parent pointer alignment is 2, then the actual alignment of the - // float is 2. - const field_natural_alignment: u64 = field_natural_alignment_bits / bits_per_byte; - const offset_alignment = field_offset_bits / bits_per_byte; - const possible_alignment = @min(parent_ptr_alignment, offset_alignment); - if (possible_alignment == field_natural_alignment) { - return null; - } else if (possible_alignment < field_natural_alignment) { - if (std.math.isPowerOfTwo(possible_alignment)) { - return possible_alignment; - } else { - return 1; - } - } else { // possible_alignment > field_natural_alignment - // Here, the field is positioned be at a higher alignment than it's natural alignment. This means we - // need to determine whether it's a specified alignment. We can determine that from the padding preceding - // the field. - const padding_from_prev_field: u64 = blk: { - if (field_offset_bits != 0) { - const previous_field = fields[field_index - 1]; - break :blk (field_offset_bits - previous_field.layout.offset_bits) - previous_field.layout.size_bits; - } else { - break :blk 0; - } - }; - if (padding_from_prev_field < field_natural_alignment_bits) { - return null; - } else { - return possible_alignment; - } - } -} - -const FnProtoContext = struct { - is_pub: bool = false, - is_export: bool = false, - is_extern: bool = false, - is_always_inline: bool = false, - fn_name: ?[]const u8 = null, - has_body: bool = false, - cc: ast.Payload.Func.CallingConvention = .c, -}; - -fn transFnType( - t: *Translator, - scope: *Scope, - func_qt: QualType, - func_ty: aro.Type.Func, - source_loc: TokenIndex, - ctx: FnProtoContext, -) !ZigNode { - const param_count: usize = func_ty.params.len; - const fn_params = try t.arena.alloc(ast.Payload.Param, param_count); - - for (func_ty.params, fn_params) |param_info, *param_node| { - const param_qt = param_info.qt; - const is_noalias = param_qt.restrict; - - const param_name: ?[]const u8 = if (param_info.name == .empty) - null - else - param_info.name.lookup(t.comp); - - const type_node = try t.transType(scope, param_qt, param_info.name_tok); - param_node.* = .{ - .is_noalias = is_noalias, - .name = param_name, - .type = type_node, - }; - } - - const linksection_string = blk: { - if (func_qt.getAttribute(t.comp, .section)) |section| { - break :blk t.comp.interner.get(section.name.ref()).bytes; - } - break :blk null; - }; - - const alignment: ?c_uint = func_qt.requestedAlignment(t.comp) orelse null; - - const explicit_callconv = if ((ctx.is_always_inline or ctx.is_export or ctx.is_extern) and ctx.cc == .c) null else ctx.cc; - - const return_type_node = blk: { - if (func_qt.getAttribute(t.comp, .noreturn) != null) { - break :blk ZigTag.noreturn_type.init(); - } else { - const return_qt = func_ty.return_type; - if (return_qt.is(t.comp, .void)) { - // convert primitive anyopaque to actual void (only for return type) - break :blk ZigTag.void_type.init(); - } else { - break :blk t.transType(scope, return_qt, source_loc) catch |err| switch (err) { - error.UnsupportedType => { - try t.warn(scope, source_loc, "unsupported function proto return type", .{}); - return err; - }, - error.OutOfMemory => |e| return e, - }; - } - } - }; - - const payload = try t.arena.create(ast.Payload.Func); - payload.* = .{ - .base = .{ .tag = .func }, - .data = .{ - .is_pub = ctx.is_pub, - .is_extern = ctx.is_extern, - .is_export = ctx.is_export, - .is_inline = ctx.is_always_inline, - .is_var_args = switch (func_ty.kind) { - .normal => false, - .variadic => true, - .old_style => !ctx.is_export and !ctx.is_always_inline and !ctx.has_body, - }, - .name = ctx.fn_name, - .linksection_string = linksection_string, - .explicit_callconv = explicit_callconv, - .params = fn_params, - .return_type = return_type_node, - .body = null, - .alignment = alignment, - }, - }; - return ZigNode.initPayload(&payload.base); -} - -/// Produces a Zig AST node by translating a Type, respecting the width, but modifying the signed-ness. -/// Asserts the type is an integer. -fn transTypeIntWidthOf(t: *Translator, qt: QualType, is_signed: bool) TypeError!ZigNode { - return ZigTag.type.create(t.arena, loop: switch (qt.base(t.comp).type) { - .int => |int_ty| switch (int_ty) { - .char, .schar, .uchar => if (is_signed) "i8" else "u8", - .short, .ushort => if (is_signed) "c_short" else "c_ushort", - .int, .uint => if (is_signed) "c_int" else "c_uint", - .long, .ulong => if (is_signed) "c_long" else "c_ulong", - .long_long, .ulong_long => if (is_signed) "c_longlong" else "c_ulonglong", - .int128, .uint128 => if (is_signed) "i128" else "u128", - }, - .bit_int => |bit_int_ty| try std.fmt.allocPrint(t.arena, "{s}{d}", .{ - if (is_signed) "i" else "u", - bit_int_ty.bits, - }), - .@"enum" => |enum_ty| blk: { - const tag_ty = enum_ty.tag orelse - break :blk if (is_signed) "c_int" else "c_uint"; - - continue :loop tag_ty.base(t.comp).type; - }, - else => unreachable, // only call this function when it has already been determined the type is int - }); -} - -fn transTypeInit( - t: *Translator, - scope: *Scope, - qt: QualType, - init: Node.Index, - source_loc: TokenIndex, -) TypeError!ZigNode { - switch (init.get(t.tree)) { - .string_literal_expr => |literal| { - const elem_ty = try t.transType(scope, qt.childType(t.comp), source_loc); - - const string_lit_size = literal.qt.arrayLen(t.comp).?; - const array_size = qt.arrayLen(t.comp).?; - - if (array_size == string_lit_size) { - return ZigTag.null_sentinel_array_type.create(t.arena, .{ .len = array_size - 1, .elem_type = elem_ty }); - } else { - return ZigTag.array_type.create(t.arena, .{ .len = array_size, .elem_type = elem_ty }); - } - }, - else => {}, - } - return t.transType(scope, qt, source_loc); -} - -// ============ -// Type helpers -// ============ - -fn typeIsOpaque(t: *Translator, qt: QualType) bool { - return switch (qt.base(t.comp).type) { - .void => true, - .@"struct", .@"union" => |record_ty| { - if (record_ty.layout == null) return true; - for (record_ty.fields) |field| { - if (field.bit_width != .null) return true; - } - return false; - }, - else => false, - }; -} - -fn typeWasDemotedToOpaque(t: *Translator, qt: QualType) bool { - const base = qt.base(t.comp); - switch (base.type) { - .@"struct", .@"union" => |record_ty| { - if (t.opaque_demotes.contains(base.qt)) return true; - for (record_ty.fields) |field| { - if (t.typeWasDemotedToOpaque(field.qt)) return true; - } - return false; - }, - .@"enum" => return t.opaque_demotes.contains(base.qt), - else => return false, - } -} - -fn typeHasWrappingOverflow(t: *Translator, qt: QualType) bool { - if (t.signedness(qt) == .unsigned) { - // unsigned integer overflow wraps around. - return true; - } else { - // float, signed integer, and pointer overflow is undefined behavior. - return false; - } -} - -/// Signedness of type when translated to Zig. -/// Different from `QualType.signedness()` for `char` and enums. -/// Returns null for non-int types. -fn signedness(t: *Translator, qt: QualType) ?std.builtin.Signedness { - return loop: switch (qt.base(t.comp).type) { - .bool => .unsigned, - .bit_int => |bit_int| bit_int.signedness, - .int => |int_ty| switch (int_ty) { - .char => .unsigned, // Always translated as u8 - .schar, .short, .int, .long, .long_long, .int128 => .signed, - .uchar, .ushort, .uint, .ulong, .ulong_long, .uint128 => .unsigned, - }, - .@"enum" => |enum_ty| { - const tag_qt = enum_ty.tag orelse return .signed; - continue :loop tag_qt.base(t.comp).type; - }, - else => return null, - }; -} - -// ===================== -// Statement translation -// ===================== - -fn transStmt(t: *Translator, scope: *Scope, stmt: Node.Index) TransError!ZigNode { - switch (stmt.get(t.tree)) { - .compound_stmt => |compound| { - return t.transCompoundStmt(scope, compound); - }, - .static_assert => |static_assert| { - try t.transStaticAssert(scope, static_assert); - return ZigTag.declaration.init(); - }, - .return_stmt => |return_stmt| return t.transReturnStmt(scope, return_stmt), - .null_stmt => return ZigTag.empty_block.init(), - .if_stmt => |if_stmt| return t.transIfStmt(scope, if_stmt), - .while_stmt => |while_stmt| return t.transWhileStmt(scope, while_stmt), - .do_while_stmt => |do_while_stmt| return t.transDoWhileStmt(scope, do_while_stmt), - .for_stmt => |for_stmt| return t.transForStmt(scope, for_stmt), - .continue_stmt => return ZigTag.@"continue".init(), - .break_stmt => return ZigTag.@"break".init(), - .typedef => |typedef_decl| { - assert(!typedef_decl.implicit); - try t.transTypeDef(scope, stmt); - return ZigTag.declaration.init(); - }, - .struct_decl, .union_decl => |record_decl| { - try t.transRecordDecl(scope, record_decl.container_qt); - return ZigTag.declaration.init(); - }, - .enum_decl => |enum_decl| { - try t.transEnumDecl(scope, enum_decl.container_qt); - return ZigTag.declaration.init(); - }, - .function => |function| { - try t.transFnDecl(scope, function); - return ZigTag.declaration.init(); - }, - .variable => |variable| { - try t.transVarDecl(scope, variable); - return ZigTag.declaration.init(); - }, - .switch_stmt => |switch_stmt| return t.transSwitch(scope, switch_stmt), - .case_stmt, .default_stmt => { - return t.fail(error.UnsupportedTranslation, stmt.tok(t.tree), "TODO complex switch", .{}); - }, - .goto_stmt, .computed_goto_stmt, .labeled_stmt => { - return t.fail(error.UnsupportedTranslation, stmt.tok(t.tree), "TODO goto", .{}); - }, - else => return t.transExprCoercing(scope, stmt, .unused), - } -} - -fn transCompoundStmtInline(t: *Translator, compound: Node.CompoundStmt, block: *Scope.Block) TransError!void { - for (compound.body) |stmt| { - const result = try t.transStmt(&block.base, stmt); - switch (result.tag()) { - .declaration, .empty_block => {}, - else => try block.statements.append(t.gpa, result), - } - } -} - -fn transCompoundStmt(t: *Translator, scope: *Scope, compound: Node.CompoundStmt) TransError!ZigNode { - var block_scope = try Scope.Block.init(t, scope, false); - defer block_scope.deinit(); - try t.transCompoundStmtInline(compound, &block_scope); - return try block_scope.complete(); -} - -fn transReturnStmt(t: *Translator, scope: *Scope, return_stmt: Node.ReturnStmt) TransError!ZigNode { - switch (return_stmt.operand) { - .none => return ZigTag.return_void.init(), - .expr => |operand| { - var rhs = try t.transExprCoercing(scope, operand, .used); - const return_qt = scope.findBlockReturnType(); - if (rhs.isBoolRes() and !return_qt.is(t.comp, .bool)) { - rhs = try ZigTag.int_from_bool.create(t.arena, rhs); - } - return ZigTag.@"return".create(t.arena, rhs); - }, - .implicit => |zero| { - if (zero) return ZigTag.@"return".create(t.arena, ZigTag.zero_literal.init()); - - const return_qt = scope.findBlockReturnType(); - if (return_qt.is(t.comp, .void)) return ZigTag.empty_block.init(); - - return ZigTag.@"return".create(t.arena, ZigTag.undefined_literal.init()); - }, - } -} - -/// If a statement can possibly translate to a Zig assignment (either directly because it's -/// an assignment in C or indirectly via result assignment to `_`) AND it's the sole statement -/// in the body of an if statement or loop, then we need to put the statement into its own block. -/// The `else` case here corresponds to statements that could result in an assignment. If a statement -/// class never needs a block, add its enum to the top prong. -fn maybeBlockify(t: *Translator, scope: *Scope, stmt: Node.Index) TransError!ZigNode { - switch (stmt.get(t.tree)) { - .break_stmt, - .continue_stmt, - .compound_stmt, - .decl_ref_expr, - .enumeration_ref, - .do_while_stmt, - .for_stmt, - .if_stmt, - .return_stmt, - .null_stmt, - .while_stmt, - => return t.transStmt(scope, stmt), - else => return t.blockify(scope, stmt), - } -} - -/// Translate statement and place it in its own block. -fn blockify(t: *Translator, scope: *Scope, stmt: Node.Index) TransError!ZigNode { - var block_scope = try Scope.Block.init(t, scope, false); - defer block_scope.deinit(); - const result = try t.transStmt(&block_scope.base, stmt); - try block_scope.statements.append(t.gpa, result); - return block_scope.complete(); -} - -fn transIfStmt(t: *Translator, scope: *Scope, if_stmt: Node.IfStmt) TransError!ZigNode { - var cond_scope: Scope.Condition = .{ - .base = .{ - .parent = scope, - .id = .condition, - }, - }; - defer cond_scope.deinit(); - const cond = try t.transBoolExpr(&cond_scope.base, if_stmt.cond); - - // block needed to keep else statement from attaching to inner while - const must_blockify = (if_stmt.else_body != null) and switch (if_stmt.then_body.get(t.tree)) { - .while_stmt, .do_while_stmt, .for_stmt => true, - else => false, - }; - - const then_node = if (must_blockify) - try t.blockify(scope, if_stmt.then_body) - else - try t.maybeBlockify(scope, if_stmt.then_body); - - const else_node = if (if_stmt.else_body) |stmt| - try t.maybeBlockify(scope, stmt) - else - null; - return ZigTag.@"if".create(t.arena, .{ .cond = cond, .then = then_node, .@"else" = else_node }); -} - -fn transWhileStmt(t: *Translator, scope: *Scope, while_stmt: Node.WhileStmt) TransError!ZigNode { - var cond_scope: Scope.Condition = .{ - .base = .{ - .parent = scope, - .id = .condition, - }, - }; - defer cond_scope.deinit(); - const cond = try t.transBoolExpr(&cond_scope.base, while_stmt.cond); - - var loop_scope: Scope = .{ - .parent = scope, - .id = .loop, - }; - const body = try t.maybeBlockify(&loop_scope, while_stmt.body); - return ZigTag.@"while".create(t.arena, .{ .cond = cond, .body = body, .cont_expr = null }); -} - -fn transDoWhileStmt(t: *Translator, scope: *Scope, do_stmt: Node.DoWhileStmt) TransError!ZigNode { - var loop_scope: Scope = .{ - .parent = scope, - .id = .do_loop, - }; - - // if (!cond) break; - var cond_scope: Scope.Condition = .{ - .base = .{ - .parent = scope, - .id = .condition, - }, - }; - defer cond_scope.deinit(); - const cond = try t.transBoolExpr(&cond_scope.base, do_stmt.cond); - const if_not_break = switch (cond.tag()) { - .true_literal => { - const body_node = try t.maybeBlockify(scope, do_stmt.body); - return ZigTag.while_true.create(t.arena, body_node); - }, - else => try ZigTag.if_not_break.create(t.arena, cond), - }; - - var body_node = try t.transStmt(&loop_scope, do_stmt.body); - if (body_node.isNoreturn(true)) { - // The body node ends in a noreturn statement. Simply put it in a while (true) - // in case it contains breaks or continues. - } else if (do_stmt.body.get(t.tree) == .compound_stmt) { - // there's already a block in C, so we'll append our condition to it. - // c: do { - // c: a; - // c: b; - // c: } while(c); - // zig: while (true) { - // zig: a; - // zig: b; - // zig: if (!cond) break; - // zig: } - const block = body_node.castTag(.block).?; - block.data.stmts.len += 1; // This is safe since we reserve one extra space in Scope.Block.complete. - block.data.stmts[block.data.stmts.len - 1] = if_not_break; - } else { - // the C statement is without a block, so we need to create a block to contain it. - // c: do - // c: a; - // c: while(c); - // zig: while (true) { - // zig: a; - // zig: if (!cond) break; - // zig: } - const statements = try t.arena.alloc(ZigNode, 2); - statements[0] = body_node; - statements[1] = if_not_break; - body_node = try ZigTag.block.create(t.arena, .{ .label = null, .stmts = statements }); - } - return ZigTag.while_true.create(t.arena, body_node); -} - -fn transForStmt(t: *Translator, scope: *Scope, for_stmt: Node.ForStmt) TransError!ZigNode { - var loop_scope: Scope = .{ - .parent = scope, - .id = .loop, - }; - - var block_scope: ?Scope.Block = null; - defer if (block_scope) |*bs| bs.deinit(); - - switch (for_stmt.init) { - .decls => |decls| { - block_scope = try Scope.Block.init(t, scope, false); - loop_scope.parent = &block_scope.?.base; - for (decls) |decl| { - try t.transDecl(&block_scope.?.base, decl); - } - }, - .expr => |maybe_init| if (maybe_init) |init| { - block_scope = try Scope.Block.init(t, scope, false); - loop_scope.parent = &block_scope.?.base; - const init_node = try t.transStmt(&block_scope.?.base, init); - try loop_scope.appendNode(init_node); - }, - } - var cond_scope: Scope.Condition = .{ - .base = .{ - .parent = &loop_scope, - .id = .condition, - }, - }; - defer cond_scope.deinit(); - - const cond = if (for_stmt.cond) |cond| - try t.transBoolExpr(&cond_scope.base, cond) - else - ZigTag.true_literal.init(); - - const cont_expr = if (for_stmt.incr) |incr| - try t.transExpr(&cond_scope.base, incr, .unused) - else - null; - - const body = try t.maybeBlockify(&loop_scope, for_stmt.body); - const while_node = try ZigTag.@"while".create(t.arena, .{ .cond = cond, .body = body, .cont_expr = cont_expr }); - if (block_scope) |*bs| { - try bs.statements.append(t.gpa, while_node); - return try bs.complete(); - } else { - return while_node; - } -} - -fn transSwitch(t: *Translator, scope: *Scope, switch_stmt: Node.SwitchStmt) TransError!ZigNode { - var loop_scope: Scope = .{ - .parent = scope, - .id = .loop, - }; - - var block_scope = try Scope.Block.init(t, &loop_scope, false); - defer block_scope.deinit(); - - const base_scope = &block_scope.base; - - var cond_scope: Scope.Condition = .{ - .base = .{ - .parent = base_scope, - .id = .condition, - }, - }; - defer cond_scope.deinit(); - const switch_expr = try t.transExpr(&cond_scope.base, switch_stmt.cond, .used); - - var cases = std.ArrayList(ZigNode).init(t.gpa); - defer cases.deinit(); - var has_default = false; - - const body_node = switch_stmt.body.get(t.tree); - if (body_node != .compound_stmt) { - return t.fail(error.UnsupportedTranslation, switch_stmt.switch_tok, "TODO complex switch", .{}); - } - const body = body_node.compound_stmt.body; - // Iterate over switch body and collect all cases. - // Fallthrough is handled by duplicating statements. - for (body, 0..) |stmt, i| { - switch (stmt.get(t.tree)) { - .case_stmt => { - var items = std.ArrayList(ZigNode).init(t.gpa); - defer items.deinit(); - const sub = try t.transCaseStmt(base_scope, stmt, &items); - const res = try t.transSwitchProngStmt(base_scope, sub, body[i..]); - - if (items.items.len == 0) { - has_default = true; - const switch_else = try ZigTag.switch_else.create(t.arena, res); - try cases.append(switch_else); - } else { - const switch_prong = try ZigTag.switch_prong.create(t.arena, .{ - .cases = try t.arena.dupe(ZigNode, items.items), - .cond = res, - }); - try cases.append(switch_prong); - } - }, - .default_stmt => |default_stmt| { - has_default = true; - - var sub = default_stmt.body; - while (true) switch (sub.get(t.tree)) { - .case_stmt => |sub_case| sub = sub_case.body, - .default_stmt => |sub_default| sub = sub_default.body, - else => break, - }; - - const res = try t.transSwitchProngStmt(base_scope, sub, body[i..]); - - const switch_else = try ZigTag.switch_else.create(t.arena, res); - try cases.append(switch_else); - }, - else => {}, // collected in transSwitchProngStmt - } - } - - if (!has_default) { - const else_prong = try ZigTag.switch_else.create(t.arena, ZigTag.empty_block.init()); - try cases.append(else_prong); - } - - const switch_node = try ZigTag.@"switch".create(t.arena, .{ - .cond = switch_expr, - .cases = try t.arena.dupe(ZigNode, cases.items), - }); - try block_scope.statements.append(t.gpa, switch_node); - try block_scope.statements.append(t.gpa, ZigTag.@"break".init()); - const while_body = try block_scope.complete(); - - return ZigTag.while_true.create(t.arena, while_body); -} - -/// Collects all items for this case, returns the first statement after the labels. -/// If items ends up empty, the prong should be translated as an else. -fn transCaseStmt( - t: *Translator, - scope: *Scope, - stmt: Node.Index, - items: *std.ArrayList(ZigNode), -) TransError!Node.Index { - var sub = stmt; - var seen_default = false; - while (true) { - switch (sub.get(t.tree)) { - .default_stmt => |default_stmt| { - seen_default = true; - items.items.len = 0; - sub = default_stmt.body; - }, - .case_stmt => |case_stmt| { - if (seen_default) { - items.items.len = 0; - sub = case_stmt.body; - continue; - } - - const expr = if (case_stmt.end) |end| blk: { - const start_node = try t.transExpr(scope, case_stmt.start, .used); - const end_node = try t.transExpr(scope, end, .used); - - break :blk try ZigTag.ellipsis3.create(t.arena, .{ .lhs = start_node, .rhs = end_node }); - } else try t.transExpr(scope, case_stmt.start, .used); - - try items.append(expr); - sub = case_stmt.body; - }, - else => return sub, - } - } -} - -/// Collects all statements seen by this case into a block. -/// Avoids creating a block if the first statement is a break or return. -fn transSwitchProngStmt( - t: *Translator, - scope: *Scope, - stmt: Node.Index, - body: []const Node.Index, -) TransError!ZigNode { - switch (stmt.get(t.tree)) { - .break_stmt => return ZigTag.@"break".init(), - .return_stmt => return t.transStmt(scope, stmt), - .case_stmt, .default_stmt => unreachable, - else => { - var block_scope = try Scope.Block.init(t, scope, false); - defer block_scope.deinit(); - - // we do not need to translate `stmt` since it is the first stmt of `body` - try t.transSwitchProngStmtInline(&block_scope, body); - return try block_scope.complete(); - }, - } -} - -/// Collects all statements seen by this case into a block. -fn transSwitchProngStmtInline( - t: *Translator, - block: *Scope.Block, - body: []const Node.Index, -) TransError!void { - for (body) |stmt| { - switch (stmt.get(t.tree)) { - .return_stmt => { - const result = try t.transStmt(&block.base, stmt); - try block.statements.append(t.gpa, result); - return; - }, - .break_stmt => { - try block.statements.append(t.gpa, ZigTag.@"break".init()); - return; - }, - .case_stmt => |case_stmt| { - var sub = case_stmt.body; - while (true) switch (sub.get(t.tree)) { - .case_stmt => |sub_case| sub = sub_case.body, - .default_stmt => |sub_default| sub = sub_default.body, - else => break, - }; - const result = try t.transStmt(&block.base, sub); - assert(result.tag() != .declaration); - try block.statements.append(t.gpa, result); - if (result.isNoreturn(true)) return; - }, - .default_stmt => |default_stmt| { - var sub = default_stmt.body; - while (true) switch (sub.get(t.tree)) { - .case_stmt => |sub_case| sub = sub_case.body, - .default_stmt => |sub_default| sub = sub_default.body, - else => break, - }; - const result = try t.transStmt(&block.base, sub); - assert(result.tag() != .declaration); - try block.statements.append(t.gpa, result); - if (result.isNoreturn(true)) return; - }, - .compound_stmt => |compound_stmt| { - const result = try t.transCompoundStmt(&block.base, compound_stmt); - try block.statements.append(t.gpa, result); - if (result.isNoreturn(true)) return; - }, - else => { - const result = try t.transStmt(&block.base, stmt); - switch (result.tag()) { - .declaration, .empty_block => {}, - else => try block.statements.append(t.gpa, result), - } - }, - } - } -} - -// ====================== -// Expression translation -// ====================== - -const ResultUsed = enum { used, unused }; - -fn transExpr(t: *Translator, scope: *Scope, expr: Node.Index, used: ResultUsed) TransError!ZigNode { - const qt = expr.qt(t.tree); - return t.maybeSuppressResult(used, switch (expr.get(t.tree)) { - .paren_expr => |paren_expr| { - return t.transExpr(scope, paren_expr.operand, used); - }, - .cast => |cast| return t.transCastExpr(scope, cast, cast.qt, used, .with_as), - .decl_ref_expr => |decl_ref| try t.transDeclRefExpr(scope, decl_ref), - .enumeration_ref => |enum_ref| try t.transDeclRefExpr(scope, enum_ref), - .addr_of_expr => |addr_of_expr| try ZigTag.address_of.create(t.arena, try t.transExpr(scope, addr_of_expr.operand, .used)), - .deref_expr => |deref_expr| res: { - if (t.typeWasDemotedToOpaque(qt)) - return t.fail(error.UnsupportedTranslation, deref_expr.op_tok, "cannot dereference opaque type", .{}); - - // Dereferencing a function pointer is a no-op. - if (qt.is(t.comp, .func)) return t.transExpr(scope, deref_expr.operand, used); - - break :res try ZigTag.deref.create(t.arena, try t.transExpr(scope, deref_expr.operand, .used)); - }, - .bool_not_expr => |bool_not_expr| try ZigTag.not.create(t.arena, try t.transBoolExpr(scope, bool_not_expr.operand)), - .bit_not_expr => |bit_not_expr| try ZigTag.bit_not.create(t.arena, try t.transExpr(scope, bit_not_expr.operand, .used)), - .plus_expr => |plus_expr| return t.transExpr(scope, plus_expr.operand, used), - .negate_expr => |negate_expr| res: { - const operand_qt = negate_expr.operand.qt(t.tree); - if (!t.typeHasWrappingOverflow(operand_qt)) { - const sub_expr_node = try t.transExpr(scope, negate_expr.operand, .used); - const to_negate = if (sub_expr_node.isBoolRes()) blk: { - const ty_node = try ZigTag.type.create(t.arena, "c_int"); - const int_node = try ZigTag.int_from_bool.create(t.arena, sub_expr_node); - break :blk try ZigTag.as.create(t.arena, .{ .lhs = ty_node, .rhs = int_node }); - } else sub_expr_node; - - break :res try ZigTag.negate.create(t.arena, to_negate); - } else if (t.signedness(operand_qt) == .unsigned) { - // use -% x for unsigned integers - break :res try ZigTag.negate_wrap.create(t.arena, try t.transExpr(scope, negate_expr.operand, .used)); - } else return t.fail(error.UnsupportedTranslation, negate_expr.op_tok, "C negation with non float non integer", .{}); - }, - .div_expr => |div_expr| res: { - if (qt.isInt(t.comp) and t.signedness(qt) == .signed) { - // signed integer division uses @divTrunc - const lhs = try t.transExpr(scope, div_expr.lhs, .used); - const rhs = try t.transExpr(scope, div_expr.rhs, .used); - break :res try ZigTag.div_trunc.create(t.arena, .{ .lhs = lhs, .rhs = rhs }); - } - // unsigned/float division uses the operator - break :res try t.transBinExpr(scope, div_expr, .div); - }, - .mod_expr => |mod_expr| res: { - if (qt.isInt(t.comp) and t.signedness(qt) == .signed) { - // signed integer remainder uses __helpers.signedRemainder - const lhs = try t.transExpr(scope, mod_expr.lhs, .used); - const rhs = try t.transExpr(scope, mod_expr.rhs, .used); - break :res try t.createHelperCallNode(.signedRemainder, &.{ lhs, rhs }); - } - // unsigned/float division uses the operator - break :res try t.transBinExpr(scope, mod_expr, .mod); - }, - .add_expr => |add_expr| res: { - // `ptr + idx` and `idx + ptr` -> ptr + @as(usize, @bitCast(@as(isize, @intCast(idx)))) - const lhs_qt = add_expr.lhs.qt(t.tree); - const rhs_qt = add_expr.rhs.qt(t.tree); - if (qt.isPointer(t.comp) and (t.signedness(lhs_qt) == .signed or - t.signedness(rhs_qt) == .signed)) - { - break :res try t.transPointerArithmeticSignedOp(scope, add_expr, .add); - } - - if (t.signedness(qt) == .unsigned) { - break :res try t.transBinExpr(scope, add_expr, .add_wrap); - } else { - break :res try t.transBinExpr(scope, add_expr, .add); - } - }, - .sub_expr => |sub_expr| res: { - // `ptr - idx` -> ptr - @as(usize, @bitCast(@as(isize, @intCast(idx)))) - const lhs_qt = sub_expr.lhs.qt(t.tree); - const rhs_qt = sub_expr.rhs.qt(t.tree); - if (qt.isPointer(t.comp) and (t.signedness(lhs_qt) == .signed or - t.signedness(rhs_qt) == .signed)) - { - break :res try t.transPointerArithmeticSignedOp(scope, sub_expr, .sub); - } - - if (sub_expr.lhs.qt(t.tree).isPointer(t.comp) and sub_expr.rhs.qt(t.tree).isPointer(t.comp)) { - break :res try t.transPtrDiffExpr(scope, sub_expr); - } else if (t.signedness(qt) == .unsigned) { - break :res try t.transBinExpr(scope, sub_expr, .sub_wrap); - } else { - break :res try t.transBinExpr(scope, sub_expr, .sub); - } - }, - .mul_expr => |mul_expr| if (t.signedness(qt) == .unsigned) - try t.transBinExpr(scope, mul_expr, .mul_wrap) - else - try t.transBinExpr(scope, mul_expr, .mul), - - .less_than_expr => |lt| try t.transBinExpr(scope, lt, .less_than), - .greater_than_expr => |gt| try t.transBinExpr(scope, gt, .greater_than), - .less_than_equal_expr => |lte| try t.transBinExpr(scope, lte, .less_than_equal), - .greater_than_equal_expr => |gte| try t.transBinExpr(scope, gte, .greater_than_equal), - .equal_expr => |equal_expr| try t.transBinExpr(scope, equal_expr, .equal), - .not_equal_expr => |not_equal_expr| try t.transBinExpr(scope, not_equal_expr, .not_equal), - - .bool_and_expr => |bool_and_expr| try t.transBoolBinExpr(scope, bool_and_expr, .@"and"), - .bool_or_expr => |bool_or_expr| try t.transBoolBinExpr(scope, bool_or_expr, .@"or"), - - .bit_and_expr => |bit_and_expr| try t.transBinExpr(scope, bit_and_expr, .bit_and), - .bit_or_expr => |bit_or_expr| try t.transBinExpr(scope, bit_or_expr, .bit_or), - .bit_xor_expr => |bit_xor_expr| try t.transBinExpr(scope, bit_xor_expr, .bit_xor), - - .shl_expr => |shl_expr| try t.transShiftExpr(scope, shl_expr, .shl), - .shr_expr => |shr_expr| try t.transShiftExpr(scope, shr_expr, .shr), - - .member_access_expr => |member_access| try t.transMemberAccess(scope, .normal, member_access, null), - .member_access_ptr_expr => |member_access| try t.transMemberAccess(scope, .ptr, member_access, null), - .array_access_expr => |array_access| try t.transArrayAccess(scope, array_access, null), - - .builtin_ref => unreachable, - .builtin_call_expr => |call| return t.transBuiltinCall(scope, call, used), - .call_expr => |call| return t.transCall(scope, call, used), - - .builtin_types_compatible_p => |compatible| blk: { - const lhs = try t.transType(scope, compatible.lhs, compatible.builtin_tok); - const rhs = try t.transType(scope, compatible.rhs, compatible.builtin_tok); - - break :blk try ZigTag.equal.create(t.arena, .{ - .lhs = lhs, - .rhs = rhs, - }); - }, - .builtin_choose_expr => |choose| return t.transCondExpr(scope, choose, used), - .cond_expr => |cond_expr| return t.transCondExpr(scope, cond_expr, used), - .binary_cond_expr => |conditional| return t.transBinaryCondExpr(scope, conditional, used), - .cond_dummy_expr => unreachable, - - .assign_expr => |assign| return t.transAssignExpr(scope, assign, used), - .add_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), - .sub_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), - .mul_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), - .div_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), - .mod_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), - .shl_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), - .shr_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), - .bit_and_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), - .bit_xor_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), - .bit_or_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), - .compound_assign_dummy_expr => { - assert(used == .used); - return t.compound_assign_dummy.?; - }, - - .comma_expr => |comma_expr| return t.transCommaExpr(scope, comma_expr, used), - .pre_inc_expr => |un| return t.transIncDecExpr(scope, un, .pre, .inc, used), - .pre_dec_expr => |un| return t.transIncDecExpr(scope, un, .pre, .dec, used), - .post_inc_expr => |un| return t.transIncDecExpr(scope, un, .post, .inc, used), - .post_dec_expr => |un| return t.transIncDecExpr(scope, un, .post, .dec, used), - - .int_literal => return t.transIntLiteral(scope, expr, used, .with_as), - .char_literal => return t.transCharLiteral(scope, expr, used, .with_as), - .float_literal => return t.transFloatLiteral(scope, expr, used, .with_as), - .string_literal_expr => |literal| try t.transStringLiteral(scope, expr, literal), - .bool_literal => res: { - const val = t.tree.value_map.get(expr).?; - break :res if (val.toBool(t.comp)) - ZigTag.true_literal.init() - else - ZigTag.false_literal.init(); - }, - .nullptr_literal => ZigTag.null_literal.init(), - .imaginary_literal => |literal| { - return t.fail(error.UnsupportedTranslation, literal.op_tok, "TODO complex numbers", .{}); - }, - .compound_literal_expr => |literal| return t.transCompoundLiteral(scope, literal, used), - - .default_init_expr => |default_init| return t.transDefaultInit(scope, default_init, used, .with_as), - .array_init_expr => |array_init| return t.transArrayInit(scope, array_init, used), - .union_init_expr => |union_init| return t.transUnionInit(scope, union_init, used), - .struct_init_expr => |struct_init| return t.transStructInit(scope, struct_init, used), - .array_filler_expr => unreachable, - - .sizeof_expr => |sizeof| try t.transTypeInfo(scope, .sizeof, sizeof), - .alignof_expr => |alignof| try t.transTypeInfo(scope, .alignof, alignof), - - .imag_expr, .real_expr => |un| { - return t.fail(error.UnsupportedTranslation, un.op_tok, "TODO complex numbers", .{}); - }, - .addr_of_label => |addr_of_label| { - return t.fail(error.UnsupportedTranslation, addr_of_label.label_tok, "TODO computed goto", .{}); - }, - - .generic_expr => |generic| return t.transExpr(scope, generic.chosen, used), - .generic_association_expr => |generic| return t.transExpr(scope, generic.expr, used), - .generic_default_expr => |generic| return t.transExpr(scope, generic.expr, used), - - .stmt_expr => |stmt_expr| return t.transStmtExpr(scope, stmt_expr, used), - - .builtin_convertvector => |convertvector| try t.transConvertvectorExpr(scope, convertvector), - .builtin_shufflevector => |shufflevector| try t.transShufflevectorExpr(scope, shufflevector), - - .compound_stmt, - .static_assert, - .return_stmt, - .null_stmt, - .if_stmt, - .while_stmt, - .do_while_stmt, - .for_stmt, - .continue_stmt, - .break_stmt, - .labeled_stmt, - .switch_stmt, - .case_stmt, - .default_stmt, - .goto_stmt, - .computed_goto_stmt, - .gnu_asm_simple, - .global_asm, - .typedef, - .struct_decl, - .union_decl, - .enum_decl, - .function, - .param, - .variable, - .enum_field, - .record_field, - .struct_forward_decl, - .union_forward_decl, - .enum_forward_decl, - .empty_decl, - => unreachable, // not an expression - }); -} - -/// Same as `transExpr` but with the knowledge that the operand will be type coerced, and therefore -/// an `@as` would be redundant. This is used to prevent redundant `@as` in integer literals. -fn transExprCoercing(t: *Translator, scope: *Scope, expr: Node.Index, used: ResultUsed) TransError!ZigNode { - switch (expr.get(t.tree)) { - .int_literal => return t.transIntLiteral(scope, expr, used, .no_as), - .char_literal => return t.transCharLiteral(scope, expr, used, .no_as), - .float_literal => return t.transFloatLiteral(scope, expr, used, .no_as), - .cast => |cast| switch (cast.kind) { - .no_op => { - const operand = cast.operand.get(t.tree); - if (operand == .cast) { - return t.transCastExpr(scope, operand.cast, cast.qt, used, .no_as); - } - return t.transExprCoercing(scope, cast.operand, used); - }, - .lval_to_rval => return t.transExprCoercing(scope, cast.operand, used), - else => return t.transCastExpr(scope, cast, cast.qt, used, .no_as), - }, - .default_init_expr => |default_init| return try t.transDefaultInit(scope, default_init, used, .no_as), - .compound_literal_expr => |literal| { - if (!literal.thread_local and literal.storage_class != .static) { - return t.transExprCoercing(scope, literal.initializer, used); - } - }, - else => {}, - } - - return t.transExpr(scope, expr, used); -} - -fn transBoolExpr(t: *Translator, scope: *Scope, expr: Node.Index) TransError!ZigNode { - switch (expr.get(t.tree)) { - .int_literal => { - const int_val = t.tree.value_map.get(expr).?; - return if (int_val.isZero(t.comp)) - ZigTag.false_literal.init() - else - ZigTag.true_literal.init(); - }, - .cast => |cast| switch (cast.kind) { - .bool_to_int => return t.transExpr(scope, cast.operand, .used), - .array_to_pointer => { - const operand = cast.operand.get(t.tree); - if (operand == .string_literal_expr) { - // @intFromPtr("foo") != 0, always true - const str = try t.transStringLiteral(scope, cast.operand, operand.string_literal_expr); - const int_from_ptr = try ZigTag.int_from_ptr.create(t.arena, str); - return ZigTag.not_equal.create(t.arena, .{ .lhs = int_from_ptr, .rhs = ZigTag.zero_literal.init() }); - } - }, - else => {}, - }, - else => {}, - } - - const maybe_bool_res = try t.transExpr(scope, expr, .used); - if (maybe_bool_res.isBoolRes()) { - return maybe_bool_res; - } - - return t.finishBoolExpr(expr.qt(t.tree), maybe_bool_res); -} - -fn finishBoolExpr(t: *Translator, qt: QualType, node: ZigNode) TransError!ZigNode { - const sk = qt.scalarKind(t.comp); - if (sk == .bool) return node; - if (sk == .nullptr_t) { - // node == null, always true - return ZigTag.equal.create(t.arena, .{ .lhs = node, .rhs = ZigTag.null_literal.init() }); - } - if (sk.isPointer()) { - // node != null - return ZigTag.not_equal.create(t.arena, .{ .lhs = node, .rhs = ZigTag.null_literal.init() }); - } - if (sk != .none) { - // node != 0 - return ZigTag.not_equal.create(t.arena, .{ .lhs = node, .rhs = ZigTag.zero_literal.init() }); - } - unreachable; // Unexpected bool expression type -} - -fn transCastExpr( - t: *Translator, - scope: *Scope, - cast: Node.Cast, - dest_qt: QualType, - used: ResultUsed, - suppress_as: SuppressCast, -) TransError!ZigNode { - const operand = switch (cast.kind) { - .no_op => { - const operand = cast.operand.get(t.tree); - if (operand == .cast) { - return t.transCastExpr(scope, operand.cast, cast.qt, used, suppress_as); - } - return t.transExpr(scope, cast.operand, used); - }, - .lval_to_rval, .function_to_pointer => { - return t.transExpr(scope, cast.operand, used); - }, - .int_cast => int_cast: { - const src_qt = cast.operand.qt(t.tree); - - if (cast.implicit) { - if (t.tree.value_map.get(cast.operand)) |val| { - const max_int = try aro.Value.maxInt(dest_qt, t.comp); - const min_int = try aro.Value.minInt(dest_qt, t.comp); - - if (val.compare(.lte, max_int, t.comp) and val.compare(.gte, min_int, t.comp)) { - break :int_cast try t.transExprCoercing(scope, cast.operand, .used); - } - } - } - const operand = try t.transExpr(scope, cast.operand, .used); - break :int_cast try t.transIntCast(operand, src_qt, dest_qt); - }, - .to_void => { - assert(used == .unused); - return try t.transExpr(scope, cast.operand, .unused); - }, - .null_to_pointer => ZigTag.null_literal.init(), - .array_to_pointer => array_to_pointer: { - const child_qt = dest_qt.childType(t.comp); - - loop: switch (cast.operand.get(t.tree)) { - .string_literal_expr => |literal| { - const sub_expr_node = try t.transExpr(scope, cast.operand, .used); - - const ref = if (literal.kind == .utf8 or literal.kind == .ascii) - sub_expr_node - else - try ZigTag.address_of.create(t.arena, sub_expr_node); - - const casted = if (child_qt.@"const") - ref - else - try ZigTag.const_cast.create(t.arena, sub_expr_node); - - return t.maybeSuppressResult(used, casted); - }, - .paren_expr => |paren_expr| { - continue :loop paren_expr.operand.get(t.tree); - }, - .generic_expr => |generic| { - continue :loop generic.chosen.get(t.tree); - }, - .generic_association_expr => |generic| { - continue :loop generic.expr.get(t.tree); - }, - .generic_default_expr => |generic| { - continue :loop generic.expr.get(t.tree); - }, - else => {}, - } - - if (cast.operand.qt(t.tree).arrayLen(t.comp) == null) { - return try t.transExpr(scope, cast.operand, used); - } - - const sub_expr_node = try t.transExpr(scope, cast.operand, .used); - const ref = try ZigTag.address_of.create(t.arena, sub_expr_node); - const align_cast = try ZigTag.align_cast.create(t.arena, ref); - break :array_to_pointer try ZigTag.ptr_cast.create(t.arena, align_cast); - }, - .int_to_pointer => int_to_pointer: { - var sub_expr_node = try t.transExpr(scope, cast.operand, .used); - const operand_qt = cast.operand.qt(t.tree); - if (t.signedness(operand_qt) == .signed or operand_qt.bitSizeof(t.comp) > t.comp.target.ptrBitWidth()) { - sub_expr_node = try ZigTag.as.create(t.arena, .{ - .lhs = try ZigTag.type.create(t.arena, "usize"), - .rhs = try ZigTag.int_cast.create(t.arena, sub_expr_node), - }); - } - break :int_to_pointer try ZigTag.ptr_from_int.create(t.arena, sub_expr_node); - }, - .int_to_bool => { - const sub_expr_node = try t.transExpr(scope, cast.operand, .used); - if (sub_expr_node.isBoolRes()) return sub_expr_node; - if (cast.operand.qt(t.tree).is(t.comp, .bool)) return sub_expr_node; - const cmp_node = try ZigTag.not_equal.create(t.arena, .{ .lhs = sub_expr_node, .rhs = ZigTag.zero_literal.init() }); - return t.maybeSuppressResult(used, cmp_node); - }, - .float_to_bool => { - const sub_expr_node = try t.transExpr(scope, cast.operand, .used); - const cmp_node = try ZigTag.not_equal.create(t.arena, .{ .lhs = sub_expr_node, .rhs = ZigTag.zero_literal.init() }); - return t.maybeSuppressResult(used, cmp_node); - }, - .pointer_to_bool => { - const sub_expr_node = try t.transExpr(scope, cast.operand, .used); - - // Special case function pointers as @intFromPtr(expr) != 0 - if (cast.operand.qt(t.tree).get(t.comp, .pointer)) |ptr_ty| if (ptr_ty.child.is(t.comp, .func)) { - const ptr_node = if (sub_expr_node.tag() == .identifier) - try ZigTag.address_of.create(t.arena, sub_expr_node) - else - sub_expr_node; - const int_from_ptr = try ZigTag.int_from_ptr.create(t.arena, ptr_node); - const cmp_node = try ZigTag.not_equal.create(t.arena, .{ .lhs = int_from_ptr, .rhs = ZigTag.zero_literal.init() }); - return t.maybeSuppressResult(used, cmp_node); - }; - - const cmp_node = try ZigTag.not_equal.create(t.arena, .{ .lhs = sub_expr_node, .rhs = ZigTag.null_literal.init() }); - return t.maybeSuppressResult(used, cmp_node); - }, - .bool_to_int => bool_to_int: { - const sub_expr_node = try t.transExpr(scope, cast.operand, .used); - break :bool_to_int try ZigTag.int_from_bool.create(t.arena, sub_expr_node); - }, - .bool_to_float => bool_to_float: { - const sub_expr_node = try t.transExpr(scope, cast.operand, .used); - const int_from_bool = try ZigTag.int_from_bool.create(t.arena, sub_expr_node); - break :bool_to_float try ZigTag.float_from_int.create(t.arena, int_from_bool); - }, - .bool_to_pointer => bool_to_pointer: { - const sub_expr_node = try t.transExpr(scope, cast.operand, .used); - const int_from_bool = try ZigTag.int_from_bool.create(t.arena, sub_expr_node); - break :bool_to_pointer try ZigTag.ptr_from_int.create(t.arena, int_from_bool); - }, - .float_cast => float_cast: { - const sub_expr_node = try t.transExpr(scope, cast.operand, .used); - break :float_cast try ZigTag.float_cast.create(t.arena, sub_expr_node); - }, - .int_to_float => int_to_float: { - const sub_expr_node = try t.transExpr(scope, cast.operand, used); - const int_node = if (sub_expr_node.isBoolRes()) - try ZigTag.int_from_bool.create(t.arena, sub_expr_node) - else - sub_expr_node; - break :int_to_float try ZigTag.float_from_int.create(t.arena, int_node); - }, - .float_to_int => float_to_int: { - const sub_expr_node = try t.transExpr(scope, cast.operand, .used); - break :float_to_int try ZigTag.int_from_float.create(t.arena, sub_expr_node); - }, - .pointer_to_int => pointer_to_int: { - const sub_expr_node = try t.transPointerCastExpr(scope, cast.operand); - const ptr_node = try ZigTag.int_from_ptr.create(t.arena, sub_expr_node); - break :pointer_to_int try ZigTag.int_cast.create(t.arena, ptr_node); - }, - .bitcast => bitcast: { - const sub_expr_node = try t.transPointerCastExpr(scope, cast.operand); - const operand_qt = cast.operand.qt(t.tree); - if (dest_qt.isPointer(t.comp) and operand_qt.isPointer(t.comp)) { - var casted = try ZigTag.align_cast.create(t.arena, sub_expr_node); - casted = try ZigTag.ptr_cast.create(t.arena, casted); - - const src_elem = operand_qt.childType(t.comp); - const dest_elem = dest_qt.childType(t.comp); - if ((src_elem.@"const" or src_elem.is(t.comp, .func)) and !dest_elem.@"const") { - casted = try ZigTag.const_cast.create(t.arena, casted); - } - if (src_elem.@"volatile" and !dest_elem.@"volatile") { - casted = try ZigTag.volatile_cast.create(t.arena, casted); - } - break :bitcast casted; - } - - break :bitcast try ZigTag.bit_cast.create(t.arena, sub_expr_node); - }, - .union_cast => union_cast: { - const union_type = try t.transType(scope, dest_qt, cast.l_paren); - - const operand_qt = cast.operand.qt(t.tree); - const union_base = dest_qt.base(t.comp); - const field = for (union_base.type.@"union".fields) |field| { - if (field.qt.eql(operand_qt, t.comp)) break field; - } else unreachable; - const field_name = if (field.name_tok == 0) t.anonymous_record_field_names.get(.{ - .parent = union_base.qt, - .field = field.qt, - }).? else field.name.lookup(t.comp); - - const field_init = try t.arena.create(ast.Payload.ContainerInit.Initializer); - field_init.* = .{ - .name = field_name, - .value = try t.transExpr(scope, cast.operand, .used), - }; - break :union_cast try ZigTag.container_init.create(t.arena, .{ - .lhs = union_type, - .inits = field_init[0..1], - }); - }, - else => return t.fail(error.UnsupportedTranslation, cast.l_paren, "TODO translate {s} cast", .{@tagName(cast.kind)}), - }; - if (suppress_as == .no_as) return t.maybeSuppressResult(used, operand); - if (used == .unused) return t.maybeSuppressResult(used, operand); - const as = try ZigTag.as.create(t.arena, .{ - .lhs = try t.transType(scope, dest_qt, cast.l_paren), - .rhs = operand, - }); - return as; -} - -fn transIntCast(t: *Translator, operand: ZigNode, src_qt: QualType, dest_qt: QualType) !ZigNode { - const src_dest_order = src_qt.intRankOrder(dest_qt, t.comp); - const different_sign = t.signedness(src_qt) != t.signedness(dest_qt); - const needs_bitcast = different_sign and !(t.signedness(src_qt) == .unsigned and src_dest_order == .lt); - - var casted = operand; - if (casted.isBoolRes()) { - casted = try ZigTag.int_from_bool.create(t.arena, casted); - } else if (src_dest_order == .gt) { - // No C type is smaller than the 1 bit from @intFromBool - casted = try ZigTag.truncate.create(t.arena, casted); - } - if (needs_bitcast) { - if (src_dest_order != .eq) { - casted = try ZigTag.as.create(t.arena, .{ - .lhs = try t.transTypeIntWidthOf(dest_qt, t.signedness(src_qt) == .signed), - .rhs = casted, - }); - } - return ZigTag.bit_cast.create(t.arena, casted); - } - return casted; -} - -/// Same as `transExpr` but adds a `&` if the expression is an identifier referencing a function type. -fn transPointerCastExpr(t: *Translator, scope: *Scope, expr: Node.Index) TransError!ZigNode { - const sub_expr_node = try t.transExpr(scope, expr, .used); - switch (expr.get(t.tree)) { - .cast => |cast| if (cast.kind == .function_to_pointer and sub_expr_node.tag() == .identifier) { - return ZigTag.address_of.create(t.arena, sub_expr_node); - }, - else => {}, - } - return sub_expr_node; -} - -fn transDeclRefExpr(t: *Translator, scope: *Scope, decl_ref: Node.DeclRef) TransError!ZigNode { - const name = t.tree.tokSlice(decl_ref.name_tok); - const maybe_alias = scope.getAlias(name); - const mangled_name = maybe_alias orelse name; - - switch (decl_ref.decl.get(t.tree)) { - .function => |function| if (function.definition == null and function.body == null) { - // Try translating the decl again in case of out of scope declaration. - try t.transFnDecl(scope, function); - }, - else => {}, - } - - const decl = decl_ref.decl.get(t.tree); - const ref_expr = blk: { - const identifier = try ZigTag.identifier.create(t.arena, mangled_name); - if (decl_ref.qt.is(t.comp, .func) and maybe_alias != null) { - break :blk try ZigTag.field_access.create(t.arena, .{ - .lhs = identifier, - .field_name = name, - }); - } - if (decl == .variable and maybe_alias != null) { - switch (decl.variable.storage_class) { - .@"extern", .static => { - break :blk try ZigTag.field_access.create(t.arena, .{ - .lhs = identifier, - .field_name = name, - }); - }, - else => {}, - } - } - break :blk identifier; - }; - - scope.skipVariableDiscard(mangled_name); - return ref_expr; -} - -fn transBinExpr(t: *Translator, scope: *Scope, bin: Node.Binary, op_id: ZigTag) TransError!ZigNode { - const lhs_uncasted = try t.transExpr(scope, bin.lhs, .used); - const rhs_uncasted = try t.transExpr(scope, bin.rhs, .used); - - const lhs = if (lhs_uncasted.isBoolRes()) - try ZigTag.int_from_bool.create(t.arena, lhs_uncasted) - else - lhs_uncasted; - - const rhs = if (rhs_uncasted.isBoolRes()) - try ZigTag.int_from_bool.create(t.arena, rhs_uncasted) - else - rhs_uncasted; - - return t.createBinOpNode(op_id, lhs, rhs); -} - -fn transBoolBinExpr(t: *Translator, scope: *Scope, bin: Node.Binary, op: ZigTag) !ZigNode { - std.debug.assert(op == .@"and" or op == .@"or"); - - const lhs = try t.transBoolExpr(scope, bin.lhs); - const rhs = try t.transBoolExpr(scope, bin.rhs); - - return t.createBinOpNode(op, lhs, rhs); -} - -fn transShiftExpr(t: *Translator, scope: *Scope, bin: Node.Binary, op_id: ZigTag) !ZigNode { - std.debug.assert(op_id == .shl or op_id == .shr); - - // lhs >> @intCast(rh) - const lhs = try t.transExpr(scope, bin.lhs, .used); - - const rhs = try t.transExprCoercing(scope, bin.rhs, .used); - const rhs_casted = try ZigTag.int_cast.create(t.arena, rhs); - - return t.createBinOpNode(op_id, lhs, rhs_casted); -} - -fn transCondExpr( - t: *Translator, - scope: *Scope, - conditional: Node.Conditional, - used: ResultUsed, -) TransError!ZigNode { - var cond_scope: Scope.Condition = .{ - .base = .{ - .parent = scope, - .id = .condition, - }, - }; - defer cond_scope.deinit(); - - const res_is_bool = conditional.qt.is(t.comp, .bool); - const cond = try t.transBoolExpr(&cond_scope.base, conditional.cond); - - var then_body = try t.transExpr(scope, conditional.then_expr, used); - if (!res_is_bool and then_body.isBoolRes()) { - then_body = try ZigTag.int_from_bool.create(t.arena, then_body); - } - - var else_body = try t.transExpr(scope, conditional.else_expr, used); - if (!res_is_bool and else_body.isBoolRes()) { - else_body = try ZigTag.int_from_bool.create(t.arena, else_body); - } - - // The `ResultUsed` is forwarded to both branches so no need to suppress the result here. - return ZigTag.@"if".create(t.arena, .{ .cond = cond, .then = then_body, .@"else" = else_body }); -} - -fn transBinaryCondExpr( - t: *Translator, - scope: *Scope, - conditional: Node.Conditional, - used: ResultUsed, -) TransError!ZigNode { - // GNU extension of the ternary operator where the middle expression is - // omitted, the condition itself is returned if it evaluates to true. - - if (used == .unused) { - // Result unused so this can be translated as - // if (condition) else_expr; - var cond_scope: Scope.Condition = .{ - .base = .{ - .parent = scope, - .id = .condition, - }, - }; - defer cond_scope.deinit(); - - return ZigTag.@"if".create(t.arena, .{ - .cond = try t.transBoolExpr(&cond_scope.base, conditional.cond), - .then = try t.transExpr(scope, conditional.else_expr, .unused), - .@"else" = null, - }); - } - - const res_is_bool = conditional.qt.is(t.comp, .bool); - // c: (condition)?:(else_expr) - // zig: (blk: { - // const _cond_temp = (condition); - // break :blk if (_cond_temp) _cond_temp else (else_expr); - // }) - var block_scope = try Scope.Block.init(t, scope, true); - defer block_scope.deinit(); - - const cond_temp = try block_scope.reserveMangledName("cond_temp"); - const init_node = try t.transExpr(&block_scope.base, conditional.cond, .used); - const temp_decl = try ZigTag.var_simple.create(t.arena, .{ .name = cond_temp, .init = init_node }); - try block_scope.statements.append(t.gpa, temp_decl); - - var cond_scope: Scope.Condition = .{ - .base = .{ - .parent = &block_scope.base, - .id = .condition, - }, - }; - defer cond_scope.deinit(); - - const cond_ident = try ZigTag.identifier.create(t.arena, cond_temp); - const cond_node = try t.finishBoolExpr(conditional.cond.qt(t.tree), cond_ident); - var then_body = cond_ident; - if (!res_is_bool and init_node.isBoolRes()) { - then_body = try ZigTag.int_from_bool.create(t.arena, then_body); - } - - var else_body = try t.transExpr(&block_scope.base, conditional.else_expr, .used); - if (!res_is_bool and else_body.isBoolRes()) { - else_body = try ZigTag.int_from_bool.create(t.arena, else_body); - } - const if_node = try ZigTag.@"if".create(t.arena, .{ - .cond = cond_node, - .then = then_body, - .@"else" = else_body, - }); - const break_node = try ZigTag.break_val.create(t.arena, .{ - .label = block_scope.label, - .val = if_node, - }); - try block_scope.statements.append(t.gpa, break_node); - return block_scope.complete(); -} - -fn transCommaExpr(t: *Translator, scope: *Scope, bin: Node.Binary, used: ResultUsed) TransError!ZigNode { - if (used == .unused) { - const lhs = try t.transExprCoercing(scope, bin.lhs, .unused); - try scope.appendNode(lhs); - const rhs = try t.transExprCoercing(scope, bin.rhs, .unused); - return rhs; - } - - var block_scope = try Scope.Block.init(t, scope, true); - defer block_scope.deinit(); - - const lhs = try t.transExprCoercing(&block_scope.base, bin.lhs, .unused); - try block_scope.statements.append(t.gpa, lhs); - - const rhs = try t.transExprCoercing(&block_scope.base, bin.rhs, .used); - const break_node = try ZigTag.break_val.create(t.arena, .{ - .label = block_scope.label, - .val = rhs, - }); - try block_scope.statements.append(t.gpa, break_node); - - return try block_scope.complete(); -} - -fn transAssignExpr(t: *Translator, scope: *Scope, bin: Node.Binary, used: ResultUsed) !ZigNode { - if (used == .unused) { - const lhs = try t.transExpr(scope, bin.lhs, .used); - var rhs = try t.transExprCoercing(scope, bin.rhs, .used); - - const lhs_qt = bin.lhs.qt(t.tree); - if (rhs.isBoolRes() and !lhs_qt.is(t.comp, .bool)) { - rhs = try ZigTag.int_from_bool.create(t.arena, rhs); - } - - return t.createBinOpNode(.assign, lhs, rhs); - } - - var block_scope = try Scope.Block.init(t, scope, true); - defer block_scope.deinit(); - - const tmp = try block_scope.reserveMangledName("tmp"); - - var rhs = try t.transExpr(&block_scope.base, bin.rhs, .used); - const lhs_qt = bin.lhs.qt(t.tree); - if (rhs.isBoolRes() and !lhs_qt.is(t.comp, .bool)) { - rhs = try ZigTag.int_from_bool.create(t.arena, rhs); - } - - const tmp_decl = try ZigTag.var_simple.create(t.arena, .{ .name = tmp, .init = rhs }); - try block_scope.statements.append(t.gpa, tmp_decl); - - const lhs = try t.transExprCoercing(&block_scope.base, bin.lhs, .used); - const tmp_ident = try ZigTag.identifier.create(t.arena, tmp); - - const assign = try t.createBinOpNode(.assign, lhs, tmp_ident); - try block_scope.statements.append(t.gpa, assign); - - const break_node = try ZigTag.break_val.create(t.arena, .{ - .label = block_scope.label, - .val = tmp_ident, - }); - try block_scope.statements.append(t.gpa, break_node); - - return try block_scope.complete(); -} - -fn transCompoundAssign( - t: *Translator, - scope: *Scope, - assign: Node.Binary, - used: ResultUsed, -) !ZigNode { - // If the result is unused we can try using the equivalent Zig operator - // without a block - if (used == .unused) { - if (try t.transCompoundAssignSimple(scope, null, assign)) |some| { - return some; - } - } - - // Otherwise we need to wrap the the compound assignment in a block. - var block_scope = try Scope.Block.init(t, scope, used == .used); - defer block_scope.deinit(); - const ref = try block_scope.reserveMangledName("ref"); - - const lhs_expr = try t.transExpr(&block_scope.base, assign.lhs, .used); - const addr_of = try ZigTag.address_of.create(t.arena, lhs_expr); - const ref_decl = try ZigTag.var_simple.create(t.arena, .{ .name = ref, .init = addr_of }); - try block_scope.statements.append(t.gpa, ref_decl); - - const lhs_node = try ZigTag.identifier.create(t.arena, ref); - const ref_node = try ZigTag.deref.create(t.arena, lhs_node); - - // Use the equivalent Zig operator if possible. - if (try t.transCompoundAssignSimple(scope, ref_node, assign)) |some| { - try block_scope.statements.append(t.gpa, some); - } else { - const old_dummy = t.compound_assign_dummy; - defer t.compound_assign_dummy = old_dummy; - t.compound_assign_dummy = ref_node; - - // Otherwise do the operation and assignment separately. - const rhs_node = try t.transExprCoercing(&block_scope.base, assign.rhs, .used); - const assign_node = try t.createBinOpNode(.assign, ref_node, rhs_node); - try block_scope.statements.append(t.gpa, assign_node); - } - - if (used == .used) { - const break_node = try ZigTag.break_val.create(t.arena, .{ - .label = block_scope.label, - .val = ref_node, - }); - try block_scope.statements.append(t.gpa, break_node); - } - return block_scope.complete(); -} - -/// Translates compound assignment using the equivalent Zig operator if possible. -fn transCompoundAssignSimple(t: *Translator, scope: *Scope, lhs_dummy_opt: ?ZigNode, assign: Node.Binary) TransError!?ZigNode { - const assign_rhs = assign.rhs.get(t.tree); - if (assign_rhs == .cast) return null; - - const is_signed = t.signedness(assign.qt) == .signed; - switch (assign_rhs) { - .div_expr, .mod_expr => if (is_signed) return null, - else => {}, - } - const lhs_ptr = assign.qt.isPointer(t.comp); - - const bin, const op: ZigTag, const cast: enum { none, shift, usize } = switch (assign_rhs) { - .add_expr => |bin| .{ - bin, - if (t.typeHasWrappingOverflow(bin.qt)) .add_wrap_assign else .add_assign, - if (lhs_ptr and t.signedness(bin.rhs.qt(t.tree)) == .signed) .usize else .none, - }, - .sub_expr => |bin| .{ - bin, - if (t.typeHasWrappingOverflow(bin.qt)) .sub_wrap_assign else .sub_assign, - if (lhs_ptr and t.signedness(bin.rhs.qt(t.tree)) == .signed) .usize else .none, - }, - .mul_expr => |bin| .{ - bin, - if (t.typeHasWrappingOverflow(bin.qt)) .mul_wrap_assign else .mul_assign, - .none, - }, - .mod_expr => |bin| .{ bin, .mod_assign, .none }, - .div_expr => |bin| .{ bin, .div_assign, .none }, - .shl_expr => |bin| .{ bin, .shl_assign, .shift }, - .shr_expr => |bin| .{ bin, .shr_assign, .shift }, - .bit_and_expr => |bin| .{ bin, .bit_and_assign, .none }, - .bit_xor_expr => |bin| .{ bin, .bit_xor_assign, .none }, - .bit_or_expr => |bin| .{ bin, .bit_or_assign, .none }, - else => unreachable, - }; - - const lhs_node = blk: { - const old_dummy = t.compound_assign_dummy; - defer t.compound_assign_dummy = old_dummy; - t.compound_assign_dummy = lhs_dummy_opt orelse try t.transExpr(scope, assign.lhs, .used); - - break :blk try t.transExpr(scope, bin.lhs, .used); - }; - - const rhs_node = try t.transExprCoercing(scope, bin.rhs, .used); - const casted_rhs = switch (cast) { - .none => rhs_node, - .shift => try ZigTag.int_cast.create(t.arena, rhs_node), - .usize => try t.usizeCastForWrappingPtrArithmetic(rhs_node), - }; - return try t.createBinOpNode(op, lhs_node, casted_rhs); -} - -fn transIncDecExpr( - t: *Translator, - scope: *Scope, - un: Node.Unary, - position: enum { pre, post }, - kind: enum { inc, dec }, - used: ResultUsed, -) !ZigNode { - const is_wrapping = t.typeHasWrappingOverflow(un.qt); - const op: ZigTag = switch (kind) { - .inc => if (is_wrapping) .add_wrap_assign else .add_assign, - .dec => if (is_wrapping) .sub_wrap_assign else .sub_assign, - }; - - const one_literal = ZigTag.one_literal.init(); - if (used == .unused) { - const operand = try t.transExpr(scope, un.operand, .used); - return try t.createBinOpNode(op, operand, one_literal); - } - - var block_scope = try Scope.Block.init(t, scope, true); - defer block_scope.deinit(); - - const ref = try block_scope.reserveMangledName("ref"); - const operand = try t.transExprCoercing(&block_scope.base, un.operand, .used); - const operand_ref = try ZigTag.address_of.create(t.arena, operand); - const ref_decl = try ZigTag.var_simple.create(t.arena, .{ .name = ref, .init = operand_ref }); - try block_scope.statements.append(t.gpa, ref_decl); - - const ref_ident = try ZigTag.identifier.create(t.arena, ref); - const ref_deref = try ZigTag.deref.create(t.arena, ref_ident); - const effect = try t.createBinOpNode(op, ref_deref, one_literal); - - switch (position) { - .pre => { - try block_scope.statements.append(t.gpa, effect); - - const break_node = try ZigTag.break_val.create(t.arena, .{ - .label = block_scope.label, - .val = ref_deref, - }); - try block_scope.statements.append(t.gpa, break_node); - }, - .post => { - const tmp = try block_scope.reserveMangledName("tmp"); - const tmp_decl = try ZigTag.var_simple.create(t.arena, .{ .name = tmp, .init = ref_deref }); - try block_scope.statements.append(t.gpa, tmp_decl); - - try block_scope.statements.append(t.gpa, effect); - - const tmp_ident = try ZigTag.identifier.create(t.arena, tmp); - const break_node = try ZigTag.break_val.create(t.arena, .{ - .label = block_scope.label, - .val = tmp_ident, - }); - try block_scope.statements.append(t.gpa, break_node); - }, - } - - return try block_scope.complete(); -} - -fn transPtrDiffExpr(t: *Translator, scope: *Scope, bin: Node.Binary) TransError!ZigNode { - const lhs_uncasted = try t.transExpr(scope, bin.lhs, .used); - const rhs_uncasted = try t.transExpr(scope, bin.rhs, .used); - - const lhs = try ZigTag.int_from_ptr.create(t.arena, lhs_uncasted); - const rhs = try ZigTag.int_from_ptr.create(t.arena, rhs_uncasted); - - const sub_res = try t.createBinOpNode(.sub_wrap, lhs, rhs); - - // @divExact(@as(, @bitCast(@intFromPtr(lhs)) -% @intFromPtr(rhs)), @sizeOf()) - const ptrdiff_type = try t.transTypeIntWidthOf(bin.qt, true); - - const bitcast = try ZigTag.as.create(t.arena, .{ - .lhs = ptrdiff_type, - .rhs = try ZigTag.bit_cast.create(t.arena, sub_res), - }); - - // C standard requires that pointer subtraction operands are of the same type, - // otherwise it is undefined behavior. So we can assume the left and right - // sides are the same Type and arbitrarily choose left. - const lhs_ty = try t.transType(scope, bin.lhs.qt(t.tree), bin.lhs.tok(t.tree)); - const c_pointer = t.getContainer(lhs_ty).?; - - if (c_pointer.castTag(.c_pointer)) |c_pointer_payload| { - const sizeof = try ZigTag.sizeof.create(t.arena, c_pointer_payload.data.elem_type); - return ZigTag.div_exact.create(t.arena, .{ - .lhs = bitcast, - .rhs = sizeof, - }); - } else { - // This is an opaque/incomplete type. This subtraction exhibits Undefined Behavior by the C99 spec. - // However, allowing subtraction on `void *` and function pointers is a commonly used extension. - // So, just return the value in byte units, mirroring the behavior of this language extension as implemented by GCC and Clang. - return bitcast; - } -} - -/// Translate an arithmetic expression with a pointer operand and a signed-integer operand. -/// Zig requires a usize argument for pointer arithmetic, so we intCast to isize and then -/// bitcast to usize; pointer wraparound makes the math work. -/// Zig pointer addition is not commutative (unlike C); the pointer operand needs to be on the left. -/// The + operator in C is not a sequence point so it should be safe to switch the order if necessary. -fn transPointerArithmeticSignedOp(t: *Translator, scope: *Scope, bin: Node.Binary, op_id: ZigTag) TransError!ZigNode { - std.debug.assert(op_id == .add or op_id == .sub); - - const lhs_qt = bin.lhs.qt(t.tree); - const swap_operands = op_id == .add and t.signedness(lhs_qt) == .signed; - - const swizzled_lhs = if (swap_operands) bin.rhs else bin.lhs; - const swizzled_rhs = if (swap_operands) bin.lhs else bin.rhs; - - const lhs_node = try t.transExpr(scope, swizzled_lhs, .used); - const rhs_node = try t.transExpr(scope, swizzled_rhs, .used); - - const bitcast_node = try t.usizeCastForWrappingPtrArithmetic(rhs_node); - - return t.createBinOpNode(op_id, lhs_node, bitcast_node); -} - -fn transMemberAccess( - t: *Translator, - scope: *Scope, - kind: enum { normal, ptr }, - member_access: Node.MemberAccess, - opt_base: ?ZigNode, -) TransError!ZigNode { - const base_info = switch (kind) { - .normal => member_access.base.qt(t.tree), - .ptr => member_access.base.qt(t.tree).childType(t.comp), - }; - const record = base_info.getRecord(t.comp).?; - const field = record.fields[member_access.member_index]; - const field_name = if (field.name_tok == 0) t.anonymous_record_field_names.get(.{ - .parent = base_info.base(t.comp).qt, - .field = field.qt, - }).? else field.name.lookup(t.comp); - const base_node = opt_base orelse try t.transExpr(scope, member_access.base, .used); - const lhs = switch (kind) { - .normal => base_node, - .ptr => try ZigTag.deref.create(t.arena, base_node), - }; - const field_access = try ZigTag.field_access.create(t.arena, .{ - .lhs = lhs, - .field_name = field_name, - }); - - // Flexible array members are translated as member functions. - if (member_access.member_index == record.fields.len - 1 or base_info.base(t.comp).type == .@"union") { - if (field.qt.get(t.comp, .array)) |array_ty| { - if (array_ty.len == .incomplete or (array_ty.len == .fixed and array_ty.len.fixed == 0)) { - return ZigTag.call.create(t.arena, .{ .lhs = field_access, .args = &.{} }); - } - } - } - - return field_access; -} - -fn transArrayAccess(t: *Translator, scope: *Scope, array_access: Node.ArrayAccess, opt_base: ?ZigNode) TransError!ZigNode { - // Unwrap the base statement if it's an array decayed to a bare pointer type - // so that we index the array itself - const base = base: { - const base = array_access.base.get(t.tree); - if (base != .cast) break :base array_access.base; - if (base.cast.kind != .array_to_pointer) break :base array_access.base; - break :base base.cast.operand; - }; - - const base_node = opt_base orelse try t.transExpr(scope, base, .used); - const index = index: { - const index = try t.transExpr(scope, array_access.index, .used); - const index_qt = array_access.index.qt(t.tree); - const maybe_bigger_than_usize = switch (index_qt.base(t.comp).type) { - .bool => { - break :index try ZigTag.int_from_bool.create(t.arena, index); - }, - .int => |int| switch (int) { - .long_long, .ulong_long, .int128, .uint128 => true, - else => false, - }, - .bit_int => |bit_int| bit_int.bits > t.comp.target.ptrBitWidth(), - else => unreachable, - }; - - const is_nonnegative_int_literal = if (t.tree.value_map.get(array_access.index)) |val| - val.compare(.gte, .zero, t.comp) - else - false; - const is_signed = t.signedness(index_qt) == .signed; - - if (is_signed and !is_nonnegative_int_literal) { - // First cast to `isize` to get proper sign extension and - // then @bitCast to `usize` to satisfy the compiler. - const index_isize = try ZigTag.as.create(t.arena, .{ - .lhs = try ZigTag.type.create(t.arena, "isize"), - .rhs = try ZigTag.int_cast.create(t.arena, index), - }); - break :index try ZigTag.bit_cast.create(t.arena, index_isize); - } - - if (maybe_bigger_than_usize) { - break :index try ZigTag.int_cast.create(t.arena, index); - } - break :index index; - }; - - return ZigTag.array_access.create(t.arena, .{ - .lhs = base_node, - .rhs = index, - }); -} - -fn transOffsetof(t: *Translator, scope: *Scope, arg: Node.Index) TransError!ZigNode { - // Translate __builtin_offsetof(T, designator) as - // @intFromPtr(&(@as(*allowzero T, @ptrFromInt(0)).designator)) - const member = try t.transMemberDesignator(scope, arg); - const address = try ZigTag.address_of.create(t.arena, member); - return ZigTag.int_from_ptr.create(t.arena, address); -} - -fn transMemberDesignator(t: *Translator, scope: *Scope, arg: Node.Index) TransError!ZigNode { - switch (arg.get(t.tree)) { - .default_init_expr => |default| { - const elem_node = try t.transType(scope, default.qt, default.last_tok); - const ptr_ty = try ZigTag.single_pointer.create(t.arena, .{ - .elem_type = elem_node, - .is_allowzero = true, - .is_const = false, - .is_volatile = false, - }); - const zero = try ZigTag.ptr_from_int.create(t.arena, ZigTag.zero_literal.init()); - return ZigTag.as.create(t.arena, .{ .lhs = ptr_ty, .rhs = zero }); - }, - .array_access_expr => |access| { - const base = try t.transMemberDesignator(scope, access.base); - return t.transArrayAccess(scope, access, base); - }, - .member_access_expr => |access| { - const base = try t.transMemberDesignator(scope, access.base); - return t.transMemberAccess(scope, .normal, access, base); - }, - .cast => |cast| { - assert(cast.kind == .array_to_pointer); - return t.transMemberDesignator(scope, cast.operand); - }, - else => unreachable, - } -} - -fn transBuiltinCall( - t: *Translator, - scope: *Scope, - call: Node.BuiltinCall, - used: ResultUsed, -) TransError!ZigNode { - const builtin_name = t.tree.tokSlice(call.builtin_tok); - if (std.mem.eql(u8, builtin_name, "__builtin_offsetof")) { - const res = try t.transOffsetof(scope, call.args[0]); - return t.maybeSuppressResult(used, res); - } - - const builtin = builtins.map.get(builtin_name) orelse - return t.fail(error.UnsupportedTranslation, call.builtin_tok, "TODO implement function '{s}' in std.zig.c_builtins", .{builtin_name}); - - if (builtin.tag) |tag| switch (tag) { - .byte_swap, .ceil, .cos, .sin, .exp, .exp2, .exp10, .abs, .log, .log2, .log10, .round, .sqrt, .trunc, .floor => { - assert(call.args.len == 1); - const arg = try t.transExprCoercing(scope, call.args[0], .used); - const arg_ty = try t.transType(scope, call.args[0].qt(t.tree), call.args[0].tok(t.tree)); - const coerced = try ZigTag.as.create(t.arena, .{ .lhs = arg_ty, .rhs = arg }); - - const ptr = try t.arena.create(ast.Payload.UnOp); - ptr.* = .{ .base = .{ .tag = tag }, .data = coerced }; - return t.maybeSuppressResult(used, ZigNode.initPayload(&ptr.base)); - }, - .@"unreachable" => return ZigTag.@"unreachable".init(), - else => unreachable, - }; - - const arg_nodes = try t.arena.alloc(ZigNode, call.args.len); - for (call.args, arg_nodes) |c_arg, *zig_arg| { - zig_arg.* = try t.transExprCoercing(scope, c_arg, .used); - } - - const builtin_identifier = try ZigTag.identifier.create(t.arena, "__builtin"); - const field_access = try ZigTag.field_access.create(t.arena, .{ - .lhs = builtin_identifier, - .field_name = builtin.name, - }); - - const res = try ZigTag.call.create(t.arena, .{ - .lhs = field_access, - .args = arg_nodes, - }); - if (call.qt.is(t.comp, .void)) return res; - return t.maybeSuppressResult(used, res); -} - -fn transCall( - t: *Translator, - scope: *Scope, - call: Node.Call, - used: ResultUsed, -) TransError!ZigNode { - const raw_fn_expr = try t.transExpr(scope, call.callee, .used); - const fn_expr = blk: { - loop: switch (call.callee.get(t.tree)) { - .paren_expr => |paren_expr| { - continue :loop paren_expr.operand.get(t.tree); - }, - .decl_ref_expr => |decl_ref| { - if (decl_ref.qt.is(t.comp, .func)) break :blk raw_fn_expr; - }, - .cast => |cast| { - if (cast.kind == .function_to_pointer) { - continue :loop cast.operand.get(t.tree); - } - }, - .deref_expr, .addr_of_expr => |un| { - continue :loop un.operand.get(t.tree); - }, - .generic_expr => |generic| { - continue :loop generic.chosen.get(t.tree); - }, - .generic_association_expr => |generic| { - continue :loop generic.expr.get(t.tree); - }, - .generic_default_expr => |generic| { - continue :loop generic.expr.get(t.tree); - }, - else => {}, - } - break :blk try ZigTag.unwrap.create(t.arena, raw_fn_expr); - }; - - const callee_qt = call.callee.qt(t.tree); - const maybe_ptr_ty = callee_qt.get(t.comp, .pointer); - const func_qt = if (maybe_ptr_ty) |ptr| ptr.child else callee_qt; - const func_ty = func_qt.get(t.comp, .func).?; - - const arg_nodes = try t.arena.alloc(ZigNode, call.args.len); - for (call.args, arg_nodes, 0..) |c_arg, *zig_arg, i| { - if (i < func_ty.params.len) { - zig_arg.* = try t.transExprCoercing(scope, c_arg, .used); - - if (zig_arg.isBoolRes() and !func_ty.params[i].qt.is(t.comp, .bool)) { - // In C the result type of a boolean expression is int. If this result is passed as - // an argument to a function whose parameter is also int, there is no cast. Therefore - // in Zig we'll need to cast it from bool to u1 (which will safely coerce to c_int). - zig_arg.* = try ZigTag.int_from_bool.create(t.arena, zig_arg.*); - } - } else { - zig_arg.* = try t.transExpr(scope, c_arg, .used); - - if (zig_arg.isBoolRes()) { - // Same as above but now we don't have a result type. - const u1_node = try ZigTag.int_from_bool.create(t.arena, zig_arg.*); - const c_int_node = try ZigTag.type.create(t.arena, "c_int"); - zig_arg.* = try ZigTag.as.create(t.arena, .{ .lhs = c_int_node, .rhs = u1_node }); - } - } - } - - const res = try ZigTag.call.create(t.arena, .{ - .lhs = fn_expr, - .args = arg_nodes, - }); - if (call.qt.is(t.comp, .void)) return res; - return t.maybeSuppressResult(used, res); -} - -const SuppressCast = enum { with_as, no_as }; - -fn transIntLiteral( - t: *Translator, - scope: *Scope, - literal_index: Node.Index, - used: ResultUsed, - suppress_as: SuppressCast, -) TransError!ZigNode { - const val = t.tree.value_map.get(literal_index).?; - const int_lit_node = try t.createIntNode(val); - if (suppress_as == .no_as) { - return t.maybeSuppressResult(used, int_lit_node); - } - - // Integer literals in C have types, and this can matter for several reasons. - // For example, this is valid C: - // unsigned char y = 256; - // How this gets evaluated is the 256 is an integer, which gets truncated to signed char, then bit-casted - // to unsigned char, resulting in 0. In order for this to work, we have to emit this zig code: - // var y = @as(u8, @bitCast(@as(i8, @truncate(@as(c_int, 256))))); - - // @as(T, x) - const ty_node = try t.transType(scope, literal_index.qt(t.tree), literal_index.tok(t.tree)); - const as = try ZigTag.as.create(t.arena, .{ .lhs = ty_node, .rhs = int_lit_node }); - return t.maybeSuppressResult(used, as); -} - -fn transCharLiteral( - t: *Translator, - scope: *Scope, - literal_index: Node.Index, - used: ResultUsed, - suppress_as: SuppressCast, -) TransError!ZigNode { - const val = t.tree.value_map.get(literal_index).?; - const char_literal = literal_index.get(t.tree).char_literal; - const narrow = char_literal.kind == .ascii or char_literal.kind == .utf8; - - // C has a somewhat obscure feature called multi-character character constant - // e.g. 'abcd' - const int_value = val.toInt(u32, t.comp).?; - const int_lit_node = if (char_literal.kind == .ascii and int_value > 255) - try t.createNumberNode(int_value, .int) - else - try t.createCharLiteralNode(narrow, int_value); - - if (suppress_as == .no_as) { - return t.maybeSuppressResult(used, int_lit_node); - } - - // See comment in `transIntLiteral` for why this code is here. - // @as(T, x) - const as_node = try ZigTag.as.create(t.arena, .{ - .lhs = try t.transType(scope, char_literal.qt, char_literal.literal_tok), - .rhs = int_lit_node, - }); - return t.maybeSuppressResult(used, as_node); -} - -fn transFloatLiteral( - t: *Translator, - scope: *Scope, - literal_index: Node.Index, - used: ResultUsed, - suppress_as: SuppressCast, -) TransError!ZigNode { - const val = t.tree.value_map.get(literal_index).?; - const float_literal = literal_index.get(t.tree).float_literal; - - var allocating: std.Io.Writer.Allocating = .init(t.gpa); - defer allocating.deinit(); - _ = val.print(float_literal.qt, t.comp, &allocating.writer) catch return error.OutOfMemory; - - const float_lit_node = try ZigTag.float_literal.create(t.arena, try t.arena.dupe(u8, allocating.getWritten())); - if (suppress_as == .no_as) { - return t.maybeSuppressResult(used, float_lit_node); - } - - const as_node = try ZigTag.as.create(t.arena, .{ - .lhs = try t.transType(scope, float_literal.qt, float_literal.literal_tok), - .rhs = float_lit_node, - }); - return t.maybeSuppressResult(used, as_node); -} - -fn transStringLiteral( - t: *Translator, - scope: *Scope, - expr: Node.Index, - literal: Node.CharLiteral, -) TransError!ZigNode { - switch (literal.kind) { - .ascii, .utf8 => return t.transNarrowStringLiteral(expr, literal), - .utf16, .utf32, .wide => { - const name = try std.fmt.allocPrint(t.arena, "{s}_string_{d}", .{ @tagName(literal.kind), t.getMangle() }); - - const array_type = try t.transTypeInit(scope, literal.qt, expr, literal.literal_tok); - const lit_array = try t.transStringLiteralInitializer(expr, literal, array_type); - const decl = try ZigTag.var_simple.create(t.arena, .{ .name = name, .init = lit_array }); - try scope.appendNode(decl); - return ZigTag.identifier.create(t.arena, name); - }, - } -} - -fn transNarrowStringLiteral( - t: *Translator, - expr: Node.Index, - literal: Node.CharLiteral, -) TransError!ZigNode { - const val = t.tree.value_map.get(expr).?; - - const bytes = t.comp.interner.get(val.ref()).bytes; - var allocating: std.Io.Writer.Allocating = try .initCapacity(t.gpa, bytes.len); - defer allocating.deinit(); - - aro.Value.printString(bytes, literal.qt, t.comp, &allocating.writer) catch return error.OutOfMemory; - - return ZigTag.string_literal.create(t.arena, try t.arena.dupe(u8, allocating.getWritten())); -} - -/// Translate a string literal that is initializing an array. In general narrow string -/// literals become `"".*` or `""[0..].*` if they need truncation. -/// Wide string literals become an array of integers. zero-fillers pad out the array to -/// the appropriate length, if necessary. -fn transStringLiteralInitializer( - t: *Translator, - expr: Node.Index, - literal: Node.CharLiteral, - array_type: ZigNode, -) TransError!ZigNode { - assert(array_type.tag() == .array_type or array_type.tag() == .null_sentinel_array_type); - - const is_narrow = literal.kind == .ascii or literal.kind == .utf8; - - // The length of the string literal excluding the sentinel. - const str_length = literal.qt.arrayLen(t.comp).? - 1; - - const payload = (array_type.castTag(.array_type) orelse array_type.castTag(.null_sentinel_array_type).?).data; - const array_size = payload.len; - const elem_type = payload.elem_type; - - if (array_size == 0) return ZigTag.empty_array.create(t.arena, array_type); - - const num_inits = @min(str_length, array_size); - if (num_inits == 0) { - return ZigTag.array_filler.create(t.arena, .{ - .type = elem_type, - .filler = ZigTag.zero_literal.init(), - .count = array_size, - }); - } - - const init_node = if (is_narrow) blk: { - // "string literal".* or string literal"[0..num_inits].* - var str = try t.transNarrowStringLiteral(expr, literal); - if (str_length != array_size) str = try ZigTag.string_slice.create(t.arena, .{ .string = str, .end = num_inits }); - break :blk try ZigTag.deref.create(t.arena, str); - } else blk: { - const size = literal.qt.childType(t.comp).sizeof(t.comp); - - const val = t.tree.value_map.get(expr).?; - const bytes = t.comp.interner.get(val.ref()).bytes; - - const init_list = try t.arena.alloc(ZigNode, @intCast(num_inits)); - for (init_list, 0..) |*item, i| { - const codepoint = switch (size) { - 2 => @as(*const u16, @alignCast(@ptrCast(bytes.ptr + i * 2))).*, - 4 => @as(*const u32, @alignCast(@ptrCast(bytes.ptr + i * 4))).*, - else => unreachable, - }; - item.* = try t.createCharLiteralNode(false, codepoint); - } - const init_args: ast.Payload.Array.ArrayTypeInfo = .{ .len = num_inits, .elem_type = elem_type }; - const init_array_type = if (array_type.tag() == .array_type) - try ZigTag.array_type.create(t.arena, init_args) - else - try ZigTag.null_sentinel_array_type.create(t.arena, init_args); - break :blk try ZigTag.array_init.create(t.arena, .{ - .cond = init_array_type, - .cases = init_list, - }); - }; - - if (num_inits == array_size) return init_node; - assert(array_size > str_length); // If array_size <= str_length, `num_inits == array_size` and we've already returned. - - const filler_node = try ZigTag.array_filler.create(t.arena, .{ - .type = elem_type, - .filler = ZigTag.zero_literal.init(), - .count = array_size - str_length, - }); - return ZigTag.array_cat.create(t.arena, .{ .lhs = init_node, .rhs = filler_node }); -} - -fn transCompoundLiteral( - t: *Translator, - scope: *Scope, - literal: Node.CompoundLiteral, - used: ResultUsed, -) TransError!ZigNode { - if (used == .unused) { - return t.transExpr(scope, literal.initializer, .unused); - } - - // TODO taking a reference to a compound literal should result in a mutable - // pointer (unless the literal is const). - - const initializer = try t.transExprCoercing(scope, literal.initializer, .used); - const ty = try t.transType(scope, literal.qt, literal.l_paren_tok); - if (!literal.thread_local and literal.storage_class != .static) { - // In the simple case a compound literal can be translated - // simply as `@as(type, initializer)`. - return ZigTag.as.create(t.arena, .{ .lhs = ty, .rhs = initializer }); - } - - // Otherwise static or thread local compound literals are translated as - // a reference to a variable wrapped in a struct. - - var block_scope = try Scope.Block.init(t, scope, true); - defer block_scope.deinit(); - - const tmp = try block_scope.reserveMangledName("tmp"); - const wrapped_name = "compound_literal"; - - // const tmp = struct { var compound_literal = initializer }; - const temp_decl = try ZigTag.var_decl.create(t.arena, .{ - .is_pub = false, - .is_const = literal.qt.@"const", - .is_extern = false, - .is_export = false, - .is_threadlocal = literal.thread_local, - .linksection_string = null, - .alignment = null, - .name = wrapped_name, - .type = ty, - .init = initializer, - }); - const wrapped = try ZigTag.wrapped_local.create(t.arena, .{ .name = tmp, .init = temp_decl }); - try block_scope.statements.append(t.gpa, wrapped); - - // break :blk tmp.compound_literal - const static_tmp_ident = try ZigTag.identifier.create(t.arena, tmp); - const field_access = try ZigTag.field_access.create(t.arena, .{ - .lhs = static_tmp_ident, - .field_name = wrapped_name, - }); - const break_node = try ZigTag.break_val.create(t.arena, .{ - .label = block_scope.label, - .val = field_access, - }); - try block_scope.statements.append(t.gpa, break_node); - - return block_scope.complete(); -} - -fn transDefaultInit( - t: *Translator, - scope: *Scope, - default_init: Node.DefaultInit, - used: ResultUsed, - suppress_as: SuppressCast, -) TransError!ZigNode { - assert(used == .used); - const type_node = try t.transType(scope, default_init.qt, default_init.last_tok); - return try t.createZeroValueNode(default_init.qt, type_node, suppress_as); -} - -fn transArrayInit( - t: *Translator, - scope: *Scope, - array_init: Node.ContainerInit, - used: ResultUsed, -) TransError!ZigNode { - assert(used == .used); - const array_item_qt = array_init.container_qt.childType(t.comp); - const array_item_type = try t.transType(scope, array_item_qt, array_init.l_brace_tok); - var maybe_lhs: ?ZigNode = null; - var val_list: std.ArrayListUnmanaged(ZigNode) = .empty; - defer val_list.deinit(t.gpa); - var i: usize = 0; - while (i < array_init.items.len) { - const rhs = switch (array_init.items[i].get(t.tree)) { - .array_filler_expr => |array_filler| blk: { - const node = try ZigTag.array_filler.create(t.arena, .{ - .type = array_item_type, - .filler = try t.createZeroValueNode(array_item_qt, array_item_type, .no_as), - .count = @intCast(array_filler.count), - }); - i += 1; - break :blk node; - }, - else => blk: { - defer val_list.clearRetainingCapacity(); - while (i < array_init.items.len) : (i += 1) { - if (array_init.items[i].get(t.tree) == .array_filler_expr) break; - const expr = try t.transExprCoercing(scope, array_init.items[i], .used); - try val_list.append(t.gpa, expr); - } - const array_type = try ZigTag.array_type.create(t.arena, .{ - .elem_type = array_item_type, - .len = val_list.items.len, - }); - const array_init_node = try ZigTag.array_init.create(t.arena, .{ - .cond = array_type, - .cases = try t.arena.dupe(ZigNode, val_list.items), - }); - break :blk array_init_node; - }, - }; - maybe_lhs = if (maybe_lhs) |lhs| blk: { - const cat = try ZigTag.array_cat.create(t.arena, .{ - .lhs = lhs, - .rhs = rhs, - }); - break :blk cat; - } else rhs; - } - return maybe_lhs orelse try ZigTag.container_init_dot.create(t.arena, &.{}); -} - -fn transUnionInit( - t: *Translator, - scope: *Scope, - union_init: Node.UnionInit, - used: ResultUsed, -) TransError!ZigNode { - assert(used == .used); - const init_expr = union_init.initializer orelse - return ZigTag.undefined_literal.init(); - - if (init_expr.get(t.tree) == .default_init_expr) { - return try t.transExpr(scope, init_expr, used); - } - - const union_type = try t.transType(scope, union_init.union_qt, union_init.l_brace_tok); - - const union_base = union_init.union_qt.base(t.comp); - const field = union_base.type.@"union".fields[union_init.field_index]; - const field_name = if (field.name_tok == 0) t.anonymous_record_field_names.get(.{ - .parent = union_base.qt, - .field = field.qt, - }).? else field.name.lookup(t.comp); - - const field_init = try t.arena.create(ast.Payload.ContainerInit.Initializer); - field_init.* = .{ - .name = field_name, - .value = try t.transExprCoercing(scope, init_expr, .used), - }; - const container_init = try ZigTag.container_init.create(t.arena, .{ - .lhs = union_type, - .inits = field_init[0..1], - }); - return container_init; -} - -fn transStructInit( - t: *Translator, - scope: *Scope, - struct_init: Node.ContainerInit, - used: ResultUsed, -) TransError!ZigNode { - assert(used == .used); - const struct_type = try t.transType(scope, struct_init.container_qt, struct_init.l_brace_tok); - const field_inits = try t.arena.alloc(ast.Payload.ContainerInit.Initializer, struct_init.items.len); - - const struct_base = struct_init.container_qt.base(t.comp); - for ( - field_inits, - struct_init.items, - struct_base.type.@"struct".fields, - ) |*init, field_expr, field| { - const field_name = if (field.name_tok == 0) t.anonymous_record_field_names.get(.{ - .parent = struct_base.qt, - .field = field.qt, - }).? else field.name.lookup(t.comp); - init.* = .{ - .name = field_name, - .value = try t.transExprCoercing(scope, field_expr, .used), - }; - } - - const container_init = try ZigTag.container_init.create(t.arena, .{ - .lhs = struct_type, - .inits = field_inits, - }); - return container_init; -} - -fn transTypeInfo( - t: *Translator, - scope: *Scope, - op: ZigTag, - typeinfo: Node.TypeInfo, -) TransError!ZigNode { - const operand = operand: { - if (typeinfo.expr) |expr| { - const operand = try t.transExpr(scope, expr, .used); - break :operand try ZigTag.typeof.create(t.arena, operand); - } - break :operand try t.transType(scope, typeinfo.operand_qt, typeinfo.op_tok); - }; - - const payload = try t.arena.create(ast.Payload.UnOp); - payload.* = .{ - .base = .{ .tag = op }, - .data = operand, - }; - return ZigNode.initPayload(&payload.base); -} - -fn transStmtExpr( - t: *Translator, - scope: *Scope, - stmt_expr: Node.Unary, - used: ResultUsed, -) TransError!ZigNode { - const compound_stmt = stmt_expr.operand.get(t.tree).compound_stmt; - if (used == .unused) { - return t.transCompoundStmt(scope, compound_stmt); - } - var block_scope = try Scope.Block.init(t, scope, true); - defer block_scope.deinit(); - - for (compound_stmt.body[0 .. compound_stmt.body.len - 1]) |stmt| { - const result = try t.transStmt(&block_scope.base, stmt); - switch (result.tag()) { - .declaration, .empty_block => {}, - else => try block_scope.statements.append(t.gpa, result), - } - } - - const last_result = try t.transExpr(&block_scope.base, compound_stmt.body[compound_stmt.body.len - 1], .used); - switch (last_result.tag()) { - .declaration, .empty_block => {}, - else => { - const break_node = try ZigTag.break_val.create(t.arena, .{ - .label = block_scope.label, - .val = last_result, - }); - try block_scope.statements.append(t.gpa, break_node); - }, - } - return block_scope.complete(); -} - -fn transConvertvectorExpr( - t: *Translator, - scope: *Scope, - convertvector: Node.Convertvector, -) TransError!ZigNode { - var block_scope = try Scope.Block.init(t, scope, true); - defer block_scope.deinit(); - - const src_expr_node = try t.transExpr(&block_scope.base, convertvector.operand, .used); - const tmp = try block_scope.reserveMangledName("tmp"); - const tmp_decl = try ZigTag.var_simple.create(t.arena, .{ .name = tmp, .init = src_expr_node }); - try block_scope.statements.append(t.gpa, tmp_decl); - const tmp_ident = try ZigTag.identifier.create(t.arena, tmp); - - const dest_type_node = try t.transType(&block_scope.base, convertvector.dest_qt, convertvector.builtin_tok); - const dest_vec_ty = convertvector.dest_qt.get(t.comp, .vector).?; - const src_vec_ty = convertvector.operand.qt(t.tree).get(t.comp, .vector).?; - - const src_elem_sk = src_vec_ty.elem.scalarKind(t.comp); - const dest_elem_sk = convertvector.dest_qt.childType(t.comp).scalarKind(t.comp); - - const items = try t.arena.alloc(ZigNode, dest_vec_ty.len); - for (items, 0..dest_vec_ty.len) |*item, i| { - const value = try ZigTag.array_access.create(t.arena, .{ - .lhs = tmp_ident, - .rhs = try t.createNumberNode(i, .int), - }); - - if (src_elem_sk == .float and dest_elem_sk == .float) { - item.* = try ZigTag.float_cast.create(t.arena, value); - } else if (src_elem_sk == .float) { - item.* = try ZigTag.int_from_float.create(t.arena, value); - } else if (dest_elem_sk == .float) { - item.* = try ZigTag.float_from_int.create(t.arena, value); - } else { - item.* = try t.transIntCast(value, src_vec_ty.elem, dest_vec_ty.elem); - } - } - - const vec_init = try ZigTag.array_init.create(t.arena, .{ - .cond = dest_type_node, - .cases = items, - }); - const break_node = try ZigTag.break_val.create(t.arena, .{ - .label = block_scope.label, - .val = vec_init, - }); - try block_scope.statements.append(t.gpa, break_node); - - return block_scope.complete(); -} - -fn transShufflevectorExpr( - t: *Translator, - scope: *Scope, - shufflevector: Node.Shufflevector, -) TransError!ZigNode { - if (shufflevector.indexes.len == 0) { - return t.fail(error.UnsupportedTranslation, shufflevector.builtin_tok, "@shuffle needs at least 1 index", .{}); - } - - const a = try t.transExpr(scope, shufflevector.lhs, .used); - const b = try t.transExpr(scope, shufflevector.rhs, .used); - - // First two arguments to __builtin_shufflevector must be the same type - const vector_child_type = try t.vectorTypeInfo(a, "child"); - const vector_len = try t.vectorTypeInfo(a, "len"); - const shuffle_mask = blk: { - const mask_len = shufflevector.indexes.len; - - const mask_type = try ZigTag.vector.create(t.arena, .{ - .lhs = try t.createNumberNode(mask_len, .int), - .rhs = try ZigTag.type.create(t.arena, "i32"), - }); - - const init_list = try t.arena.alloc(ZigNode, mask_len); - for (init_list, shufflevector.indexes) |*init, index| { - const index_expr = try t.transExprCoercing(scope, index, .used); - const converted_index = try t.createHelperCallNode(.shuffleVectorIndex, &.{ index_expr, vector_len }); - init.* = converted_index; - } - - break :blk try ZigTag.array_init.create(t.arena, .{ - .cond = mask_type, - .cases = init_list, - }); - }; - - return ZigTag.shuffle.create(t.arena, .{ - .element_type = vector_child_type, - .a = a, - .b = b, - .mask_vector = shuffle_mask, - }); -} - -// ===================== -// Node creation helpers -// ===================== - -fn createZeroValueNode( - t: *Translator, - qt: QualType, - type_node: ZigNode, - suppress_as: SuppressCast, -) !ZigNode { - switch (qt.base(t.comp).type) { - .bool => return ZigTag.false_literal.init(), - .int, .bit_int, .float => { - const zero_literal = ZigTag.zero_literal.init(); - return switch (suppress_as) { - .with_as => try t.createBinOpNode(.as, type_node, zero_literal), - .no_as => zero_literal, - }; - }, - .pointer => { - const null_literal = ZigTag.null_literal.init(); - return switch (suppress_as) { - .with_as => try t.createBinOpNode(.as, type_node, null_literal), - .no_as => null_literal, - }; - }, - else => {}, - } - return try ZigTag.std_mem_zeroes.create(t.arena, type_node); -} - -fn createIntNode(t: *Translator, int: aro.Value) !ZigNode { - var space: aro.Interner.Tag.Int.BigIntSpace = undefined; - var big = t.comp.interner.get(int.ref()).toBigInt(&space); - const is_negative = !big.positive; - big.positive = true; - - const str = big.toStringAlloc(t.arena, 10, .lower) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - }; - const res = try ZigTag.integer_literal.create(t.arena, str); - if (is_negative) return ZigTag.negate.create(t.arena, res); - return res; -} - -fn createNumberNode(t: *Translator, num: anytype, num_kind: enum { int, float }) !ZigNode { - const fmt_s = switch (@typeInfo(@TypeOf(num))) { - .int, .comptime_int => "{d}", - else => "{s}", - }; - const str = try std.fmt.allocPrint(t.arena, fmt_s, .{num}); - if (num_kind == .float) - return ZigTag.float_literal.create(t.arena, str) - else - return ZigTag.integer_literal.create(t.arena, str); -} - -fn createCharLiteralNode(t: *Translator, narrow: bool, val: u32) TransError!ZigNode { - return ZigTag.char_literal.create(t.arena, if (narrow) - try std.fmt.allocPrint(t.arena, "'{f}'", .{std.zig.fmtChar(&.{@as(u8, @intCast(val))})}) - else - try std.fmt.allocPrint(t.arena, "'\\u{{{x}}}'", .{val})); -} - -fn createBinOpNode( - t: *Translator, - op: ZigTag, - lhs: ZigNode, - rhs: ZigNode, -) !ZigNode { - const payload = try t.arena.create(ast.Payload.BinOp); - payload.* = .{ - .base = .{ .tag = op }, - .data = .{ - .lhs = lhs, - .rhs = rhs, - }, - }; - return ZigNode.initPayload(&payload.base); -} - -pub fn createHelperCallNode(t: *Translator, name: std.meta.DeclEnum(@import("helpers")), args_opt: ?[]const ZigNode) !ZigNode { - if (args_opt) |args| { - return ZigTag.helper_call.create(t.arena, .{ - .name = @tagName(name), - .args = try t.arena.dupe(ZigNode, args), - }); - } else { - return ZigTag.helper_ref.create(t.arena, @tagName(name)); - } -} - -/// Cast a signed integer node to a usize, for use in pointer arithmetic. Negative numbers -/// will become very large positive numbers but that is ok since we only use this in -/// pointer arithmetic expressions, where wraparound will ensure we get the correct value. -/// node -> @as(usize, @bitCast(@as(isize, @intCast(node)))) -fn usizeCastForWrappingPtrArithmetic(t: *Translator, node: ZigNode) TransError!ZigNode { - const intcast_node = try ZigTag.as.create(t.arena, .{ - .lhs = try ZigTag.type.create(t.arena, "isize"), - .rhs = try ZigTag.int_cast.create(t.arena, node), - }); - - return ZigTag.as.create(t.arena, .{ - .lhs = try ZigTag.type.create(t.arena, "usize"), - .rhs = try ZigTag.bit_cast.create(t.arena, intcast_node), - }); -} - -/// @typeInfo(@TypeOf(vec_node)).vector. -fn vectorTypeInfo(t: *Translator, vec_node: ZigNode, field: []const u8) TransError!ZigNode { - const typeof_call = try ZigTag.typeof.create(t.arena, vec_node); - const typeinfo_call = try ZigTag.typeinfo.create(t.arena, typeof_call); - const vector_type_info = try ZigTag.field_access.create(t.arena, .{ .lhs = typeinfo_call, .field_name = "vector" }); - return ZigTag.field_access.create(t.arena, .{ .lhs = vector_type_info, .field_name = field }); -} - -/// Build a getter function for a flexible array field in a C record -/// e.g. `T items[]` or `T items[0]`. The generated function returns a [*c] pointer -/// to the flexible array with the correct const and volatile qualifiers -fn createFlexibleMemberFn( - t: *Translator, - member_name: []const u8, - field_name: []const u8, -) Error!ZigNode { - const self_param_name = "self"; - const self_param = try ZigTag.identifier.create(t.arena, self_param_name); - const self_type = try ZigTag.typeof.create(t.arena, self_param); - - const fn_params = try t.arena.alloc(ast.Payload.Param, 1); - fn_params[0] = .{ - .name = self_param_name, - .type = ZigTag.@"anytype".init(), - .is_noalias = false, - }; - - // @typeInfo(@TypeOf(self.*.)).pointer.child - const dereffed = try ZigTag.deref.create(t.arena, self_param); - const field_access = try ZigTag.field_access.create(t.arena, .{ .lhs = dereffed, .field_name = field_name }); - const type_of = try ZigTag.typeof.create(t.arena, field_access); - const type_info = try ZigTag.typeinfo.create(t.arena, type_of); - const array_info = try ZigTag.field_access.create(t.arena, .{ .lhs = type_info, .field_name = "array" }); - const child_info = try ZigTag.field_access.create(t.arena, .{ .lhs = array_info, .field_name = "child" }); - - const return_type = try t.createHelperCallNode(.FlexibleArrayType, &.{ self_type, child_info }); - - // return @ptrCast(&self.*.); - const address_of = try ZigTag.address_of.create(t.arena, field_access); - const casted = try ZigTag.ptr_cast.create(t.arena, address_of); - const return_stmt = try ZigTag.@"return".create(t.arena, casted); - const body = try ZigTag.block_single.create(t.arena, return_stmt); - - return ZigTag.func.create(t.arena, .{ - .is_pub = true, - .is_extern = false, - .is_export = false, - .is_inline = false, - .is_var_args = false, - .name = member_name, - .linksection_string = null, - .explicit_callconv = null, - .params = fn_params, - .return_type = return_type, - .body = body, - .alignment = null, - }); -} - -// ================= -// Macro translation -// ================= - -fn transMacros(t: *Translator) !void { - var tok_list = std.ArrayList(CToken).init(t.gpa); - defer tok_list.deinit(); - - var pattern_list = try PatternList.init(t.gpa); - defer pattern_list.deinit(t.gpa); - - for (t.pp.defines.keys(), t.pp.defines.values()) |name, macro| { - if (macro.is_builtin) continue; - if (t.global_scope.containsNow(name)) { - continue; - } - - tok_list.items.len = 0; - try tok_list.ensureUnusedCapacity(macro.tokens.len); - for (macro.tokens) |tok| { - switch (tok.id) { - .invalid => continue, - .whitespace => continue, - .comment => continue, - .macro_ws => continue, - else => {}, - } - tok_list.appendAssumeCapacity(tok); - } - - if (macro.is_func) { - const ms: PatternList.MacroSlicer = .{ - .tokens = tok_list.items, - .source = t.comp.getSource(macro.loc.id).buf, - .params = @intCast(macro.params.len), - }; - if (try pattern_list.match(ms)) |impl| { - const decl = try ZigTag.pub_var_simple.create(t.arena, .{ - .name = name, - .init = try t.createHelperCallNode(impl, null), - }); - try t.addTopLevelDecl(name, decl); - continue; - } - } - - if (t.checkTranslatableMacro(tok_list.items, macro.params)) |err| { - switch (err) { - .undefined_identifier => |ident| try t.failDeclExtra(&t.global_scope.base, macro.loc, name, "unable to translate macro: undefined identifier `{s}`", .{ident}), - .invalid_arg_usage => |ident| try t.failDeclExtra(&t.global_scope.base, macro.loc, name, "unable to translate macro: untranslatable usage of arg `{s}`", .{ident}), - } - continue; - } - - var macro_translator: MacroTranslator = .{ - .t = t, - .tokens = tok_list.items, - .source = t.comp.getSource(macro.loc.id).buf, - .name = name, - .macro = macro, - }; - - const res = if (macro.is_func) - macro_translator.transFnMacro() - else - macro_translator.transMacro(); - res catch |err| switch (err) { - error.ParseError => continue, - error.OutOfMemory => |e| return e, - }; - } -} - -const MacroTranslateError = union(enum) { - undefined_identifier: []const u8, - invalid_arg_usage: []const u8, -}; - -fn checkTranslatableMacro(t: *Translator, tokens: []const CToken, params: []const []const u8) ?MacroTranslateError { - var last_is_type_kw = false; - var i: usize = 0; - while (i < tokens.len) : (i += 1) { - const token = tokens[i]; - switch (token.id) { - .period, .arrow => i += 1, // skip next token since field identifiers can be unknown - .keyword_struct, .keyword_union, .keyword_enum => if (!last_is_type_kw) { - last_is_type_kw = true; - continue; - }, - .macro_param, .macro_param_no_expand => { - if (last_is_type_kw) { - return .{ .invalid_arg_usage = params[token.end] }; - } - }, - .identifier, .extended_identifier => { - const identifier = t.pp.tokSlice(token); - if (!t.global_scope.contains(identifier) and !builtins.map.has(identifier)) { - return .{ .undefined_identifier = identifier }; - } - }, - else => {}, - } - last_is_type_kw = false; - } - return null; -} - -fn getContainer(t: *Translator, node: ZigNode) ?ZigNode { - switch (node.tag()) { - .@"union", - .@"struct", - .address_of, - .bit_not, - .not, - .optional_type, - .negate, - .negate_wrap, - .array_type, - .c_pointer, - .single_pointer, - => return node, - - .identifier => { - const ident = node.castTag(.identifier).?; - if (t.global_scope.sym_table.get(ident.data)) |value| { - if (value.castTag(.var_decl)) |var_decl| - return t.getContainer(var_decl.data.init.?); - if (value.castTag(.var_simple) orelse value.castTag(.pub_var_simple)) |var_decl| - return t.getContainer(var_decl.data.init); - } - }, - - .field_access => { - const field_access = node.castTag(.field_access).?; - - if (t.getContainerTypeOf(field_access.data.lhs)) |ty_node| { - if (ty_node.castTag(.@"struct") orelse ty_node.castTag(.@"union")) |container| { - for (container.data.fields) |field| { - if (mem.eql(u8, field.name, field_access.data.field_name)) { - return t.getContainer(field.type); - } - } - } - } - }, - - else => {}, - } - return null; -} - -fn getContainerTypeOf(t: *Translator, ref: ZigNode) ?ZigNode { - if (ref.castTag(.identifier)) |ident| { - if (t.global_scope.sym_table.get(ident.data)) |value| { - if (value.castTag(.var_decl)) |var_decl| { - return t.getContainer(var_decl.data.type); - } - } - } else if (ref.castTag(.field_access)) |field_access| { - if (t.getContainerTypeOf(field_access.data.lhs)) |ty_node| { - if (ty_node.castTag(.@"struct") orelse ty_node.castTag(.@"union")) |container| { - for (container.data.fields) |field| { - if (mem.eql(u8, field.name, field_access.data.field_name)) { - return t.getContainer(field.type); - } - } - } else return ty_node; - } - } - return null; -} - -pub fn getFnProto(t: *Translator, ref: ZigNode) ?*ast.Payload.Func { - const init = if (ref.castTag(.var_decl)) |v| - v.data.init orelse return null - else if (ref.castTag(.var_simple) orelse ref.castTag(.pub_var_simple)) |v| - v.data.init - else - return null; - if (t.getContainerTypeOf(init)) |ty_node| { - if (ty_node.castTag(.optional_type)) |prefix| { - if (prefix.data.castTag(.single_pointer)) |sp| { - if (sp.data.elem_type.castTag(.func)) |fn_proto| { - return fn_proto; - } - } - } - } - return null; -} diff --git a/lib/compiler/translate-c/src/ast.zig b/lib/compiler/translate-c/src/ast.zig deleted file mode 100644 index 264a23906f5d2a2cd2814e9a945e369dd954c0c5..0000000000000000000000000000000000000000 --- a/lib/compiler/translate-c/src/ast.zig +++ /dev/null @@ -1,3063 +0,0 @@ -const std = @import("std"); -const Allocator = std.mem.Allocator; - -pub const Node = extern union { - /// If the tag value is less than Tag.no_payload_count, then no pointer - /// dereference is needed. - tag_if_small_enough: usize, - ptr_otherwise: *Payload, - - pub const Tag = enum { - /// Declarations add themselves to the correct scopes and should not be emitted as this tag. - declaration, - null_literal, - undefined_literal, - /// opaque {} - opaque_literal, - true_literal, - false_literal, - empty_block, - return_void, - zero_literal, - one_literal, - @"unreachable", - void_type, - noreturn_type, - @"anytype", - @"continue", - @"break", - // After this, the tag requires a payload. - - integer_literal, - float_literal, - string_literal, - char_literal, - enum_literal, - /// "string"[0..end] - string_slice, - identifier, - @"if", - /// if (!operand) break; - if_not_break, - @"while", - /// while (true) operand - while_true, - @"switch", - /// else => operand, - switch_else, - /// items => body, - switch_prong, - break_val, - @"return", - field_access, - array_access, - call, - var_decl, - /// const name = struct { init } - wrapped_local, - /// var name = init.* - mut_str, - func, - warning, - @"struct", - @"union", - @"opaque", - @"comptime", - @"defer", - array_init, - tuple, - container_init, - container_init_dot, - /// _ = operand; - discard, - - // a + b - add, - // a = b - add_assign, - // c = (a = b) - add_wrap, - add_wrap_assign, - sub, - sub_assign, - sub_wrap, - sub_wrap_assign, - mul, - mul_assign, - mul_wrap, - mul_wrap_assign, - div, - div_assign, - shl, - shl_assign, - shr, - shr_assign, - mod, - mod_assign, - @"and", - @"or", - less_than, - less_than_equal, - greater_than, - greater_than_equal, - equal, - not_equal, - bit_and, - bit_and_assign, - bit_or, - bit_or_assign, - bit_xor, - bit_xor_assign, - array_cat, - ellipsis3, - assign, - - /// @intCast(operand) - int_cast, - /// @constCast(operand) - const_cast, - /// @volatileCast(operand) - volatile_cast, - /// @divTrunc(lhs, rhs) - div_trunc, - /// @intFromBool(operand) - int_from_bool, - /// @as(lhs, rhs) - as, - /// @truncate(operand) - truncate, - /// @bitCast(operand) - bit_cast, - /// @floatCast(operand) - float_cast, - /// @intFromFloat(operand) - int_from_float, - /// @floatFromInt(operand) - float_from_int, - /// @ptrFromInt(operand) - ptr_from_int, - /// @intFromPtr(operand) - int_from_ptr, - /// @alignCast(operand) - align_cast, - /// @ptrCast(operand) - ptr_cast, - /// @divExact(lhs, rhs) - div_exact, - /// @offsetOf(lhs, rhs) - offset_of, - /// @splat(operand) - vector_zero_init, - /// @shuffle(type, a, b, mask) - shuffle, - /// @extern(ty, .{ .name = n }) - builtin_extern, - - /// @byteSwap(operand) - byte_swap, - /// @ceil(operand) - ceil, - /// @cos(operand) - cos, - /// @sin(operand) - sin, - /// @exp(operand) - exp, - /// @exp2(operand) - exp2, - /// @exp10(operand) - exp10, - /// @abs(operand) - abs, - /// @log(operand) - log, - /// @log2(operand) - log2, - /// @log10(operand) - log10, - /// @round(operand) - round, - /// @sqrt(operand) - sqrt, - /// @trunc(operand) - trunc, - /// @floor(operand) - floor, - - /// __helpers.(argshelper_call) - helper_call, - /// __helpers. - helper_ref, - - asm_simple, - - negate, - negate_wrap, - bit_not, - not, - address_of, - /// .? - unwrap, - /// .* - deref, - - block, - /// { operand } - block_single, - - sizeof, - alignof, - typeof, - typeinfo, - type, - - optional_type, - c_pointer, - single_pointer, - array_type, - null_sentinel_array_type, - - /// @Vector(lhs, rhs) - vector, - /// @import("std").mem.zeroes(operand) - std_mem_zeroes, - /// @import("std").mem.zeroInit(lhs, rhs) - std_mem_zeroinit, - // pub const name = @compileError(msg); - fail_decl, - // var actual = mangled; - arg_redecl, - /// pub const alias = actual; - alias, - /// const name = init; - var_simple, - /// pub const name = init; - pub_var_simple, - /// pub? const name (: type)? = value - enum_constant, - - /// pub inline fn name(params) return_type body - pub_inline_fn, - - /// array_type{} - empty_array, - /// [1]type{val} ** count - array_filler, - - /// comptime { if (!(lhs)) @compileError(rhs); } - static_assert, - - pub const last_no_payload_tag = Tag.@"break"; - pub const no_payload_count = @intFromEnum(last_no_payload_tag) + 1; - - pub fn Type(comptime t: Tag) type { - return switch (t) { - .declaration, - .null_literal, - .undefined_literal, - .opaque_literal, - .true_literal, - .false_literal, - .empty_block, - .return_void, - .zero_literal, - .one_literal, - .void_type, - .noreturn_type, - .@"anytype", - .@"continue", - .@"break", - .@"unreachable", - => @compileError("Type Tag " ++ @tagName(t) ++ " has no payload"), - - .std_mem_zeroes, - .@"return", - .@"comptime", - .@"defer", - .asm_simple, - .negate, - .negate_wrap, - .bit_not, - .not, - .optional_type, - .address_of, - .unwrap, - .deref, - .int_from_ptr, - .empty_array, - .while_true, - .if_not_break, - .switch_else, - .block_single, - .int_from_bool, - .sizeof, - .alignof, - .typeof, - .typeinfo, - .align_cast, - .truncate, - .bit_cast, - .float_cast, - .int_from_float, - .float_from_int, - .ptr_from_int, - .ptr_cast, - .int_cast, - .const_cast, - .volatile_cast, - .vector_zero_init, - .byte_swap, - .ceil, - .cos, - .sin, - .exp, - .exp2, - .exp10, - .abs, - .log, - .log2, - .log10, - .round, - .sqrt, - .trunc, - .floor, - => Payload.UnOp, - - .add, - .add_assign, - .add_wrap, - .add_wrap_assign, - .sub, - .sub_assign, - .sub_wrap, - .sub_wrap_assign, - .mul, - .mul_assign, - .mul_wrap, - .mul_wrap_assign, - .div, - .div_assign, - .shl, - .shl_assign, - .shr, - .shr_assign, - .mod, - .mod_assign, - .@"and", - .@"or", - .less_than, - .less_than_equal, - .greater_than, - .greater_than_equal, - .equal, - .not_equal, - .bit_and, - .bit_and_assign, - .bit_or, - .bit_or_assign, - .bit_xor, - .bit_xor_assign, - .div_trunc, - .as, - .array_cat, - .ellipsis3, - .assign, - .array_access, - .std_mem_zeroinit, - .vector, - .div_exact, - .offset_of, - .static_assert, - => Payload.BinOp, - - .integer_literal, - .float_literal, - .string_literal, - .char_literal, - .enum_literal, - .identifier, - .warning, - .type, - => Payload.Value, - .discard => Payload.Discard, - .@"if" => Payload.If, - .@"while" => Payload.While, - .@"switch", .array_init, .switch_prong => Payload.Switch, - .break_val => Payload.BreakVal, - .call => Payload.Call, - .var_decl => Payload.VarDecl, - .func => Payload.Func, - .@"struct", .@"union", .@"opaque" => Payload.Container, - .tuple => Payload.TupleInit, - .container_init => Payload.ContainerInit, - .container_init_dot => Payload.ContainerInitDot, - .block => Payload.Block, - .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, .wrapped_local, .mut_str => Payload.SimpleVarDecl, - .enum_constant => Payload.EnumConstant, - .array_filler => Payload.ArrayFiller, - .pub_inline_fn => Payload.PubInlineFn, - .field_access => Payload.FieldAccess, - .string_slice => Payload.StringSlice, - .shuffle => Payload.Shuffle, - .builtin_extern => Payload.Extern, - .helper_call => Payload.HelperCall, - .helper_ref => Payload.HelperRef, - }; - } - - pub fn init(comptime t: Tag) Node { - comptime std.debug.assert(@intFromEnum(t) < Tag.no_payload_count); - return .{ .tag_if_small_enough = @intFromEnum(t) }; - } - - pub fn create(comptime t: Tag, ally: Allocator, data: Data(t)) error{OutOfMemory}!Node { - const ptr = try ally.create(t.Type()); - ptr.* = .{ - .base = .{ .tag = t }, - .data = data, - }; - return Node{ .ptr_otherwise = &ptr.base }; - } - - pub fn Data(comptime t: Tag) type { - return std.meta.fieldInfo(t.Type(), .data).type; - } - }; - - pub fn tag(self: Node) Tag { - if (self.tag_if_small_enough < Tag.no_payload_count) { - return @enumFromInt(@as(std.meta.Tag(Tag), @intCast(self.tag_if_small_enough))); - } else { - return self.ptr_otherwise.tag; - } - } - - pub fn castTag(self: Node, comptime t: Tag) ?*t.Type() { - if (self.tag_if_small_enough < Tag.no_payload_count) - return null; - - if (self.ptr_otherwise.tag == t) - return @alignCast(@fieldParentPtr("base", self.ptr_otherwise)); - - return null; - } - - pub fn initPayload(payload: *Payload) Node { - std.debug.assert(@intFromEnum(payload.tag) >= Tag.no_payload_count); - return .{ .ptr_otherwise = payload }; - } - - pub fn isNoreturn(node: Node, break_counts: bool) bool { - switch (node.tag()) { - .block => { - const block_node = node.castTag(.block).?; - if (block_node.data.stmts.len == 0) return false; - - const last = block_node.data.stmts[block_node.data.stmts.len - 1]; - return last.isNoreturn(break_counts); - }, - .@"switch" => { - const switch_node = node.castTag(.@"switch").?; - - for (switch_node.data.cases) |case| { - const body = if (case.castTag(.switch_else)) |some| - some.data - else if (case.castTag(.switch_prong)) |some| - some.data.cond - else - unreachable; - - if (!body.isNoreturn(break_counts)) return false; - } - return true; - }, - .@"return", .return_void => return true, - .@"break" => if (break_counts) return true, - else => {}, - } - return false; - } - - pub fn isBoolRes(res: Node) bool { - switch (res.tag()) { - .@"or", - .@"and", - .equal, - .not_equal, - .less_than, - .less_than_equal, - .greater_than, - .greater_than_equal, - .not, - .false_literal, - .true_literal, - => return true, - else => return false, - } - } -}; - -pub const Payload = struct { - tag: Node.Tag, - - pub const Value = struct { - base: Payload, - data: []const u8, - }; - - pub const UnOp = struct { - base: Payload, - data: Node, - }; - - pub const BinOp = struct { - base: Payload, - data: struct { - lhs: Node, - rhs: Node, - }, - }; - - pub const Discard = struct { - base: Payload, - data: struct { - should_skip: bool, - value: Node, - }, - }; - - pub const If = struct { - base: Payload, - data: struct { - cond: Node, - then: Node, - @"else": ?Node, - }, - }; - - pub const While = struct { - base: Payload, - data: struct { - cond: Node, - body: Node, - cont_expr: ?Node, - }, - }; - - pub const Switch = struct { - base: Payload, - data: struct { - cond: Node, - cases: []Node, - }, - }; - - pub const BreakVal = struct { - base: Payload, - data: struct { - label: ?[]const u8, - val: Node, - }, - }; - - pub const Call = struct { - base: Payload, - data: struct { - lhs: Node, - args: []Node, - }, - }; - - pub const VarDecl = struct { - base: Payload, - data: struct { - is_pub: bool, - is_const: bool, - is_extern: bool, - is_export: bool, - is_threadlocal: bool, - alignment: ?c_uint, - linksection_string: ?[]const u8, - name: []const u8, - type: Node, - init: ?Node, - }, - }; - - pub const Func = struct { - base: Payload, - data: struct { - is_pub: bool, - is_extern: bool, - is_export: bool, - is_inline: bool, - is_var_args: bool, - name: ?[]const u8, - linksection_string: ?[]const u8, - explicit_callconv: ?CallingConvention, - params: []Param, - return_type: Node, - body: ?Node, - alignment: ?c_uint, - }, - - pub const CallingConvention = enum { - c, - x86_64_sysv, - x86_64_win, - x86_stdcall, - x86_fastcall, - x86_thiscall, - x86_vectorcall, - x86_regcall, - aarch64_vfabi, - aarch64_sve_pcs, - arm_aapcs, - arm_aapcs_vfp, - m68k_rtd, - riscv_vector, - }; - }; - - pub const Param = struct { - is_noalias: bool, - name: ?[]const u8, - type: Node, - }; - - pub const Container = struct { - base: Payload, - data: struct { - layout: enum { @"packed", @"extern", none }, - fields: []Field, - decls: []Node, - }, - - pub const Field = struct { - name: []const u8, - type: Node, - alignment: ?c_uint, - default_value: ?Node, - }; - }; - - pub const TupleInit = struct { - base: Payload, - data: []Node, - }; - - pub const ContainerInit = struct { - base: Payload, - data: struct { - lhs: Node, - inits: []Initializer, - }, - - pub const Initializer = struct { - name: []const u8, - value: Node, - }; - }; - - pub const ContainerInitDot = struct { - base: Payload, - data: []Initializer, - - pub const Initializer = struct { - name: []const u8, - value: Node, - }; - }; - - pub const Block = struct { - base: Payload, - data: struct { - label: ?[]const u8, - stmts: []Node, - }, - }; - - pub const Array = struct { - base: Payload, - data: ArrayTypeInfo, - - pub const ArrayTypeInfo = struct { - elem_type: Node, - len: u64, - }; - }; - - pub const Pointer = struct { - base: Payload, - data: struct { - elem_type: Node, - is_const: bool, - is_volatile: bool, - is_allowzero: bool, - }, - }; - - pub const ArgRedecl = struct { - base: Payload, - data: struct { - actual: []const u8, - mangled: []const u8, - }, - }; - - pub const SimpleVarDecl = struct { - base: Payload, - data: struct { - name: []const u8, - init: Node, - }, - }; - - pub const EnumConstant = struct { - base: Payload, - data: struct { - name: []const u8, - is_public: bool, - type: ?Node, - value: Node, - }, - }; - - pub const ArrayFiller = struct { - base: Payload, - data: struct { - type: Node, - filler: Node, - count: u64, - }, - }; - - pub const PubInlineFn = struct { - base: Payload, - data: struct { - name: []const u8, - params: []Param, - return_type: Node, - body: Node, - }, - }; - - pub const FieldAccess = struct { - base: Payload, - data: struct { - lhs: Node, - field_name: []const u8, - }, - }; - - pub const StringSlice = struct { - base: Payload, - data: struct { - string: Node, - end: u64, - }, - }; - - pub const Shuffle = struct { - base: Payload, - data: struct { - element_type: Node, - a: Node, - b: Node, - mask_vector: Node, - }, - }; - - pub const Extern = struct { - base: Payload, - data: struct { - type: Node, - name: Node, - }, - }; - - pub const HelperCall = struct { - base: Payload, - data: struct { - name: []const u8, - args: []const Node, - }, - }; - - pub const HelperRef = struct { - base: Payload, - data: []const u8, - }; -}; - -/// Converts the nodes into a Zig Ast. -/// Caller must free the source slice. -pub fn render(gpa: Allocator, nodes: []const Node) !std.zig.Ast { - var ctx: Context = .{ - .gpa = gpa, - .buf = std.array_list.Managed(u8).init(gpa), - }; - defer ctx.buf.deinit(); - defer ctx.nodes.deinit(gpa); - defer ctx.extra_data.deinit(gpa); - defer ctx.tokens.deinit(gpa); - - // Estimate that each top level node has 10 child nodes. - const estimated_node_count = nodes.len * 10 + 1; // +1 for the .root node - try ctx.nodes.ensureTotalCapacity(gpa, estimated_node_count); - // Estimate that each each node has 2 tokens. - const estimated_tokens_count = estimated_node_count * 2; - try ctx.tokens.ensureTotalCapacity(gpa, estimated_tokens_count); - // Estimate that each each token is 3 bytes long. - const estimated_buf_len = estimated_tokens_count * 3; - try ctx.buf.ensureTotalCapacity(estimated_buf_len); - - ctx.nodes.appendAssumeCapacity(.{ - .tag = .root, - .main_token = 0, - .data = undefined, - }); - - const root_members = blk: { - var result = std.array_list.Managed(NodeIndex).init(gpa); - defer result.deinit(); - - for (nodes) |node| { - const res = (try renderNodeOpt(&ctx, node)) orelse continue; - try result.append(res); - } - break :blk try ctx.listToSpan(result.items); - }; - - ctx.nodes.items(.data)[0] = .{ .extra_range = .{ - .start = root_members.start, - .end = root_members.end, - } }; - - try ctx.tokens.append(gpa, .{ - .tag = .eof, - .start = @as(u32, @intCast(ctx.buf.items.len)), - }); - - return .{ - .source = try ctx.buf.toOwnedSliceSentinel(0), - .tokens = ctx.tokens.toOwnedSlice(), - .nodes = ctx.nodes.toOwnedSlice(), - .extra_data = try ctx.extra_data.toOwnedSlice(gpa), - .errors = &.{}, - .mode = .zig, - }; -} - -const NodeIndex = std.zig.Ast.Node.Index; -const NodeSubRange = std.zig.Ast.Node.SubRange; -const TokenIndex = std.zig.Ast.TokenIndex; -const TokenTag = std.zig.Token.Tag; - -const Context = struct { - gpa: Allocator, - buf: std.array_list.Managed(u8), - nodes: std.zig.Ast.NodeList = .{}, - extra_data: std.ArrayListUnmanaged(u32) = .empty, - tokens: std.zig.Ast.TokenList = .{}, - - fn addTokenFmt(c: *Context, tag: TokenTag, comptime format: []const u8, args: anytype) Allocator.Error!TokenIndex { - const start_index = c.buf.items.len; - try c.buf.print(format ++ " ", args); - - try c.tokens.append(c.gpa, .{ - .tag = tag, - .start = @intCast(start_index), - }); - - return @intCast(c.tokens.len - 1); - } - - fn addToken(c: *Context, tag: TokenTag, bytes: []const u8) Allocator.Error!TokenIndex { - return c.addTokenFmt(tag, "{s}", .{bytes}); - } - - fn addIdentifier(c: *Context, bytes: []const u8) Allocator.Error!TokenIndex { - if (std.zig.primitives.isPrimitive(bytes)) - return c.addTokenFmt(.identifier, "@\"{s}\"", .{bytes}); - return c.addTokenFmt(.identifier, "{f}", .{std.zig.fmtId(bytes)}); - } - - fn listToSpan(c: *Context, list: []const NodeIndex) Allocator.Error!NodeSubRange { - try c.extra_data.appendSlice(c.gpa, @ptrCast(list)); - return .{ - .start = @enumFromInt(c.extra_data.items.len - list.len), - .end = @enumFromInt(c.extra_data.items.len), - }; - } - - fn addNode(c: *Context, elem: std.zig.Ast.Node) Allocator.Error!NodeIndex { - const result: NodeIndex = @enumFromInt(c.nodes.len); - try c.nodes.append(c.gpa, elem); - return result; - } - - fn addExtra(c: *Context, extra: anytype) Allocator.Error!std.zig.Ast.ExtraIndex { - const fields = std.meta.fields(@TypeOf(extra)); - try c.extra_data.ensureUnusedCapacity(c.gpa, fields.len); - const result: std.zig.Ast.ExtraIndex = @enumFromInt(c.extra_data.items.len); - inline for (fields) |field| { - const data: u32 = switch (field.type) { - NodeIndex, - std.zig.Ast.Node.OptionalIndex, - std.zig.Ast.OptionalTokenIndex, - std.zig.Ast.ExtraIndex, - => @intFromEnum(@field(extra, field.name)), - TokenIndex, - => @field(extra, field.name), - else => @compileError("unexpected field type"), - }; - c.extra_data.appendAssumeCapacity(data); - } - return result; - } -}; - -fn renderNodeOpt(c: *Context, node: Node) Allocator.Error!?NodeIndex { - switch (node.tag()) { - .warning => { - const payload = node.castTag(.warning).?.data; - try c.buf.appendSlice(payload); - try c.buf.append('\n'); - return null; - }, - .discard => { - const payload = node.castTag(.discard).?.data; - if (payload.should_skip) return null; - - return try renderNode(c, node); - }, - else => return try renderNode(c, node), - } -} - -fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex { - switch (node.tag()) { - .declaration => unreachable, - .warning => unreachable, - .discard => { - const payload = node.castTag(.discard).?.data; - std.debug.assert(!payload.should_skip); - - const lhs = try c.addNode(.{ - .tag = .identifier, - .main_token = try c.addToken(.identifier, "_"), - .data = undefined, - }); - const main_token = try c.addToken(.equal, "="); - if (payload.value.tag() == .identifier) { - // Render as `_ = &foo;` to avoid tripping "pointless discard" and "local variable never mutated" errors. - var addr_of_pl: Payload.UnOp = .{ - .base = .{ .tag = .address_of }, - .data = payload.value, - }; - const addr_of: Node = .{ .ptr_otherwise = &addr_of_pl.base }; - return try c.addNode(.{ - .tag = .assign, - .main_token = main_token, - .data = .{ .node_and_node = .{ - lhs, try renderNode(c, addr_of), - } }, - }); - } else { - return try c.addNode(.{ - .tag = .assign, - .main_token = main_token, - .data = .{ .node_and_node = .{ - lhs, try renderNode(c, payload.value), - } }, - }); - } - }, - .std_mem_zeroes => { - const payload = node.castTag(.std_mem_zeroes).?.data; - const import_node = try renderStdImport(c, &.{ "mem", "zeroes" }); - return renderCall(c, import_node, &.{payload}); - }, - .std_mem_zeroinit => { - const payload = node.castTag(.std_mem_zeroinit).?.data; - const import_node = try renderStdImport(c, &.{ "mem", "zeroInit" }); - return renderCall(c, import_node, &.{ payload.lhs, payload.rhs }); - }, - .vector => { - const payload = node.castTag(.vector).?.data; - return renderBuiltinCall(c, "@Vector", &.{ payload.lhs, payload.rhs }); - }, - .call => { - const payload = node.castTag(.call).?.data; - const lhs = try renderNodeGrouped(c, payload.lhs); - return renderCall(c, lhs, payload.args); - }, - .null_literal => return c.addNode(.{ - .tag = .identifier, - .main_token = try c.addToken(.identifier, "null"), - .data = undefined, - }), - .undefined_literal => return c.addNode(.{ - .tag = .identifier, - .main_token = try c.addToken(.identifier, "undefined"), - .data = undefined, - }), - .true_literal => return c.addNode(.{ - .tag = .identifier, - .main_token = try c.addToken(.identifier, "true"), - .data = undefined, - }), - .false_literal => return c.addNode(.{ - .tag = .identifier, - .main_token = try c.addToken(.identifier, "false"), - .data = undefined, - }), - .zero_literal => return c.addNode(.{ - .tag = .number_literal, - .main_token = try c.addToken(.number_literal, "0"), - .data = undefined, - }), - .one_literal => return c.addNode(.{ - .tag = .number_literal, - .main_token = try c.addToken(.number_literal, "1"), - .data = undefined, - }), - .@"unreachable" => return c.addNode(.{ - .tag = .unreachable_literal, - .main_token = try c.addToken(.keyword_unreachable, "unreachable"), - .data = undefined, - }), - .void_type => return c.addNode(.{ - .tag = .identifier, - .main_token = try c.addToken(.identifier, "void"), - .data = undefined, - }), - .noreturn_type => return c.addNode(.{ - .tag = .identifier, - .main_token = try c.addToken(.identifier, "noreturn"), - .data = undefined, - }), - .@"continue" => return c.addNode(.{ - .tag = .@"continue", - .main_token = try c.addToken(.keyword_continue, "continue"), - .data = .{ .opt_token_and_opt_node = .{ - .none, .none, - } }, - }), - .return_void => return c.addNode(.{ - .tag = .@"return", - .main_token = try c.addToken(.keyword_return, "return"), - .data = .{ .opt_node = .none }, - }), - .@"break" => return c.addNode(.{ - .tag = .@"break", - .main_token = try c.addToken(.keyword_break, "break"), - .data = .{ .opt_token_and_opt_node = .{ - .none, .none, - } }, - }), - .break_val => { - const payload = node.castTag(.break_val).?.data; - const tok = try c.addToken(.keyword_break, "break"); - const break_label = if (payload.label) |some| blk: { - _ = try c.addToken(.colon, ":"); - break :blk try c.addIdentifier(some); - } else 0; - return c.addNode(.{ - .tag = .@"break", - .main_token = tok, - .data = .{ .opt_token_and_opt_node = .{ - .fromToken(break_label), (try renderNode(c, payload.val)).toOptional(), - } }, - }); - }, - .@"return" => { - const payload = node.castTag(.@"return").?.data; - return c.addNode(.{ - .tag = .@"return", - .main_token = try c.addToken(.keyword_return, "return"), - .data = .{ .opt_node = (try renderNode(c, payload)).toOptional() }, - }); - }, - .@"comptime" => { - const payload = node.castTag(.@"comptime").?.data; - return c.addNode(.{ - .tag = .@"comptime", - .main_token = try c.addToken(.keyword_comptime, "comptime"), - .data = .{ - .node = try renderNode(c, payload), - }, - }); - }, - .@"defer" => { - const payload = node.castTag(.@"defer").?.data; - return c.addNode(.{ - .tag = .@"defer", - .main_token = try c.addToken(.keyword_defer, "defer"), - .data = .{ - .node = try renderNode(c, payload), - }, - }); - }, - .asm_simple => { - const payload = node.castTag(.asm_simple).?.data; - const asm_token = try c.addToken(.keyword_asm, "asm"); - _ = try c.addToken(.l_paren, "("); - return c.addNode(.{ - .tag = .asm_simple, - .main_token = asm_token, - .data = .{ .node_and_token = .{ - try renderNode(c, payload), - try c.addToken(.r_paren, ")"), - } }, - }); - }, - .type => { - const payload = node.castTag(.type).?.data; - return c.addNode(.{ - .tag = .identifier, - .main_token = try c.addToken(.identifier, payload), - .data = undefined, - }); - }, - .identifier => { - const payload = node.castTag(.identifier).?.data; - return c.addNode(.{ - .tag = .identifier, - .main_token = try c.addIdentifier(payload), - .data = undefined, - }); - }, - .float_literal => { - const payload = node.castTag(.float_literal).?.data; - return c.addNode(.{ - .tag = .number_literal, - .main_token = try c.addToken(.number_literal, payload), - .data = undefined, - }); - }, - .integer_literal => { - const payload = node.castTag(.integer_literal).?.data; - return c.addNode(.{ - .tag = .number_literal, - .main_token = try c.addToken(.number_literal, payload), - .data = undefined, - }); - }, - .string_literal => { - const payload = node.castTag(.string_literal).?.data; - return c.addNode(.{ - .tag = .string_literal, - .main_token = try c.addToken(.string_literal, payload), - .data = undefined, - }); - }, - .char_literal => { - const payload = node.castTag(.char_literal).?.data; - return c.addNode(.{ - .tag = .char_literal, - .main_token = try c.addToken(.char_literal, payload), - .data = undefined, - }); - }, - .enum_literal => { - const payload = node.castTag(.enum_literal).?.data; - _ = try c.addToken(.period, "."); - return c.addNode(.{ - .tag = .enum_literal, - .main_token = try c.addToken(.identifier, payload), - .data = undefined, - }); - }, - .string_slice => { - const payload = node.castTag(.string_slice).?.data; - - const string = try renderNode(c, payload.string); - const l_bracket = try c.addToken(.l_bracket, "["); - const start = try c.addNode(.{ - .tag = .number_literal, - .main_token = try c.addToken(.number_literal, "0"), - .data = undefined, - }); - _ = try c.addToken(.ellipsis2, ".."); - const end = try c.addNode(.{ - .tag = .number_literal, - .main_token = try c.addTokenFmt(.number_literal, "{d}", .{payload.end}), - .data = undefined, - }); - _ = try c.addToken(.r_bracket, "]"); - - return c.addNode(.{ - .tag = .slice, - .main_token = l_bracket, - .data = .{ .node_and_extra = .{ - string, try c.addExtra(std.zig.Ast.Node.Slice{ - .start = start, - .end = end, - }), - } }, - }); - }, - .fail_decl => { - const payload = node.castTag(.fail_decl).?.data; - // pub const name = @compileError(msg); - _ = try c.addToken(.keyword_pub, "pub"); - const const_tok = try c.addToken(.keyword_const, "const"); - _ = try c.addIdentifier(payload.actual); - _ = try c.addToken(.equal, "="); - - const compile_error_tok = try c.addToken(.builtin, "@compileError"); - _ = try c.addToken(.l_paren, "("); - const err_msg_tok = try c.addTokenFmt(.string_literal, "\"{f}\"", .{std.zig.fmtString(payload.mangled)}); - const err_msg = try c.addNode(.{ - .tag = .string_literal, - .main_token = err_msg_tok, - .data = undefined, - }); - _ = try c.addToken(.r_paren, ")"); - const compile_error = try c.addNode(.{ - .tag = .builtin_call_two, - .main_token = compile_error_tok, - .data = .{ .opt_node_and_opt_node = .{ - err_msg.toOptional(), .none, - } }, - }); - _ = try c.addToken(.semicolon, ";"); - - return c.addNode(.{ - .tag = .simple_var_decl, - .main_token = const_tok, - .data = .{ - .opt_node_and_opt_node = .{ - .none, // Type expression - compile_error.toOptional(), // Init expression - }, - }, - }); - }, - .pub_var_simple, .var_simple => { - const payload = @as(*Payload.SimpleVarDecl, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data; - if (node.tag() == .pub_var_simple) _ = try c.addToken(.keyword_pub, "pub"); - const const_tok = try c.addToken(.keyword_const, "const"); - _ = try c.addIdentifier(payload.name); - _ = try c.addToken(.equal, "="); - - const init = try renderNode(c, payload.init); - _ = try c.addToken(.semicolon, ";"); - - return c.addNode(.{ - .tag = .simple_var_decl, - .main_token = const_tok, - .data = .{ - .opt_node_and_opt_node = .{ - .none, // Type expression - init.toOptional(), // Init expression - }, - }, - }); - }, - .wrapped_local => { - const payload = node.castTag(.wrapped_local).?.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 = .{ .opt_node_and_opt_node = .{ - (try renderNode(c, payload.init)).toOptional(), .none, - } }, - }); - _ = try c.addToken(.r_brace, "}"); - _ = try c.addToken(.semicolon, ";"); - - return c.addNode(.{ - .tag = .simple_var_decl, - .main_token = const_tok, - .data = .{ - .opt_node_and_opt_node = .{ - .none, // Type expression - container_def.toOptional(), // Init expression - }, - }, - }); - }, - .mut_str => { - const payload = node.castTag(.mut_str).?.data; - - const var_tok = try c.addToken(.keyword_var, "var"); - _ = try c.addIdentifier(payload.name); - _ = try c.addToken(.equal, "="); - - const deref = try c.addNode(.{ - .tag = .deref, - .data = .{ - .node = try renderNodeGrouped(c, payload.init), - }, - .main_token = try c.addToken(.period_asterisk, ".*"), - }); - _ = try c.addToken(.semicolon, ";"); - - return c.addNode(.{ - .tag = .simple_var_decl, - .main_token = var_tok, - .data = .{ - .opt_node_and_opt_node = .{ - .none, // Type expression - deref.toOptional(), // Init expression - }, - }, - }); - }, - .var_decl => return renderVar(c, node), - .arg_redecl, .alias => { - const payload = @as(*Payload.ArgRedecl, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data; - if (node.tag() == .alias) _ = try c.addToken(.keyword_pub, "pub"); - const mut_tok = if (node.tag() == .alias) - try c.addToken(.keyword_const, "const") - else - try c.addToken(.keyword_var, "var"); - _ = try c.addIdentifier(payload.actual); - _ = try c.addToken(.equal, "="); - - const init = try c.addNode(.{ - .tag = .identifier, - .main_token = try c.addIdentifier(payload.mangled), - .data = undefined, - }); - _ = try c.addToken(.semicolon, ";"); - - return c.addNode(.{ - .tag = .simple_var_decl, - .main_token = mut_tok, - .data = .{ - .opt_node_and_opt_node = .{ - .none, // Type expression - init.toOptional(), // Init expression - }, - }, - }); - }, - .int_cast => { - const payload = node.castTag(.int_cast).?.data; - return renderBuiltinCall(c, "@intCast", &.{payload}); - }, - .const_cast => { - const payload = node.castTag(.const_cast).?.data; - return renderBuiltinCall(c, "@constCast", &.{payload}); - }, - .volatile_cast => { - const payload = node.castTag(.volatile_cast).?.data; - return renderBuiltinCall(c, "@volatileCast", &.{payload}); - }, - .div_trunc => { - const payload = node.castTag(.div_trunc).?.data; - return renderBuiltinCall(c, "@divTrunc", &.{ payload.lhs, payload.rhs }); - }, - .int_from_bool => { - const payload = node.castTag(.int_from_bool).?.data; - return renderBuiltinCall(c, "@intFromBool", &.{payload}); - }, - .as => { - const payload = node.castTag(.as).?.data; - return renderBuiltinCall(c, "@as", &.{ payload.lhs, payload.rhs }); - }, - .truncate => { - const payload = node.castTag(.truncate).?.data; - return renderBuiltinCall(c, "@truncate", &.{payload}); - }, - .bit_cast => { - const payload = node.castTag(.bit_cast).?.data; - return renderBuiltinCall(c, "@bitCast", &.{payload}); - }, - .float_cast => { - const payload = node.castTag(.float_cast).?.data; - return renderBuiltinCall(c, "@floatCast", &.{payload}); - }, - .int_from_float => { - const payload = node.castTag(.int_from_float).?.data; - return renderBuiltinCall(c, "@intFromFloat", &.{payload}); - }, - .float_from_int => { - const payload = node.castTag(.float_from_int).?.data; - return renderBuiltinCall(c, "@floatFromInt", &.{payload}); - }, - .ptr_from_int => { - const payload = node.castTag(.ptr_from_int).?.data; - return renderBuiltinCall(c, "@ptrFromInt", &.{payload}); - }, - .int_from_ptr => { - const payload = node.castTag(.int_from_ptr).?.data; - return renderBuiltinCall(c, "@intFromPtr", &.{payload}); - }, - .align_cast => { - const payload = node.castTag(.align_cast).?.data; - return renderBuiltinCall(c, "@alignCast", &.{payload}); - }, - .ptr_cast => { - const payload = node.castTag(.ptr_cast).?.data; - return renderBuiltinCall(c, "@ptrCast", &.{payload}); - }, - .div_exact => { - const payload = node.castTag(.div_exact).?.data; - return renderBuiltinCall(c, "@divExact", &.{ payload.lhs, payload.rhs }); - }, - .offset_of => { - const payload = node.castTag(.offset_of).?.data; - return renderBuiltinCall(c, "@offsetOf", &.{ payload.lhs, payload.rhs }); - }, - .sizeof => { - const payload = node.castTag(.sizeof).?.data; - return renderBuiltinCall(c, "@sizeOf", &.{payload}); - }, - .shuffle => { - const payload = node.castTag(.shuffle).?.data; - return renderBuiltinCall(c, "@shuffle", &.{ - payload.element_type, - payload.a, - payload.b, - payload.mask_vector, - }); - }, - .builtin_extern => { - const payload = node.castTag(.builtin_extern).?.data; - - var info_inits: [1]Payload.ContainerInitDot.Initializer = .{ - .{ .name = "name", .value = payload.name }, - }; - var info_payload: Payload.ContainerInitDot = .{ - .base = .{ .tag = .container_init_dot }, - .data = &info_inits, - }; - - return renderBuiltinCall(c, "@extern", &.{ - payload.type, - .{ .ptr_otherwise = &info_payload.base }, - }); - }, - .helper_call => { - const payload = node.castTag(.helper_call).?.data; - const helpers_tok = try c.addNode(.{ - .tag = .identifier, - .main_token = try c.addIdentifier("__helpers"), - .data = undefined, - }); - const func = try renderFieldAccess(c, helpers_tok, payload.name); - return renderCall(c, func, payload.args); - }, - .helper_ref => { - const payload = node.castTag(.helper_ref).?.data; - const helpers_tok = try c.addNode(.{ - .tag = .identifier, - .main_token = try c.addIdentifier("__helpers"), - .data = undefined, - }); - return renderFieldAccess(c, helpers_tok, payload); - }, - .alignof => { - const payload = node.castTag(.alignof).?.data; - return renderBuiltinCall(c, "@alignOf", &.{payload}); - }, - .typeof => { - const payload = node.castTag(.typeof).?.data; - return renderBuiltinCall(c, "@TypeOf", &.{payload}); - }, - .typeinfo => { - const payload = node.castTag(.typeinfo).?.data; - return renderBuiltinCall(c, "@typeInfo", &.{payload}); - }, - .byte_swap => { - const payload = node.castTag(.byte_swap).?.data; - return renderBuiltinCall(c, "@byteSwap", &.{payload}); - }, - .ceil => { - const payload = node.castTag(.ceil).?.data; - return renderBuiltinCall(c, "@ceil", &.{payload}); - }, - .cos => { - const payload = node.castTag(.cos).?.data; - return renderBuiltinCall(c, "@cos", &.{payload}); - }, - .sin => { - const payload = node.castTag(.sin).?.data; - return renderBuiltinCall(c, "@sin", &.{payload}); - }, - .exp => { - const payload = node.castTag(.exp).?.data; - return renderBuiltinCall(c, "@exp", &.{payload}); - }, - .exp2 => { - const payload = node.castTag(.exp2).?.data; - return renderBuiltinCall(c, "@exp2", &.{payload}); - }, - .exp10 => { - const payload = node.castTag(.exp10).?.data; - return renderBuiltinCall(c, "@exp10", &.{payload}); - }, - .abs => { - const payload = node.castTag(.abs).?.data; - return renderBuiltinCall(c, "@abs", &.{payload}); - }, - .log => { - const payload = node.castTag(.log).?.data; - return renderBuiltinCall(c, "@log", &.{payload}); - }, - .log2 => { - const payload = node.castTag(.log2).?.data; - return renderBuiltinCall(c, "@log2", &.{payload}); - }, - .log10 => { - const payload = node.castTag(.log10).?.data; - return renderBuiltinCall(c, "@log10", &.{payload}); - }, - .round => { - const payload = node.castTag(.round).?.data; - return renderBuiltinCall(c, "@round", &.{payload}); - }, - .sqrt => { - const payload = node.castTag(.sqrt).?.data; - return renderBuiltinCall(c, "@sqrt", &.{payload}); - }, - .trunc => { - const payload = node.castTag(.trunc).?.data; - return renderBuiltinCall(c, "@trunc", &.{payload}); - }, - .floor => { - const payload = node.castTag(.floor).?.data; - return renderBuiltinCall(c, "@floor", &.{payload}); - }, - .negate => return renderPrefixOp(c, node, .negation, .minus, "-"), - .negate_wrap => return renderPrefixOp(c, node, .negation_wrap, .minus_percent, "-%"), - .bit_not => return renderPrefixOp(c, node, .bit_not, .tilde, "~"), - .not => return renderPrefixOp(c, node, .bool_not, .bang, "!"), - .optional_type => return renderPrefixOp(c, node, .optional_type, .question_mark, "?"), - .address_of => { - const payload = node.castTag(.address_of).?.data; - - const ampersand = try c.addToken(.ampersand, "&"); - const base = try renderNodeGrouped(c, payload); - return c.addNode(.{ - .tag = .address_of, - .main_token = ampersand, - .data = .{ - .node = base, - }, - }); - }, - .deref => { - const payload = node.castTag(.deref).?.data; - const operand = try renderNodeGrouped(c, payload); - const deref_tok = try c.addToken(.period_asterisk, ".*"); - return c.addNode(.{ - .tag = .deref, - .main_token = deref_tok, - .data = .{ - .node = operand, - }, - }); - }, - .unwrap => { - const payload = node.castTag(.unwrap).?.data; - const operand = try renderNodeGrouped(c, payload); - const period = try c.addToken(.period, "."); - const question_mark = try c.addToken(.question_mark, "?"); - return c.addNode(.{ - .tag = .unwrap_optional, - .main_token = period, - .data = .{ .node_and_token = .{ - operand, question_mark, - } }, - }); - }, - .c_pointer, .single_pointer => { - const payload = @as(*Payload.Pointer, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data; - - const main_token = if (node.tag() == .single_pointer) - try c.addToken(.asterisk, "*") - else blk: { - const res = try c.addToken(.l_bracket, "["); - _ = try c.addToken(.asterisk, "*"); - _ = try c.addIdentifier("c"); - _ = try c.addToken(.r_bracket, "]"); - break :blk res; - }; - if (payload.is_const) _ = try c.addToken(.keyword_const, "const"); - if (payload.is_volatile) _ = try c.addToken(.keyword_volatile, "volatile"); - if (payload.is_allowzero) _ = try c.addToken(.keyword_allowzero, "allowzero"); - const elem_type = try renderNodeGrouped(c, payload.elem_type); - - return c.addNode(.{ - .tag = .ptr_type_aligned, - .main_token = main_token, - .data = .{ - .opt_node_and_node = .{ - .none, // Align node - elem_type, - }, - }, - }); - }, - .add => return renderBinOpGrouped(c, node, .add, .plus, "+"), - .add_assign => return renderBinOp(c, node, .assign_add, .plus_equal, "+="), - .add_wrap => return renderBinOpGrouped(c, node, .add_wrap, .plus_percent, "+%"), - .add_wrap_assign => return renderBinOp(c, node, .assign_add_wrap, .plus_percent_equal, "+%="), - .sub => return renderBinOpGrouped(c, node, .sub, .minus, "-"), - .sub_assign => return renderBinOp(c, node, .assign_sub, .minus_equal, "-="), - .sub_wrap => return renderBinOpGrouped(c, node, .sub_wrap, .minus_percent, "-%"), - .sub_wrap_assign => return renderBinOp(c, node, .assign_sub_wrap, .minus_percent_equal, "-%="), - .mul => return renderBinOpGrouped(c, node, .mul, .asterisk, "*"), - .mul_assign => return renderBinOp(c, node, .assign_mul, .asterisk_equal, "*="), - .mul_wrap => return renderBinOpGrouped(c, node, .mul_wrap, .asterisk_percent, "*%"), - .mul_wrap_assign => return renderBinOp(c, node, .assign_mul_wrap, .asterisk_percent_equal, "*%="), - .div => return renderBinOpGrouped(c, node, .div, .slash, "/"), - .div_assign => return renderBinOp(c, node, .assign_div, .slash_equal, "/="), - .shl => return renderBinOpGrouped(c, node, .shl, .angle_bracket_angle_bracket_left, "<<"), - .shl_assign => return renderBinOp(c, node, .assign_shl, .angle_bracket_angle_bracket_left_equal, "<<="), - .shr => return renderBinOpGrouped(c, node, .shr, .angle_bracket_angle_bracket_right, ">>"), - .shr_assign => return renderBinOp(c, node, .assign_shr, .angle_bracket_angle_bracket_right_equal, ">>="), - .mod => return renderBinOpGrouped(c, node, .mod, .percent, "%"), - .mod_assign => return renderBinOp(c, node, .assign_mod, .percent_equal, "%="), - .@"and" => return renderBinOpGrouped(c, node, .bool_and, .keyword_and, "and"), - .@"or" => return renderBinOpGrouped(c, node, .bool_or, .keyword_or, "or"), - .less_than => return renderBinOpGrouped(c, node, .less_than, .angle_bracket_left, "<"), - .less_than_equal => return renderBinOpGrouped(c, node, .less_or_equal, .angle_bracket_left_equal, "<="), - .greater_than => return renderBinOpGrouped(c, node, .greater_than, .angle_bracket_right, ">="), - .greater_than_equal => return renderBinOpGrouped(c, node, .greater_or_equal, .angle_bracket_right_equal, ">="), - .equal => return renderBinOpGrouped(c, node, .equal_equal, .equal_equal, "=="), - .not_equal => return renderBinOpGrouped(c, node, .bang_equal, .bang_equal, "!="), - .bit_and => return renderBinOpGrouped(c, node, .bit_and, .ampersand, "&"), - .bit_and_assign => return renderBinOp(c, node, .assign_bit_and, .ampersand_equal, "&="), - .bit_or => return renderBinOpGrouped(c, node, .bit_or, .pipe, "|"), - .bit_or_assign => return renderBinOp(c, node, .assign_bit_or, .pipe_equal, "|="), - .bit_xor => return renderBinOpGrouped(c, node, .bit_xor, .caret, "^"), - .bit_xor_assign => return renderBinOp(c, node, .assign_bit_xor, .caret_equal, "^="), - .array_cat => return renderBinOp(c, node, .array_cat, .plus_plus, "++"), - .ellipsis3 => return renderBinOpGrouped(c, node, .switch_range, .ellipsis3, "..."), - .assign => return renderBinOp(c, node, .assign, .equal, "="), - .empty_block => { - const l_brace = try c.addToken(.l_brace, "{"); - _ = try c.addToken(.r_brace, "}"); - return c.addNode(.{ - .tag = .block_two, - .main_token = l_brace, - .data = .{ .opt_node_and_opt_node = .{ - .none, .none, - } }, - }); - }, - .block_single => { - const payload = node.castTag(.block_single).?.data; - const l_brace = try c.addToken(.l_brace, "{"); - - const stmt = (try renderNodeOpt(c, payload)) orelse { - _ = try c.addToken(.r_brace, "}"); - return c.addNode(.{ - .tag = .block_two, - .main_token = l_brace, - .data = .{ .opt_node_and_opt_node = .{ - .none, .none, - } }, - }); - }; - try addSemicolonIfNeeded(c, payload); - - _ = try c.addToken(.r_brace, "}"); - return c.addNode(.{ - .tag = .block_two_semicolon, - .main_token = l_brace, - .data = .{ .opt_node_and_opt_node = .{ - stmt.toOptional(), .none, - } }, - }); - }, - .block => { - const payload = node.castTag(.block).?.data; - if (payload.label) |some| { - _ = try c.addIdentifier(some); - _ = try c.addToken(.colon, ":"); - } - const l_brace = try c.addToken(.l_brace, "{"); - - var stmts = std.array_list.Managed(NodeIndex).init(c.gpa); - defer stmts.deinit(); - for (payload.stmts) |stmt| { - const res = (try renderNodeOpt(c, stmt)) orelse continue; - try addSemicolonIfNeeded(c, stmt); - try stmts.append(res); - } - const span = try c.listToSpan(stmts.items); - _ = try c.addToken(.r_brace, "}"); - - const semicolon = c.tokens.items(.tag)[c.tokens.len - 2] == .semicolon; - return c.addNode(.{ - .tag = if (semicolon) .block_semicolon else .block, - .main_token = l_brace, - .data = .{ .extra_range = span }, - }); - }, - .func => return renderFunc(c, node), - .pub_inline_fn => return renderMacroFunc(c, node), - .@"while" => { - const payload = node.castTag(.@"while").?.data; - const while_tok = try c.addToken(.keyword_while, "while"); - _ = try c.addToken(.l_paren, "("); - const cond = try renderNode(c, payload.cond); - _ = try c.addToken(.r_paren, ")"); - - const cont_expr_opt = if (payload.cont_expr) |some| blk: { - _ = try c.addToken(.colon, ":"); - _ = try c.addToken(.l_paren, "("); - const res = try renderNode(c, some); - _ = try c.addToken(.r_paren, ")"); - break :blk res; - } else null; - const body = try renderNode(c, payload.body); - - if (cont_expr_opt) |cont_expr| { - return c.addNode(.{ - .tag = .while_cont, - .main_token = while_tok, - .data = .{ .node_and_extra = .{ - cond, - try c.addExtra(std.zig.Ast.Node.WhileCont{ - .cont_expr = cont_expr, - .then_expr = body, - }), - } }, - }); - } else { - return c.addNode(.{ - .tag = .while_simple, - .main_token = while_tok, - .data = .{ .node_and_node = .{ - cond, body, - } }, - }); - } - }, - .while_true => { - const payload = node.castTag(.while_true).?.data; - const while_tok = try c.addToken(.keyword_while, "while"); - _ = try c.addToken(.l_paren, "("); - const cond = try c.addNode(.{ - .tag = .identifier, - .main_token = try c.addToken(.identifier, "true"), - .data = undefined, - }); - _ = try c.addToken(.r_paren, ")"); - const body = try renderNode(c, payload); - - return c.addNode(.{ - .tag = .while_simple, - .main_token = while_tok, - .data = .{ .node_and_node = .{ - cond, body, - } }, - }); - }, - .@"if" => { - const payload = node.castTag(.@"if").?.data; - const if_tok = try c.addToken(.keyword_if, "if"); - _ = try c.addToken(.l_paren, "("); - const cond = try renderNode(c, payload.cond); - _ = try c.addToken(.r_paren, ")"); - - const then_expr = try renderNode(c, payload.then); - const else_node = payload.@"else" orelse return c.addNode(.{ - .tag = .if_simple, - .main_token = if_tok, - .data = .{ .node_and_node = .{ - cond, then_expr, - } }, - }); - _ = try c.addToken(.keyword_else, "else"); - const else_expr = try renderNode(c, else_node); - - return c.addNode(.{ - .tag = .@"if", - .main_token = if_tok, - .data = .{ .node_and_extra = .{ - cond, - try c.addExtra(std.zig.Ast.Node.If{ - .then_expr = then_expr, - .else_expr = else_expr, - }), - } }, - }); - }, - .if_not_break => { - const payload = node.castTag(.if_not_break).?.data; - const if_tok = try c.addToken(.keyword_if, "if"); - _ = try c.addToken(.l_paren, "("); - const cond = try c.addNode(.{ - .tag = .bool_not, - .main_token = try c.addToken(.bang, "!"), - .data = .{ - .node = try renderNodeGrouped(c, payload), - }, - }); - _ = try c.addToken(.r_paren, ")"); - const then_expr = try c.addNode(.{ - .tag = .@"break", - .main_token = try c.addToken(.keyword_break, "break"), - .data = .{ .opt_token_and_opt_node = .{ - .none, .none, - } }, - }); - - return c.addNode(.{ - .tag = .if_simple, - .main_token = if_tok, - .data = .{ .node_and_node = .{ - cond, then_expr, - } }, - }); - }, - .@"switch" => { - const payload = node.castTag(.@"switch").?.data; - const switch_tok = try c.addToken(.keyword_switch, "switch"); - _ = try c.addToken(.l_paren, "("); - const cond = try renderNode(c, payload.cond); - _ = try c.addToken(.r_paren, ")"); - - _ = try c.addToken(.l_brace, "{"); - var cases = try c.gpa.alloc(NodeIndex, payload.cases.len); - defer c.gpa.free(cases); - for (payload.cases, 0..) |case, i| { - cases[i] = try renderNode(c, case); - _ = try c.addToken(.comma, ","); - } - const span = try c.listToSpan(cases); - _ = try c.addToken(.r_brace, "}"); - return c.addNode(.{ - .tag = .switch_comma, - .main_token = switch_tok, - .data = .{ .node_and_extra = .{ - cond, - try c.addExtra(NodeSubRange{ - .start = span.start, - .end = span.end, - }), - } }, - }); - }, - .switch_else => { - const payload = node.castTag(.switch_else).?.data; - _ = try c.addToken(.keyword_else, "else"); - return c.addNode(.{ - .tag = .switch_case_one, - .main_token = try c.addToken(.equal_angle_bracket_right, "=>"), - .data = .{ .opt_node_and_node = .{ - .none, try renderNode(c, payload), - } }, - }); - }, - .switch_prong => { - const payload = node.castTag(.switch_prong).?.data; - var items = try c.gpa.alloc(NodeIndex, payload.cases.len); - defer c.gpa.free(items); - - for (payload.cases, 0..) |item, i| { - if (i != 0) _ = try c.addToken(.comma, ","); - items[i] = try renderNode(c, item); - } - _ = try c.addToken(.r_brace, "}"); - if (items.len < 2) { - return c.addNode(.{ - .tag = .switch_case_one, - .main_token = try c.addToken(.equal_angle_bracket_right, "=>"), - .data = .{ .opt_node_and_node = .{ - if (payload.cases.len == 1) items[0].toOptional() else .none, - try renderNode(c, payload.cond), - } }, - }); - } else { - return c.addNode(.{ - .tag = .switch_case, - .main_token = try c.addToken(.equal_angle_bracket_right, "=>"), - .data = .{ .extra_and_node = .{ - try c.addExtra(try c.listToSpan(items)), - try renderNode(c, payload.cond), - } }, - }); - } - }, - .opaque_literal => { - const opaque_tok = try c.addToken(.keyword_opaque, "opaque"); - _ = try c.addToken(.l_brace, "{"); - _ = try c.addToken(.r_brace, "}"); - - return c.addNode(.{ - .tag = .container_decl_two, - .main_token = opaque_tok, - .data = .{ .opt_node_and_opt_node = .{ - .none, .none, - } }, - }); - }, - .array_access => { - const payload = node.castTag(.array_access).?.data; - const lhs = try renderNodeGrouped(c, payload.lhs); - const l_bracket = try c.addToken(.l_bracket, "["); - const index_expr = try renderNode(c, payload.rhs); - _ = try c.addToken(.r_bracket, "]"); - return c.addNode(.{ - .tag = .array_access, - .main_token = l_bracket, - .data = .{ .node_and_node = .{ - lhs, index_expr, - } }, - }); - }, - .array_type => { - const payload = node.castTag(.array_type).?.data; - return renderArrayType(c, payload.len, payload.elem_type); - }, - .null_sentinel_array_type => { - const payload = node.castTag(.null_sentinel_array_type).?.data; - return renderNullSentinelArrayType(c, payload.len, payload.elem_type); - }, - .array_filler => { - const payload = node.castTag(.array_filler).?.data; - - const type_expr = try renderArrayType(c, 1, payload.type); - const l_brace = try c.addToken(.l_brace, "{"); - const val = try renderNode(c, payload.filler); - _ = try c.addToken(.r_brace, "}"); - - const init = try c.addNode(.{ - .tag = .array_init_one, - .main_token = l_brace, - .data = .{ .node_and_node = .{ - type_expr, val, - } }, - }); - return c.addNode(.{ - .tag = .array_cat, - .main_token = try c.addToken(.asterisk_asterisk, "**"), - .data = .{ .node_and_node = .{ - init, - try c.addNode(.{ - .tag = .number_literal, - .main_token = try c.addTokenFmt(.number_literal, "{d}", .{payload.count}), - .data = undefined, - }), - } }, - }); - }, - .empty_array => { - const payload = node.castTag(.empty_array).?.data; - - const type_expr = try renderNode(c, payload); - return renderArrayInit(c, type_expr, &.{}); - }, - .array_init => { - const payload = node.castTag(.array_init).?.data; - const type_expr = try renderNode(c, payload.cond); - return renderArrayInit(c, type_expr, payload.cases); - }, - .vector_zero_init => { - const payload = node.castTag(.vector_zero_init).?.data; - return renderBuiltinCall(c, "@splat", &.{payload}); - }, - .field_access => { - const payload = node.castTag(.field_access).?.data; - const lhs = try renderNodeGrouped(c, payload.lhs); - return renderFieldAccess(c, lhs, payload.field_name); - }, - .@"struct", .@"union", .@"opaque" => return renderContainer(c, node), - .enum_constant => { - const payload = node.castTag(.enum_constant).?.data; - - if (payload.is_public) _ = try c.addToken(.keyword_pub, "pub"); - const const_tok = try c.addToken(.keyword_const, "const"); - _ = try c.addIdentifier(payload.name); - - const type_node_opt = if (payload.type) |enum_const_type| blk: { - _ = try c.addToken(.colon, ":"); - break :blk try renderNode(c, enum_const_type); - } else null; - - _ = try c.addToken(.equal, "="); - - const init_node = try renderNode(c, payload.value); - _ = try c.addToken(.semicolon, ";"); - - return c.addNode(.{ - .tag = .simple_var_decl, - .main_token = const_tok, - .data = .{ .opt_node_and_opt_node = .{ - .fromOptional(type_node_opt), - init_node.toOptional(), - } }, - }); - }, - .tuple => { - const payload = node.castTag(.tuple).?.data; - _ = try c.addToken(.period, "."); - const l_brace = try c.addToken(.l_brace, "{"); - var inits = try c.gpa.alloc(NodeIndex, payload.len); - defer c.gpa.free(inits); - - for (payload, 0..) |init, i| { - if (i != 0) _ = try c.addToken(.comma, ","); - inits[i] = try renderNode(c, init); - } - _ = try c.addToken(.r_brace, "}"); - if (payload.len < 3) { - return c.addNode(.{ - .tag = .array_init_dot_two, - .main_token = l_brace, - .data = .{ .opt_node_and_opt_node = .{ - if (inits.len >= 1) inits[0].toOptional() else .none, - if (inits.len >= 2) inits[1].toOptional() else .none, - } }, - }); - } else { - return c.addNode(.{ - .tag = .array_init_dot, - .main_token = l_brace, - .data = .{ .extra_range = try c.listToSpan(inits) }, - }); - } - }, - .container_init_dot => { - const payload = node.castTag(.container_init_dot).?.data; - _ = try c.addToken(.period, "."); - const l_brace = try c.addToken(.l_brace, "{"); - var inits = try c.gpa.alloc(NodeIndex, payload.len); - defer c.gpa.free(inits); - - for (payload, 0..) |init, i| { - _ = try c.addToken(.period, "."); - _ = try c.addIdentifier(init.name); - _ = try c.addToken(.equal, "="); - inits[i] = try renderNode(c, init.value); - _ = try c.addToken(.comma, ","); - } - _ = try c.addToken(.r_brace, "}"); - - if (payload.len < 3) { - return c.addNode(.{ - .tag = .struct_init_dot_two_comma, - .main_token = l_brace, - .data = .{ .opt_node_and_opt_node = .{ - if (inits.len >= 1) inits[0].toOptional() else .none, - if (inits.len >= 2) inits[1].toOptional() else .none, - } }, - }); - } else { - return c.addNode(.{ - .tag = .struct_init_dot_comma, - .main_token = l_brace, - .data = .{ .extra_range = try c.listToSpan(inits) }, - }); - } - }, - .container_init => { - const payload = node.castTag(.container_init).?.data; - const lhs = try renderNode(c, payload.lhs); - - const l_brace = try c.addToken(.l_brace, "{"); - var inits = try c.gpa.alloc(NodeIndex, payload.inits.len); - defer c.gpa.free(inits); - - for (payload.inits, 0..) |init, i| { - _ = try c.addToken(.period, "."); - _ = try c.addIdentifier(init.name); - _ = try c.addToken(.equal, "="); - inits[i] = try renderNode(c, init.value); - _ = try c.addToken(.comma, ","); - } - _ = try c.addToken(.r_brace, "}"); - - switch (inits.len) { - 0 => return c.addNode(.{ - .tag = .struct_init_one, - .main_token = l_brace, - .data = .{ .node_and_opt_node = .{ - lhs, .none, - } }, - }), - 1 => return c.addNode(.{ - .tag = .struct_init_one_comma, - .main_token = l_brace, - .data = .{ .node_and_opt_node = .{ - lhs, inits[0].toOptional(), - } }, - }), - else => return c.addNode(.{ - .tag = .struct_init_comma, - .main_token = l_brace, - .data = .{ .node_and_extra = .{ - lhs, - try c.addExtra(try c.listToSpan(inits)), - } }, - }), - } - }, - .static_assert => { - const payload = node.castTag(.static_assert).?.data; - const comptime_tok = try c.addToken(.keyword_comptime, "comptime"); - const l_brace = try c.addToken(.l_brace, "{"); - - const if_tok = try c.addToken(.keyword_if, "if"); - _ = try c.addToken(.l_paren, "("); - const cond = try c.addNode(.{ - .tag = .bool_not, - .main_token = try c.addToken(.bang, "!"), - .data = .{ - .node = try renderNodeGrouped(c, payload.lhs), - }, - }); - _ = try c.addToken(.r_paren, ")"); - - const compile_error_tok = try c.addToken(.builtin, "@compileError"); - _ = try c.addToken(.l_paren, "("); - const err_msg = try renderNode(c, payload.rhs); - _ = try c.addToken(.r_paren, ")"); - const compile_error = try c.addNode(.{ - .tag = .builtin_call_two, - .main_token = compile_error_tok, - .data = .{ .opt_node_and_opt_node = .{ - err_msg.toOptional(), .none, - } }, - }); - - const if_node = try c.addNode(.{ - .tag = .if_simple, - .main_token = if_tok, - .data = .{ .node_and_node = .{ - cond, compile_error, - } }, - }); - _ = try c.addToken(.semicolon, ";"); - _ = try c.addToken(.r_brace, "}"); - const block_node = try c.addNode(.{ - .tag = .block_two_semicolon, - .main_token = l_brace, - .data = .{ .opt_node_and_opt_node = .{ - if_node.toOptional(), .none, - } }, - }); - - return c.addNode(.{ - .tag = .@"comptime", - .main_token = comptime_tok, - .data = .{ - .node = block_node, - }, - }); - }, - .@"anytype" => unreachable, // Handled in renderParams - } -} - -fn renderContainer(c: *Context, node: Node) !NodeIndex { - const payload = @as(*Payload.Container, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data; - if (payload.layout == .@"packed") - _ = try c.addToken(.keyword_packed, "packed") - else if (payload.layout == .@"extern") - _ = try c.addToken(.keyword_extern, "extern"); - const kind_tok = if (node.tag() == .@"struct") - try c.addToken(.keyword_struct, "struct") - else if (node.tag() == .@"union") - try c.addToken(.keyword_union, "union") - else if (node.tag() == .@"opaque") - try c.addToken(.keyword_opaque, "opaque") - else - unreachable; - - _ = try c.addToken(.l_brace, "{"); - - const num_decls = payload.decls.len; - const total_members = payload.fields.len + num_decls; - const members = try c.gpa.alloc(NodeIndex, total_members); - defer c.gpa.free(members); - - for (payload.fields, 0..) |field, i| { - const name_tok = try c.addTokenFmt(.identifier, "{f}", .{std.zig.fmtIdFlags(field.name, .{ .allow_primitive = true })}); - _ = try c.addToken(.colon, ":"); - const type_expr = try renderNode(c, field.type); - - const align_expr_opt = if (field.alignment) |alignment| blk: { - _ = try c.addToken(.keyword_align, "align"); - _ = try c.addToken(.l_paren, "("); - const align_expr = try c.addNode(.{ - .tag = .number_literal, - .main_token = try c.addTokenFmt(.number_literal, "{d}", .{alignment}), - .data = undefined, - }); - _ = try c.addToken(.r_paren, ")"); - break :blk align_expr; - } else null; - - const value_expr_opt = if (field.default_value) |value| blk: { - _ = try c.addToken(.equal, "="); - break :blk try renderNode(c, value); - } else null; - - if (align_expr_opt) |align_expr| { - if (value_expr_opt) |value_expr| { - members[i] = try c.addNode(.{ - .tag = .container_field, - .main_token = name_tok, - .data = .{ .node_and_extra = .{ - type_expr, - try c.addExtra(std.zig.Ast.Node.ContainerField{ - .align_expr = align_expr, - .value_expr = value_expr, - }), - } }, - }); - } else { - members[i] = try c.addNode(.{ - .tag = .container_field_align, - .main_token = name_tok, - .data = .{ .node_and_node = .{ - type_expr, - align_expr, - } }, - }); - } - } else { - members[i] = try c.addNode(.{ - .tag = .container_field_init, - .main_token = name_tok, - .data = .{ .node_and_opt_node = .{ - type_expr, - .fromOptional(value_expr_opt), - } }, - }); - } - _ = try c.addToken(.comma, ","); - } - for (members[payload.fields.len..], payload.decls) |*member, decl| { - member.* = try renderNode(c, decl); - } - const trailing = switch (c.tokens.items(.tag)[c.tokens.len - 1]) { - .comma, .semicolon => true, - else => false, - }; - _ = try c.addToken(.r_brace, "}"); - - if (total_members == 0) { - return c.addNode(.{ - .tag = .container_decl_two, - .main_token = kind_tok, - .data = .{ .opt_node_and_opt_node = .{ - .none, .none, - } }, - }); - } else if (total_members <= 2) { - return c.addNode(.{ - .tag = if (trailing) .container_decl_two_trailing else .container_decl_two, - .main_token = kind_tok, - .data = .{ .opt_node_and_opt_node = .{ - if (members.len >= 1) members[0].toOptional() else .none, - if (members.len >= 2) members[1].toOptional() else .none, - } }, - }); - } else { - const span = try c.listToSpan(members); - return c.addNode(.{ - .tag = if (trailing) .container_decl_trailing else .container_decl, - .main_token = kind_tok, - .data = .{ .extra_range = span }, - }); - } -} - -fn renderFieldAccess(c: *Context, lhs: NodeIndex, field_name: []const u8) !NodeIndex { - return c.addNode(.{ - .tag = .field_access, - .main_token = try c.addToken(.period, "."), - .data = .{ .node_and_token = .{ - lhs, try c.addTokenFmt(.identifier, "{f}", .{std.zig.fmtIdFlags(field_name, .{ .allow_primitive = true })}), - } }, - }); -} - -fn renderArrayInit(c: *Context, lhs: NodeIndex, inits: []const Node) !NodeIndex { - const l_brace = try c.addToken(.l_brace, "{"); - var rendered = try c.gpa.alloc(NodeIndex, inits.len); - defer c.gpa.free(rendered); - - for (inits, 0..) |init, i| { - rendered[i] = try renderNode(c, init); - _ = try c.addToken(.comma, ","); - } - _ = try c.addToken(.r_brace, "}"); - switch (inits.len) { - 0 => return c.addNode(.{ - .tag = .struct_init_one, - .main_token = l_brace, - .data = .{ .node_and_opt_node = .{ - lhs, .none, - } }, - }), - 1 => return c.addNode(.{ - .tag = .array_init_one_comma, - .main_token = l_brace, - .data = .{ .node_and_node = .{ - lhs, rendered[0], - } }, - }), - else => return c.addNode(.{ - .tag = .array_init_comma, - .main_token = l_brace, - .data = .{ .node_and_extra = .{ - lhs, - try c.addExtra(try c.listToSpan(rendered)), - } }, - }), - } -} - -fn renderArrayType(c: *Context, len: u64, elem_type: Node) !NodeIndex { - const l_bracket = try c.addToken(.l_bracket, "["); - const len_expr = try c.addNode(.{ - .tag = .number_literal, - .main_token = try c.addTokenFmt(.number_literal, "{d}", .{len}), - .data = undefined, - }); - _ = try c.addToken(.r_bracket, "]"); - const elem_type_expr = try renderNode(c, elem_type); - return c.addNode(.{ - .tag = .array_type, - .main_token = l_bracket, - .data = .{ .node_and_node = .{ - len_expr, elem_type_expr, - } }, - }); -} - -fn renderNullSentinelArrayType(c: *Context, len: u64, elem_type: Node) !NodeIndex { - const l_bracket = try c.addToken(.l_bracket, "["); - const len_expr = try c.addNode(.{ - .tag = .number_literal, - .main_token = try c.addTokenFmt(.number_literal, "{d}", .{len}), - .data = undefined, - }); - _ = try c.addToken(.colon, ":"); - - const sentinel_expr = try c.addNode(.{ - .tag = .number_literal, - .main_token = try c.addToken(.number_literal, "0"), - .data = undefined, - }); - - _ = try c.addToken(.r_bracket, "]"); - const elem_type_expr = try renderNode(c, elem_type); - return c.addNode(.{ - .tag = .array_type_sentinel, - .main_token = l_bracket, - .data = .{ .node_and_extra = .{ - len_expr, - try c.addExtra(std.zig.Ast.Node.ArrayTypeSentinel{ - .sentinel = sentinel_expr, - .elem_type = elem_type_expr, - }), - } }, - }); -} - -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", .wrapped_local, .mut_str => {}, - .while_true => { - const payload = node.castTag(.while_true).?.data; - return addSemicolonIfNotBlock(c, payload); - }, - .@"while" => { - const payload = node.castTag(.@"while").?.data; - return addSemicolonIfNotBlock(c, payload.body); - }, - .@"if" => { - const payload = node.castTag(.@"if").?.data; - if (payload.@"else") |some| - return addSemicolonIfNeeded(c, some); - return addSemicolonIfNotBlock(c, payload.then); - }, - else => _ = try c.addToken(.semicolon, ";"), - } -} - -fn addSemicolonIfNotBlock(c: *Context, node: Node) !void { - switch (node.tag()) { - .block, .empty_block, .block_single => {}, - else => _ = try c.addToken(.semicolon, ";"), - } -} - -fn renderNodeGrouped(c: *Context, node: Node) !NodeIndex { - switch (node.tag()) { - .declaration => unreachable, - .null_literal, - .undefined_literal, - .true_literal, - .false_literal, - .return_void, - .zero_literal, - .one_literal, - .void_type, - .noreturn_type, - .@"anytype", - .div_trunc, - .int_cast, - .const_cast, - .volatile_cast, - .as, - .truncate, - .bit_cast, - .float_cast, - .int_from_float, - .float_from_int, - .ptr_from_int, - .std_mem_zeroes, - .int_from_ptr, - .sizeof, - .alignof, - .typeof, - .typeinfo, - .vector, - .std_mem_zeroinit, - .integer_literal, - .float_literal, - .string_literal, - .string_slice, - .char_literal, - .enum_literal, - .identifier, - .field_access, - .ptr_cast, - .type, - .array_access, - .align_cast, - .optional_type, - .c_pointer, - .single_pointer, - .unwrap, - .deref, - .not, - .negate, - .negate_wrap, - .bit_not, - .func, - .call, - .array_type, - .null_sentinel_array_type, - .int_from_bool, - .div_exact, - .offset_of, - .shuffle, - .builtin_extern, - .wrapped_local, - .mut_str, - .helper_call, - .helper_ref, - .byte_swap, - .ceil, - .cos, - .sin, - .exp, - .exp2, - .exp10, - .abs, - .log, - .log2, - .log10, - .round, - .sqrt, - .trunc, - .floor, - => { - // no grouping needed - return renderNode(c, node); - }, - - .opaque_literal, - .@"opaque", - .empty_array, - .block_single, - .add, - .add_wrap, - .sub, - .sub_wrap, - .mul, - .mul_wrap, - .div, - .shl, - .shr, - .mod, - .@"and", - .@"or", - .less_than, - .less_than_equal, - .greater_than, - .greater_than_equal, - .equal, - .not_equal, - .bit_and, - .bit_or, - .bit_xor, - .empty_block, - .array_cat, - .array_filler, - .@"if", - .@"struct", - .@"union", - .array_init, - .vector_zero_init, - .tuple, - .container_init, - .container_init_dot, - .block, - .address_of, - => return c.addNode(.{ - .tag = .grouped_expression, - .main_token = try c.addToken(.l_paren, "("), - .data = .{ .node_and_token = .{ - try renderNode(c, node), - try c.addToken(.r_paren, ")"), - } }, - }), - .ellipsis3, - .switch_prong, - .warning, - .var_decl, - .fail_decl, - .arg_redecl, - .alias, - .var_simple, - .pub_var_simple, - .enum_constant, - .@"while", - .@"switch", - .@"break", - .break_val, - .pub_inline_fn, - .discard, - .@"continue", - .@"return", - .@"comptime", - .@"defer", - .asm_simple, - .while_true, - .if_not_break, - .switch_else, - .add_assign, - .add_wrap_assign, - .sub_assign, - .sub_wrap_assign, - .mul_assign, - .mul_wrap_assign, - .div_assign, - .shl_assign, - .shr_assign, - .mod_assign, - .bit_and_assign, - .bit_or_assign, - .bit_xor_assign, - .assign, - .static_assert, - .@"unreachable", - => { - // these should never appear in places where grouping might be needed. - unreachable; - }, - } -} - -fn renderPrefixOp(c: *Context, node: Node, tag: std.zig.Ast.Node.Tag, tok_tag: TokenTag, bytes: []const u8) !NodeIndex { - const payload = @as(*Payload.UnOp, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data; - return c.addNode(.{ - .tag = tag, - .main_token = try c.addToken(tok_tag, bytes), - .data = .{ - .node = try renderNodeGrouped(c, payload), - }, - }); -} - -fn renderBinOpGrouped(c: *Context, node: Node, tag: std.zig.Ast.Node.Tag, tok_tag: TokenTag, bytes: []const u8) !NodeIndex { - const payload = @as(*Payload.BinOp, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data; - const lhs = try renderNodeGrouped(c, payload.lhs); - return c.addNode(.{ - .tag = tag, - .main_token = try c.addToken(tok_tag, bytes), - .data = .{ .node_and_node = .{ - lhs, try renderNodeGrouped(c, payload.rhs), - } }, - }); -} - -fn renderBinOp(c: *Context, node: Node, tag: std.zig.Ast.Node.Tag, tok_tag: TokenTag, bytes: []const u8) !NodeIndex { - const payload = @as(*Payload.BinOp, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data; - const lhs = try renderNode(c, payload.lhs); - return c.addNode(.{ - .tag = tag, - .main_token = try c.addToken(tok_tag, bytes), - .data = .{ .node_and_node = .{ - lhs, try renderNode(c, payload.rhs), - } }, - }); -} - -fn renderStdImport(c: *Context, parts: []const []const u8) !NodeIndex { - const import_tok = try c.addToken(.builtin, "@import"); - _ = try c.addToken(.l_paren, "("); - const std_tok = try c.addToken(.string_literal, "\"std\""); - const std_node = try c.addNode(.{ - .tag = .string_literal, - .main_token = std_tok, - .data = undefined, - }); - _ = try c.addToken(.r_paren, ")"); - - const import_node = try c.addNode(.{ - .tag = .builtin_call_two, - .main_token = import_tok, - .data = .{ .opt_node_and_opt_node = .{ - std_node.toOptional(), .none, - } }, - }); - - var access_chain = import_node; - for (parts) |part| { - access_chain = try renderFieldAccess(c, access_chain, part); - } - return access_chain; -} - -fn renderCall(c: *Context, lhs: NodeIndex, args: []const Node) !NodeIndex { - const lparen = try c.addToken(.l_paren, "("); - const res = switch (args.len) { - 0 => try c.addNode(.{ - .tag = .call_one, - .main_token = lparen, - .data = .{ .node_and_opt_node = .{ - lhs, .none, - } }, - }), - 1 => try c.addNode(.{ - .tag = .call_one, - .main_token = lparen, - .data = .{ .node_and_opt_node = .{ - lhs, (try renderNode(c, args[0])).toOptional(), - } }, - }), - else => blk: { - var rendered = try c.gpa.alloc(NodeIndex, args.len); - defer c.gpa.free(rendered); - - for (args, 0..) |arg, i| { - if (i != 0) _ = try c.addToken(.comma, ","); - rendered[i] = try renderNode(c, arg); - } - const span = try c.listToSpan(rendered); - break :blk try c.addNode(.{ - .tag = .call, - .main_token = lparen, - .data = .{ .node_and_extra = .{ - lhs, try c.addExtra(NodeSubRange{ - .start = span.start, - .end = span.end, - }), - } }, - }); - }, - }; - _ = try c.addToken(.r_paren, ")"); - return res; -} - -fn renderBuiltinCall(c: *Context, builtin: []const u8, args: []const Node) !NodeIndex { - const builtin_tok = try c.addToken(.builtin, builtin); - _ = try c.addToken(.l_paren, "("); - var arg_1: ?NodeIndex = null; - var arg_2: ?NodeIndex = null; - var arg_3: ?NodeIndex = null; - var arg_4: ?NodeIndex = null; - switch (args.len) { - 0 => {}, - 1 => { - arg_1 = try renderNode(c, args[0]); - }, - 2 => { - arg_1 = try renderNode(c, args[0]); - _ = try c.addToken(.comma, ","); - arg_2 = try renderNode(c, args[1]); - }, - 4 => { - arg_1 = try renderNode(c, args[0]); - _ = try c.addToken(.comma, ","); - arg_2 = try renderNode(c, args[1]); - _ = try c.addToken(.comma, ","); - arg_3 = try renderNode(c, args[2]); - _ = try c.addToken(.comma, ","); - arg_4 = try renderNode(c, args[3]); - }, - else => unreachable, // expand this function as needed. - } - - _ = try c.addToken(.r_paren, ")"); - if (args.len <= 2) { - return c.addNode(.{ - .tag = .builtin_call_two, - .main_token = builtin_tok, - .data = .{ .opt_node_and_opt_node = .{ - .fromOptional(arg_1), .fromOptional(arg_2), - } }, - }); - } else { - std.debug.assert(args.len == 4); - - const params = try c.listToSpan(&.{ arg_1.?, arg_2.?, arg_3.?, arg_4.? }); - return c.addNode(.{ - .tag = .builtin_call, - .main_token = builtin_tok, - .data = .{ .extra_range = .{ - .start = params.start, - .end = params.end, - } }, - }); - } -} - -fn renderVar(c: *Context, node: Node) !NodeIndex { - const payload = node.castTag(.var_decl).?.data; - if (payload.is_pub) _ = try c.addToken(.keyword_pub, "pub"); - if (payload.is_extern) _ = try c.addToken(.keyword_extern, "extern"); - if (payload.is_export) _ = try c.addToken(.keyword_export, "export"); - if (payload.is_threadlocal) _ = try c.addToken(.keyword_threadlocal, "threadlocal"); - const mut_tok = if (payload.is_const) - try c.addToken(.keyword_const, "const") - else - try c.addToken(.keyword_var, "var"); - _ = try c.addIdentifier(payload.name); - _ = try c.addToken(.colon, ":"); - const type_node = try renderNode(c, payload.type); - - const align_node_opt = if (payload.alignment) |some| blk: { - _ = try c.addToken(.keyword_align, "align"); - _ = try c.addToken(.l_paren, "("); - const res = try c.addNode(.{ - .tag = .number_literal, - .main_token = try c.addTokenFmt(.number_literal, "{d}", .{some}), - .data = undefined, - }); - _ = try c.addToken(.r_paren, ")"); - break :blk res; - } else null; - - const section_node_opt = if (payload.linksection_string) |some| blk: { - _ = try c.addToken(.keyword_linksection, "linksection"); - _ = try c.addToken(.l_paren, "("); - const res = try c.addNode(.{ - .tag = .string_literal, - .main_token = try c.addTokenFmt(.string_literal, "\"{f}\"", .{std.zig.fmtString(some)}), - .data = undefined, - }); - _ = try c.addToken(.r_paren, ")"); - break :blk res; - } else null; - - const init_node_opt = if (payload.init) |some| blk: { - _ = try c.addToken(.equal, "="); - break :blk try renderNode(c, some); - } else null; - _ = try c.addToken(.semicolon, ";"); - - if (section_node_opt) |section_node| { - return c.addNode(.{ - .tag = .global_var_decl, - .main_token = mut_tok, - .data = .{ .extra_and_opt_node = .{ - try c.addExtra(std.zig.Ast.Node.GlobalVarDecl{ - .type_node = type_node.toOptional(), - .align_node = .fromOptional(align_node_opt), - .section_node = section_node.toOptional(), - .addrspace_node = .none, - }), - .fromOptional(init_node_opt), - } }, - }); - } else { - if (align_node_opt) |align_node| { - return c.addNode(.{ - .tag = .local_var_decl, - .main_token = mut_tok, - .data = .{ .extra_and_opt_node = .{ - try c.addExtra(std.zig.Ast.Node.LocalVarDecl{ - .type_node = type_node, - .align_node = align_node, - }), - .fromOptional(init_node_opt), - } }, - }); - } else { - return c.addNode(.{ - .tag = .simple_var_decl, - .main_token = mut_tok, - .data = .{ - .opt_node_and_opt_node = .{ - type_node.toOptional(), // Type expression - .fromOptional(init_node_opt), // Init expression - }, - }, - }); - } - } -} - -fn renderFunc(c: *Context, node: Node) !NodeIndex { - const payload = node.castTag(.func).?.data; - if (payload.is_pub) _ = try c.addToken(.keyword_pub, "pub"); - if (payload.is_extern) _ = try c.addToken(.keyword_extern, "extern"); - if (payload.is_export) _ = try c.addToken(.keyword_export, "export"); - if (payload.is_inline) _ = try c.addToken(.keyword_inline, "inline"); - const fn_token = try c.addToken(.keyword_fn, "fn"); - if (payload.name) |some| _ = try c.addIdentifier(some); - - const params = try renderParams(c, payload.params, payload.is_var_args); - defer params.deinit(); - var span: NodeSubRange = undefined; - if (params.items.len > 1) span = try c.listToSpan(params.items); - - const align_expr_opt = if (payload.alignment) |some| blk: { - _ = try c.addToken(.keyword_align, "align"); - _ = try c.addToken(.l_paren, "("); - const res = try c.addNode(.{ - .tag = .number_literal, - .main_token = try c.addTokenFmt(.number_literal, "{d}", .{some}), - .data = undefined, - }); - _ = try c.addToken(.r_paren, ")"); - break :blk res; - } else null; - - const section_expr_opt = if (payload.linksection_string) |some| blk: { - _ = try c.addToken(.keyword_linksection, "linksection"); - _ = try c.addToken(.l_paren, "("); - const res = try c.addNode(.{ - .tag = .string_literal, - .main_token = try c.addTokenFmt(.string_literal, "\"{f}\"", .{std.zig.fmtString(some)}), - .data = undefined, - }); - _ = try c.addToken(.r_paren, ")"); - break :blk res; - } else null; - - const callconv_expr_opt = if (payload.explicit_callconv) |some| blk: { - _ = try c.addToken(.keyword_callconv, "callconv"); - _ = try c.addToken(.l_paren, "("); - const cc_node = switch (some) { - .c => cc_node: { - _ = try c.addToken(.period, "."); - break :cc_node try c.addNode(.{ - .tag = .enum_literal, - .main_token = try c.addToken(.identifier, "c"), - .data = undefined, - }); - }, - .x86_64_sysv, - .x86_64_win, - .x86_stdcall, - .x86_fastcall, - .x86_thiscall, - .x86_vectorcall, - .x86_regcall, - .aarch64_vfabi, - .aarch64_sve_pcs, - .arm_aapcs, - .arm_aapcs_vfp, - .m68k_rtd, - .riscv_vector, - => cc_node: { - // .{ .foo = .{} } - _ = try c.addToken(.period, "."); - const outer_lbrace = try c.addToken(.l_brace, "{"); - _ = try c.addToken(.period, "."); - _ = try c.addToken(.identifier, @tagName(some)); - _ = try c.addToken(.equal, "="); - _ = try c.addToken(.period, "."); - const inner_lbrace = try c.addToken(.l_brace, "{"); - _ = try c.addToken(.r_brace, "}"); - _ = try c.addToken(.r_brace, "}"); - break :cc_node try c.addNode(.{ - .tag = .struct_init_dot_two, - .main_token = outer_lbrace, - .data = .{ .opt_node_and_opt_node = .{ - (try c.addNode(.{ - .tag = .struct_init_dot_two, - .main_token = inner_lbrace, - .data = .{ .opt_node_and_opt_node = .{ - .none, .none, - } }, - })).toOptional(), - .none, - } }, - }); - }, - }; - _ = try c.addToken(.r_paren, ")"); - break :blk cc_node; - } else null; - - const return_type_expr = try renderNode(c, payload.return_type); - - const fn_proto = try blk: { - if (align_expr_opt == null and section_expr_opt == null and callconv_expr_opt == null) { - if (params.items.len < 2) - break :blk c.addNode(.{ - .tag = .fn_proto_simple, - .main_token = fn_token, - .data = .{ .opt_node_and_opt_node = .{ - if (params.items.len == 1) params.items[0].toOptional() else .none, - return_type_expr.toOptional(), - } }, - }) - else - break :blk c.addNode(.{ - .tag = .fn_proto_multi, - .main_token = fn_token, - .data = .{ .extra_and_opt_node = .{ - try c.addExtra(span), - return_type_expr.toOptional(), - } }, - }); - } - if (params.items.len < 2) - break :blk c.addNode(.{ - .tag = .fn_proto_one, - .main_token = fn_token, - .data = .{ - .extra_and_opt_node = .{ - try c.addExtra(std.zig.Ast.Node.FnProtoOne{ - .param = if (params.items.len == 1) params.items[0].toOptional() else .none, - .align_expr = .fromOptional(align_expr_opt), - .addrspace_expr = .none, // TODO - .section_expr = .fromOptional(section_expr_opt), - .callconv_expr = .fromOptional(callconv_expr_opt), - }), - return_type_expr.toOptional(), - }, - }, - }) - else - break :blk c.addNode(.{ - .tag = .fn_proto, - .main_token = fn_token, - .data = .{ - .extra_and_opt_node = .{ - try c.addExtra(std.zig.Ast.Node.FnProto{ - .params_start = span.start, - .params_end = span.end, - .align_expr = .fromOptional(align_expr_opt), - .addrspace_expr = .none, // TODO - .section_expr = .fromOptional(section_expr_opt), - .callconv_expr = .fromOptional(callconv_expr_opt), - }), - return_type_expr.toOptional(), - }, - }, - }); - }; - - const payload_body = payload.body orelse { - if (payload.is_extern) { - _ = try c.addToken(.semicolon, ";"); - } - return fn_proto; - }; - const body = try renderNode(c, payload_body); - return c.addNode(.{ - .tag = .fn_decl, - .main_token = fn_token, - .data = .{ .node_and_node = .{ - fn_proto, body, - } }, - }); -} - -fn renderMacroFunc(c: *Context, node: Node) !NodeIndex { - const payload = node.castTag(.pub_inline_fn).?.data; - _ = try c.addToken(.keyword_pub, "pub"); - _ = try c.addToken(.keyword_inline, "inline"); - const fn_token = try c.addToken(.keyword_fn, "fn"); - _ = try c.addIdentifier(payload.name); - - const params = try renderParams(c, payload.params, false); - defer params.deinit(); - var span: NodeSubRange = undefined; - if (params.items.len > 1) span = try c.listToSpan(params.items); - - const return_type_expr = try renderNodeGrouped(c, payload.return_type); - - const fn_proto = blk: { - if (params.items.len < 2) { - break :blk try c.addNode(.{ - .tag = .fn_proto_simple, - .main_token = fn_token, - .data = .{ .opt_node_and_opt_node = .{ - if (params.items.len == 1) params.items[0].toOptional() else .none, - return_type_expr.toOptional(), - } }, - }); - } else { - break :blk try c.addNode(.{ - .tag = .fn_proto_multi, - .main_token = fn_token, - .data = .{ .extra_and_opt_node = .{ - try c.addExtra(span), - return_type_expr.toOptional(), - } }, - }); - } - }; - return c.addNode(.{ - .tag = .fn_decl, - .main_token = fn_token, - .data = .{ .node_and_node = .{ - fn_proto, try renderNode(c, payload.body), - } }, - }); -} - -fn renderParams(c: *Context, params: []Payload.Param, is_var_args: bool) !std.array_list.Managed(NodeIndex) { - _ = try c.addToken(.l_paren, "("); - var rendered = try std.array_list.Managed(NodeIndex).initCapacity(c.gpa, @max(params.len, 1)); - errdefer rendered.deinit(); - - for (params, 0..) |param, i| { - if (i != 0) _ = try c.addToken(.comma, ","); - if (param.is_noalias) _ = try c.addToken(.keyword_noalias, "noalias"); - if (param.name) |some| { - _ = try c.addIdentifier(some); - _ = try c.addToken(.colon, ":"); - } - if (param.type.tag() == .@"anytype") { - _ = try c.addToken(.keyword_anytype, "anytype"); - continue; - } - rendered.appendAssumeCapacity(try renderNode(c, param.type)); - } - if (is_var_args) { - if (params.len != 0) _ = try c.addToken(.comma, ","); - _ = try c.addToken(.ellipsis3, "..."); - } - _ = try c.addToken(.r_paren, ")"); - - return rendered; -} diff --git a/lib/compiler/translate-c/src/builtins.zig b/lib/compiler/translate-c/src/builtins.zig deleted file mode 100644 index cc385f8007a3b75610d3819b0c9f2cd130b985d4..0000000000000000000000000000000000000000 --- a/lib/compiler/translate-c/src/builtins.zig +++ /dev/null @@ -1,76 +0,0 @@ -const std = @import("std"); - -const ast = @import("ast.zig"); - -/// All builtins need to have a source so that macros can reference them -/// but for some it is possible to directly call an equivalent Zig builtin -/// which is preferrable. -pub const Builtin = struct { - /// The name of the builtin in `c_builtins.zig`. - name: []const u8, - tag: ?ast.Node.Tag = null, -}; - -pub const map = std.StaticStringMap(Builtin).initComptime([_]struct { []const u8, Builtin }{ - .{ "__builtin_abs", .{ .name = "abs" } }, - .{ "__builtin_assume", .{ .name = "assume" } }, - .{ "__builtin_bswap16", .{ .name = "bswap16", .tag = .byte_swap } }, - .{ "__builtin_bswap32", .{ .name = "bswap32", .tag = .byte_swap } }, - .{ "__builtin_bswap64", .{ .name = "bswap64", .tag = .byte_swap } }, - .{ "__builtin_ceilf", .{ .name = "ceilf", .tag = .ceil } }, - .{ "__builtin_ceil", .{ .name = "ceil", .tag = .ceil } }, - .{ "__builtin_clz", .{ .name = "clz" } }, - .{ "__builtin_constant_p", .{ .name = "constant_p" } }, - .{ "__builtin_cosf", .{ .name = "cosf", .tag = .cos } }, - .{ "__builtin_cos", .{ .name = "cos", .tag = .cos } }, - .{ "__builtin_ctz", .{ .name = "ctz" } }, - .{ "__builtin_exp2f", .{ .name = "exp2f", .tag = .exp2 } }, - .{ "__builtin_exp2", .{ .name = "exp2", .tag = .exp2 } }, - .{ "__builtin_expf", .{ .name = "expf", .tag = .exp } }, - .{ "__builtin_exp", .{ .name = "exp", .tag = .exp } }, - .{ "__builtin_expect", .{ .name = "expect" } }, - .{ "__builtin_fabsf", .{ .name = "fabsf", .tag = .abs } }, - .{ "__builtin_fabs", .{ .name = "fabs", .tag = .abs } }, - .{ "__builtin_floorf", .{ .name = "floorf", .tag = .floor } }, - .{ "__builtin_floor", .{ .name = "floor", .tag = .floor } }, - .{ "__builtin_huge_valf", .{ .name = "huge_valf" } }, - .{ "__builtin_inff", .{ .name = "inff" } }, - .{ "__builtin_isinf_sign", .{ .name = "isinf_sign" } }, - .{ "__builtin_isinf", .{ .name = "isinf" } }, - .{ "__builtin_isnan", .{ .name = "isnan" } }, - .{ "__builtin_labs", .{ .name = "labs" } }, - .{ "__builtin_llabs", .{ .name = "llabs" } }, - .{ "__builtin_log10f", .{ .name = "log10f", .tag = .log10 } }, - .{ "__builtin_log10", .{ .name = "log10", .tag = .log10 } }, - .{ "__builtin_log2f", .{ .name = "log2f", .tag = .log2 } }, - .{ "__builtin_log2", .{ .name = "log2", .tag = .log2 } }, - .{ "__builtin_logf", .{ .name = "logf", .tag = .log } }, - .{ "__builtin_log", .{ .name = "log", .tag = .log } }, - .{ "__builtin___memcpy_chk", .{ .name = "memcpy_chk" } }, - .{ "__builtin_memcpy", .{ .name = "memcpy" } }, - .{ "__builtin___memset_chk", .{ .name = "memset_chk" } }, - .{ "__builtin_memset", .{ .name = "memset" } }, - .{ "__builtin_mul_overflow", .{ .name = "mul_overflow" } }, - .{ "__builtin_nanf", .{ .name = "nanf" } }, - .{ "__builtin_object_size", .{ .name = "object_size" } }, - .{ "__builtin_popcount", .{ .name = "popcount" } }, - .{ "__builtin_roundf", .{ .name = "roundf", .tag = .round } }, - .{ "__builtin_round", .{ .name = "round", .tag = .round } }, - .{ "__builtin_signbitf", .{ .name = "signbitf" } }, - .{ "__builtin_signbit", .{ .name = "signbit" } }, - .{ "__builtin_sinf", .{ .name = "sinf", .tag = .sin } }, - .{ "__builtin_sin", .{ .name = "sin", .tag = .sin } }, - .{ "__builtin_sqrtf", .{ .name = "sqrtf", .tag = .sqrt } }, - .{ "__builtin_sqrt", .{ .name = "sqrt", .tag = .sqrt } }, - .{ "__builtin_strcmp", .{ .name = "strcmp" } }, - .{ "__builtin_strlen", .{ .name = "strlen" } }, - .{ "__builtin_truncf", .{ .name = "truncf", .tag = .trunc } }, - .{ "__builtin_trunc", .{ .name = "trunc", .tag = .trunc } }, - .{ "__builtin_unreachable", .{ .name = "unreachable", .tag = .@"unreachable" } }, - .{ "__has_builtin", .{ .name = "has_builtin" } }, - - // __builtin_alloca_with_align is not currently implemented. - // It is used in a run and a translate test to ensure that non-implemented - // builtins are correctly demoted. If you implement __builtin_alloca_with_align, - // please update the tests to use a different non-implemented builtin. -}); diff --git a/lib/compiler/translate-c/src/helpers.zig b/lib/compiler/translate-c/src/helpers.zig deleted file mode 100644 index db19cf75b8dedbc4f9643a01d282ffaee8e87759..0000000000000000000000000000000000000000 --- a/lib/compiler/translate-c/src/helpers.zig +++ /dev/null @@ -1,327 +0,0 @@ -const std = @import("std"); -const builtin = @import("builtin"); -const testing = std.testing; -const math = std.math; - -const helpers = @import("helpers"); - -const cast = helpers.cast; - -test cast { - var i = @as(i64, 10); - - try testing.expect(cast(*u8, 16) == @as(*u8, @ptrFromInt(16))); - try testing.expect(cast(*u64, &i).* == @as(u64, 10)); - try testing.expect(cast(*i64, @as(?*align(1) i64, &i)) == &i); - - try testing.expect(cast(?*u8, 2) == @as(*u8, @ptrFromInt(2))); - try testing.expect(cast(?*i64, @as(*align(1) i64, &i)) == &i); - try testing.expect(cast(?*i64, @as(?*align(1) i64, &i)) == &i); - - try testing.expectEqual(@as(u32, 4), cast(u32, @as(*u32, @ptrFromInt(4)))); - try testing.expectEqual(@as(u32, 4), cast(u32, @as(?*u32, @ptrFromInt(4)))); - try testing.expectEqual(@as(u32, 10), cast(u32, @as(u64, 10))); - - try testing.expectEqual(@as(i32, @bitCast(@as(u32, 0x8000_0000))), cast(i32, @as(u32, 0x8000_0000))); - - try testing.expectEqual(@as(*u8, @ptrFromInt(2)), cast(*u8, @as(*const u8, @ptrFromInt(2)))); - try testing.expectEqual(@as(*u8, @ptrFromInt(2)), cast(*u8, @as(*volatile u8, @ptrFromInt(2)))); - - try testing.expectEqual(@as(?*anyopaque, @ptrFromInt(2)), cast(?*anyopaque, @as(*u8, @ptrFromInt(2)))); - - var foo: c_int = -1; - _ = &foo; - try testing.expect(cast(*anyopaque, -1) == @as(*anyopaque, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1)))))); - try testing.expect(cast(*anyopaque, foo) == @as(*anyopaque, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1)))))); - try testing.expect(cast(?*anyopaque, -1) == @as(?*anyopaque, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1)))))); - try testing.expect(cast(?*anyopaque, foo) == @as(?*anyopaque, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1)))))); - - const FnPtr = ?*align(1) const fn (*anyopaque) void; - try testing.expect(cast(FnPtr, 0) == @as(FnPtr, @ptrFromInt(@as(usize, 0)))); - try testing.expect(cast(FnPtr, foo) == @as(FnPtr, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1)))))); - - const complexFunction = struct { - fn f(_: ?*anyopaque, _: c_uint, _: ?*const fn (?*anyopaque) callconv(.c) c_uint, _: ?*anyopaque, _: c_uint, _: [*c]c_uint) callconv(.c) usize { - return 0; - } - }.f; - - const SDL_FunctionPointer = ?*const fn () callconv(.c) void; - const fn_ptr = cast(SDL_FunctionPointer, complexFunction); - try testing.expect(fn_ptr != null); -} - -const sizeof = helpers.sizeof; - -test sizeof { - const S = extern struct { a: u32 }; - - const ptr_size = @sizeOf(*anyopaque); - - try testing.expect(sizeof(u32) == 4); - try testing.expect(sizeof(@as(u32, 2)) == 4); - try testing.expect(sizeof(2) == @sizeOf(c_int)); - - try testing.expect(sizeof(2.0) == @sizeOf(f64)); - - try testing.expect(sizeof(S) == 4); - - try testing.expect(sizeof([_]u32{ 4, 5, 6 }) == 12); - try testing.expect(sizeof([3]u32) == 12); - try testing.expect(sizeof([3:0]u32) == 16); - try testing.expect(sizeof(&[_]u32{ 4, 5, 6 }) == ptr_size); - - try testing.expect(sizeof(*u32) == ptr_size); - try testing.expect(sizeof([*]u32) == ptr_size); - try testing.expect(sizeof([*c]u32) == ptr_size); - try testing.expect(sizeof(?*u32) == ptr_size); - try testing.expect(sizeof(?[*]u32) == ptr_size); - try testing.expect(sizeof(*anyopaque) == ptr_size); - try testing.expect(sizeof(*void) == ptr_size); - try testing.expect(sizeof(null) == ptr_size); - - try testing.expect(sizeof("foobar") == 7); - try testing.expect(sizeof(&[_:0]u16{ 'f', 'o', 'o', 'b', 'a', 'r' }) == 14); - try testing.expect(sizeof(*const [4:0]u8) == 5); - try testing.expect(sizeof(*[4:0]u8) == ptr_size); - try testing.expect(sizeof([*]const [4:0]u8) == ptr_size); - try testing.expect(sizeof(*const *const [4:0]u8) == ptr_size); - try testing.expect(sizeof(*const [4]u8) == ptr_size); - - if (false) { // TODO - try testing.expect(sizeof(&sizeof) == @sizeOf(@TypeOf(&sizeof))); - try testing.expect(sizeof(sizeof) == 1); - } - - try testing.expect(sizeof(void) == 1); - try testing.expect(sizeof(anyopaque) == 1); -} - -const promoteIntLiteral = helpers.promoteIntLiteral; - -test promoteIntLiteral { - const signed_hex = promoteIntLiteral(c_int, math.maxInt(c_int) + 1, .hex); - try testing.expectEqual(c_uint, @TypeOf(signed_hex)); - - if (math.maxInt(c_longlong) == math.maxInt(c_int)) return; - - const signed_decimal = promoteIntLiteral(c_int, math.maxInt(c_int) + 1, .decimal); - const unsigned = promoteIntLiteral(c_uint, math.maxInt(c_uint) + 1, .hex); - - if (math.maxInt(c_long) > math.maxInt(c_int)) { - try testing.expectEqual(c_long, @TypeOf(signed_decimal)); - try testing.expectEqual(c_ulong, @TypeOf(unsigned)); - } else { - try testing.expectEqual(c_longlong, @TypeOf(signed_decimal)); - try testing.expectEqual(c_ulonglong, @TypeOf(unsigned)); - } -} - -const shuffleVectorIndex = helpers.shuffleVectorIndex; - -test shuffleVectorIndex { - const vector_len: usize = 4; - - _ = shuffleVectorIndex(-1, vector_len); - - try testing.expect(shuffleVectorIndex(0, vector_len) == 0); - try testing.expect(shuffleVectorIndex(1, vector_len) == 1); - try testing.expect(shuffleVectorIndex(2, vector_len) == 2); - try testing.expect(shuffleVectorIndex(3, vector_len) == 3); - - try testing.expect(shuffleVectorIndex(4, vector_len) == -1); - try testing.expect(shuffleVectorIndex(5, vector_len) == -2); - try testing.expect(shuffleVectorIndex(6, vector_len) == -3); - try testing.expect(shuffleVectorIndex(7, vector_len) == -4); -} - -const FlexibleArrayType = helpers.FlexibleArrayType; - -test FlexibleArrayType { - const Container = extern struct { - size: usize, - }; - - try testing.expectEqual(FlexibleArrayType(*Container, c_int), [*c]c_int); - try testing.expectEqual(FlexibleArrayType(*const Container, c_int), [*c]const c_int); - try testing.expectEqual(FlexibleArrayType(*volatile Container, c_int), [*c]volatile c_int); - try testing.expectEqual(FlexibleArrayType(*const volatile Container, c_int), [*c]const volatile c_int); -} - -const signedRemainder = helpers.signedRemainder; - -test signedRemainder { - // TODO add test - return error.SkipZigTest; -} - -const ArithmeticConversion = helpers.ArithmeticConversion; - -test ArithmeticConversion { - // Promotions not necessarily the same for other platforms - if (builtin.target.cpu.arch != .x86_64 or builtin.target.os.tag != .linux) return error.SkipZigTest; - - const Test = struct { - /// Order of operands should not matter for arithmetic conversions - fn checkPromotion(comptime A: type, comptime B: type, comptime Expected: type) !void { - try std.testing.expect(ArithmeticConversion(A, B) == Expected); - try std.testing.expect(ArithmeticConversion(B, A) == Expected); - } - }; - - try Test.checkPromotion(c_longdouble, c_int, c_longdouble); - try Test.checkPromotion(c_int, f64, f64); - try Test.checkPromotion(f32, bool, f32); - - try Test.checkPromotion(bool, c_short, c_int); - try Test.checkPromotion(c_int, c_int, c_int); - try Test.checkPromotion(c_short, c_int, c_int); - - try Test.checkPromotion(c_int, c_long, c_long); - - try Test.checkPromotion(c_ulonglong, c_uint, c_ulonglong); - - try Test.checkPromotion(c_uint, c_int, c_uint); - - try Test.checkPromotion(c_uint, c_long, c_long); - - try Test.checkPromotion(c_ulong, c_longlong, c_ulonglong); - - // stdint.h - try Test.checkPromotion(u8, i8, c_int); - try Test.checkPromotion(u16, i16, c_int); - try Test.checkPromotion(i32, c_int, c_int); - try Test.checkPromotion(u32, c_int, c_uint); - try Test.checkPromotion(i64, c_int, c_long); - try Test.checkPromotion(u64, c_int, c_ulong); - try Test.checkPromotion(isize, c_int, c_long); - try Test.checkPromotion(usize, c_int, c_ulong); -} - -const F_SUFFIX = helpers.F_SUFFIX; - -test F_SUFFIX { - try testing.expect(@TypeOf(F_SUFFIX(1)) == f32); -} - -const U_SUFFIX = helpers.U_SUFFIX; - -test U_SUFFIX { - try testing.expect(@TypeOf(U_SUFFIX(1)) == c_uint); - if (math.maxInt(c_ulong) > math.maxInt(c_uint)) { - try testing.expect(@TypeOf(U_SUFFIX(math.maxInt(c_uint) + 1)) == c_ulong); - } - if (math.maxInt(c_ulonglong) > math.maxInt(c_ulong)) { - try testing.expect(@TypeOf(U_SUFFIX(math.maxInt(c_ulong) + 1)) == c_ulonglong); - } -} - -const L_SUFFIX = helpers.L_SUFFIX; - -test L_SUFFIX { - try testing.expect(@TypeOf(L_SUFFIX(1)) == c_long); - if (math.maxInt(c_long) > math.maxInt(c_int)) { - try testing.expect(@TypeOf(L_SUFFIX(math.maxInt(c_int) + 1)) == c_long); - } - if (math.maxInt(c_longlong) > math.maxInt(c_long)) { - try testing.expect(@TypeOf(L_SUFFIX(math.maxInt(c_long) + 1)) == c_longlong); - } -} -const UL_SUFFIX = helpers.UL_SUFFIX; - -test UL_SUFFIX { - try testing.expect(@TypeOf(UL_SUFFIX(1)) == c_ulong); - if (math.maxInt(c_ulonglong) > math.maxInt(c_ulong)) { - try testing.expect(@TypeOf(UL_SUFFIX(math.maxInt(c_ulong) + 1)) == c_ulonglong); - } -} -const LL_SUFFIX = helpers.LL_SUFFIX; - -test LL_SUFFIX { - try testing.expect(@TypeOf(LL_SUFFIX(1)) == c_longlong); -} -const ULL_SUFFIX = helpers.ULL_SUFFIX; - -test ULL_SUFFIX { - try testing.expect(@TypeOf(ULL_SUFFIX(1)) == c_ulonglong); -} - -test "Extended C ABI casting" { - if (math.maxInt(c_long) > math.maxInt(c_char)) { - try testing.expect(@TypeOf(L_SUFFIX(@as(c_char, math.maxInt(c_char) - 1))) == c_long); // c_char - } - if (math.maxInt(c_long) > math.maxInt(c_short)) { - try testing.expect(@TypeOf(L_SUFFIX(@as(c_short, math.maxInt(c_short) - 1))) == c_long); // c_short - } - - if (math.maxInt(c_long) > math.maxInt(c_ushort)) { - try testing.expect(@TypeOf(L_SUFFIX(@as(c_ushort, math.maxInt(c_ushort) - 1))) == c_long); //c_ushort - } - - if (math.maxInt(c_long) > math.maxInt(c_int)) { - try testing.expect(@TypeOf(L_SUFFIX(@as(c_int, math.maxInt(c_int) - 1))) == c_long); // c_int - } - - if (math.maxInt(c_long) > math.maxInt(c_uint)) { - try testing.expect(@TypeOf(L_SUFFIX(@as(c_uint, math.maxInt(c_uint) - 1))) == c_long); // c_uint - try testing.expect(@TypeOf(L_SUFFIX(math.maxInt(c_uint) + 1)) == c_long); // comptime_int -> c_long - } - - if (math.maxInt(c_longlong) > math.maxInt(c_long)) { - try testing.expect(@TypeOf(L_SUFFIX(@as(c_long, math.maxInt(c_long) - 1))) == c_long); // c_long - try testing.expect(@TypeOf(L_SUFFIX(math.maxInt(c_long) + 1)) == c_longlong); // comptime_int -> c_longlong - } -} - -const WL_CONTAINER_OF = helpers.WL_CONTAINER_OF; - -test WL_CONTAINER_OF { - const S = struct { - a: u32 = 0, - b: u32 = 0, - }; - const x = S{}; - const y = S{}; - const ptr = WL_CONTAINER_OF(&x.b, &y, "b"); - try testing.expectEqual(&x, ptr); -} - -const CAST_OR_CALL = helpers.CAST_OR_CALL; - -test "CAST_OR_CALL casting" { - const arg: c_int = 1000; - const casted = CAST_OR_CALL(u8, arg); - try testing.expectEqual(cast(u8, arg), casted); - - const S = struct { - x: u32 = 0, - }; - var s: S = .{}; - const casted_ptr = CAST_OR_CALL(*u8, &s); - try testing.expectEqual(cast(*u8, &s), casted_ptr); -} - -test "CAST_OR_CALL calling" { - const Helper = struct { - var last_val: bool = false; - fn returnsVoid(val: bool) void { - last_val = val; - } - fn returnsBool(f: f32) bool { - return f > 0; - } - fn identity(self: c_uint) c_uint { - return self; - } - }; - - CAST_OR_CALL(Helper.returnsVoid, true); - try testing.expectEqual(true, Helper.last_val); - CAST_OR_CALL(Helper.returnsVoid, false); - try testing.expectEqual(false, Helper.last_val); - - try testing.expectEqual(Helper.returnsBool(1), CAST_OR_CALL(Helper.returnsBool, @as(f32, 1))); - try testing.expectEqual(Helper.returnsBool(-1), CAST_OR_CALL(Helper.returnsBool, @as(f32, -1))); - - try testing.expectEqual(Helper.identity(@as(c_uint, 100)), CAST_OR_CALL(Helper.identity, @as(c_uint, 100))); -} diff --git a/lib/compiler/translate-c/src/main.zig b/lib/compiler/translate-c/src/main.zig deleted file mode 100644 index b3848096666dff5c443c674be5c1d6f27acdec7d..0000000000000000000000000000000000000000 --- a/lib/compiler/translate-c/src/main.zig +++ /dev/null @@ -1,251 +0,0 @@ -const std = @import("std"); -const assert = std.debug.assert; -const mem = std.mem; -const process = std.process; -const aro = @import("aro"); -const Translator = @import("Translator.zig"); - -const fast_exit = @import("builtin").mode != .Debug; - -var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init; - -pub fn main() u8 { - const gpa = general_purpose_allocator.allocator(); - defer _ = general_purpose_allocator.deinit(); - - var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator); - defer arena_instance.deinit(); - const arena = arena_instance.allocator(); - - const args = process.argsAlloc(arena) catch { - std.debug.print("ran out of memory allocating arguments\n", .{}); - if (fast_exit) process.exit(1); - return 1; - }; - - var stderr_buf: [1024]u8 = undefined; - var stderr = std.fs.File.stderr().writer(&stderr_buf); - var diagnostics: aro.Diagnostics = .{ - .output = .{ .to_writer = .{ - .color = .detect(stderr.file), - .writer = &stderr.interface, - } }, - }; - - var comp = aro.Compilation.initDefault(gpa, arena, &diagnostics, std.fs.cwd()) catch |err| switch (err) { - error.OutOfMemory => { - std.debug.print("ran out of memory initializing C compilation\n", .{}); - if (fast_exit) process.exit(1); - return 1; - }, - }; - defer comp.deinit(); - - const exe_name = std.fs.selfExePathAlloc(gpa) catch { - std.debug.print("unable to find translate-c executable path\n", .{}); - if (fast_exit) process.exit(1); - return 1; - }; - defer gpa.free(exe_name); - - var driver: aro.Driver = .{ .comp = &comp, .diagnostics = &diagnostics, .aro_name = exe_name }; - defer driver.deinit(); - - var toolchain: aro.Toolchain = .{ .driver = &driver, .filesystem = .{ .real = comp.cwd } }; - defer toolchain.deinit(); - - translate(&driver, &toolchain, args) catch |err| switch (err) { - error.OutOfMemory => { - std.debug.print("ran out of memory translating\n", .{}); - if (fast_exit) process.exit(1); - return 1; - }, - error.FatalError => { - if (fast_exit) process.exit(1); - return 1; - }, - error.WriteFailed => { - std.debug.print("unable to write to stdout\n", .{}); - if (fast_exit) process.exit(1); - return 1; - }, - }; - if (fast_exit) process.exit(@intFromBool(comp.diagnostics.errors != 0)); - return @intFromBool(comp.diagnostics.errors != 0); -} - -pub const usage = - \\Usage {s}: [options] file [CC options] - \\ - \\Options: - \\ --help Print this message - \\ --version Print translate-c version - \\ -fmodule-libs Import libraries as modules - \\ -fno-module-libs (default) Install libraries next to output file - \\ - \\ -; - -fn translate(d: *aro.Driver, tc: *aro.Toolchain, args: [][:0]u8) !void { - const gpa = d.comp.gpa; - - var module_libs = false; - - const aro_args = args: { - var i: usize = 0; - for (args) |arg| { - args[i] = arg; - if (mem.eql(u8, arg, "--help")) { - var stdout_buf: [512]u8 = undefined; - var stdout = std.fs.File.stdout().writer(&stdout_buf); - try stdout.interface.print(usage, .{args[0]}); - try stdout.interface.flush(); - return; - } else if (mem.eql(u8, arg, "--version")) { - var stdout_buf: [512]u8 = undefined; - var stdout = std.fs.File.stdout().writer(&stdout_buf); - // TODO add version - try stdout.interface.writeAll("0.0.0-dev\n"); - try stdout.interface.flush(); - return; - } else if (mem.eql(u8, arg, "-fmodule-libs")) { - module_libs = true; - } else if (mem.eql(u8, arg, "-fno-module-libs")) { - module_libs = false; - } else { - i += 1; - } - } - break :args args[0..i]; - }; - const user_macros = macros: { - var macro_buf: std.ArrayListUnmanaged(u8) = .empty; - defer macro_buf.deinit(gpa); - - try macro_buf.appendSlice(gpa, "#define __TRANSLATE_C__ 1\n"); - - var discard_buf: [256]u8 = undefined; - var discarding: std.io.Writer.Discarding = .init(&discard_buf); - assert(!try d.parseArgs(&discarding.writer, ¯o_buf, aro_args)); - if (macro_buf.items.len > std.math.maxInt(u32)) { - return d.fatal("user provided macro source exceeded max size", .{}); - } - - const content = try macro_buf.toOwnedSlice(gpa); - errdefer gpa.free(content); - - break :macros try d.comp.addSourceFromOwnedBuffer("", content, .user); - }; - - if (d.inputs.items.len != 1) { - return d.fatal("expected exactly one input file", .{}); - } - const source = d.inputs.items[0]; - - tc.discover() catch |er| switch (er) { - error.OutOfMemory => return error.OutOfMemory, - error.TooManyMultilibs => return d.fatal("found more than one multilib with the same priority", .{}), - }; - tc.defineSystemIncludes() catch |er| switch (er) { - error.OutOfMemory => return error.OutOfMemory, - error.AroIncludeNotFound => return d.fatal("unable to find Aro builtin headers", .{}), - }; - - const builtin_macros = d.comp.generateBuiltinMacros(.include_system_defines) catch |err| switch (err) { - error.FileTooBig => return d.fatal("builtin macro source exceeded max size", .{}), - else => |e| return e, - }; - - var pp = try aro.Preprocessor.initDefault(d.comp); - defer pp.deinit(); - - try pp.preprocessSources(&.{ source, builtin_macros, user_macros }); - - var c_tree = try pp.parse(); - defer c_tree.deinit(); - - if (d.diagnostics.errors != 0) { - if (fast_exit) process.exit(1); - return error.FatalError; - } - - const rendered_zig = try Translator.translate(.{ - .gpa = gpa, - .comp = d.comp, - .pp = &pp, - .tree = &c_tree, - .module_libs = module_libs, - }); - defer gpa.free(rendered_zig); - - var close_out_file = false; - var out_file_path: []const u8 = ""; - var out_file: std.fs.File = .stdout(); - defer if (close_out_file) out_file.close(); - - if (d.output_name) |path| blk: { - if (std.mem.eql(u8, path, "-")) break :blk; - if (std.fs.path.dirname(path)) |dirname| { - std.fs.cwd().makePath(dirname) catch |err| - return d.fatal("failed to create path to '{s}': {s}", .{ path, aro.Driver.errorDescription(err) }); - } - out_file = std.fs.cwd().createFile(path, .{}) catch |err| { - return d.fatal("failed to create output file '{s}': {s}", .{ path, aro.Driver.errorDescription(err) }); - }; - close_out_file = true; - out_file_path = path; - } - - var out_buf: [4096]u8 = undefined; - var out_writer = out_file.writer(&out_buf); - out_writer.interface.writeAll(rendered_zig) catch - return d.fatal("failed to write result to '{s}': {s}", .{ out_file_path, aro.Driver.errorDescription(out_writer.err.?) }); - - if (!module_libs) { - const dest_path = if (d.output_name) |path| std.fs.path.dirname(path) else null; - installLibs(d, dest_path) catch |err| - return d.fatal("failed to install library files: {s}", .{aro.Driver.errorDescription(err)}); - } - - if (fast_exit) process.exit(0); -} - -fn installLibs(d: *aro.Driver, dest_path: ?[]const u8) !void { - const gpa = d.comp.gpa; - const cwd = std.fs.cwd(); - - const self_exe_path = try std.fs.selfExePathAlloc(gpa); - defer gpa.free(self_exe_path); - - var cur_dir: []const u8 = self_exe_path; - while (std.fs.path.dirname(cur_dir)) |dirname| : (cur_dir = dirname) { - var base_dir = cwd.openDir(dirname, .{}) catch continue; - defer base_dir.close(); - - var lib_dir = base_dir.openDir("lib", .{}) catch continue; - defer lib_dir.close(); - - lib_dir.access("c_builtins.zig", .{}) catch continue; - - { - const install_path = try std.fs.path.join(gpa, &.{ dest_path orelse "", "c_builtins.zig" }); - defer gpa.free(install_path); - try lib_dir.copyFile("c_builtins.zig", cwd, install_path, .{}); - } - { - const install_path = try std.fs.path.join(gpa, &.{ dest_path orelse "", "helpers.zig" }); - defer gpa.free(install_path); - try lib_dir.copyFile("helpers.zig", cwd, install_path, .{}); - } - return; - } - return error.FileNotFound; -} - -comptime { - if (@import("builtin").is_test) { - _ = Translator; - _ = @import("helpers.zig"); - _ = @import("PatternList.zig"); - } -} diff --git a/lib/std/zig.zig b/lib/std/zig.zig index c8eea995cd0f8ac6e35f790a67c8cea1bea2a867..9fc64460ece0e106f3ee1f5fa066901c86e82ce5 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -36,9 +36,10 @@ pub const ParsedCharLiteral = string_literal.ParsedCharLiteral; pub const parseCharLiteral = string_literal.parseCharLiteral; pub const parseNumberLiteral = number_literal.parseNumberLiteral; -// Files needed by translate-c. -pub const c_builtins = @import("zig/c_builtins.zig"); -pub const c_translation = @import("zig/c_translation.zig"); +pub const c_translation = struct { + pub const builtins = @import("zig/c_translation/builtins.zig"); + pub const helpers = @import("zig/c_translation/helpers.zig"); +}; pub const SrcHasher = std.crypto.hash.Blake3; pub const SrcHash = [16]u8; diff --git a/lib/std/zig/c_builtins.zig b/lib/std/zig/c_builtins.zig deleted file mode 100644 index 2f6c2e8aa5d73fea22db9f8a26017b2fbe608586..0000000000000000000000000000000000000000 --- a/lib/std/zig/c_builtins.zig +++ /dev/null @@ -1,268 +0,0 @@ -const std = @import("std"); - -pub inline fn __builtin_bswap16(val: u16) u16 { - return @byteSwap(val); -} -pub inline fn __builtin_bswap32(val: u32) u32 { - return @byteSwap(val); -} -pub inline fn __builtin_bswap64(val: u64) u64 { - return @byteSwap(val); -} - -pub inline fn __builtin_signbit(val: f64) c_int { - return @intFromBool(std.math.signbit(val)); -} -pub inline fn __builtin_signbitf(val: f32) c_int { - return @intFromBool(std.math.signbit(val)); -} - -pub inline fn __builtin_popcount(val: c_uint) c_int { - // popcount of a c_uint will never exceed the capacity of a c_int - @setRuntimeSafety(false); - return @as(c_int, @bitCast(@as(c_uint, @popCount(val)))); -} -pub inline fn __builtin_ctz(val: c_uint) c_int { - // Returns the number of trailing 0-bits in val, starting at the least significant bit position. - // In C if `val` is 0, the result is undefined; in zig it's the number of bits in a c_uint - @setRuntimeSafety(false); - return @as(c_int, @bitCast(@as(c_uint, @ctz(val)))); -} -pub inline fn __builtin_clz(val: c_uint) c_int { - // Returns the number of leading 0-bits in x, starting at the most significant bit position. - // In C if `val` is 0, the result is undefined; in zig it's the number of bits in a c_uint - @setRuntimeSafety(false); - return @as(c_int, @bitCast(@as(c_uint, @clz(val)))); -} - -pub inline fn __builtin_sqrt(val: f64) f64 { - return @sqrt(val); -} -pub inline fn __builtin_sqrtf(val: f32) f32 { - return @sqrt(val); -} - -pub inline fn __builtin_sin(val: f64) f64 { - return @sin(val); -} -pub inline fn __builtin_sinf(val: f32) f32 { - return @sin(val); -} -pub inline fn __builtin_cos(val: f64) f64 { - return @cos(val); -} -pub inline fn __builtin_cosf(val: f32) f32 { - return @cos(val); -} - -pub inline fn __builtin_exp(val: f64) f64 { - return @exp(val); -} -pub inline fn __builtin_expf(val: f32) f32 { - return @exp(val); -} -pub inline fn __builtin_exp2(val: f64) f64 { - return @exp2(val); -} -pub inline fn __builtin_exp2f(val: f32) f32 { - return @exp2(val); -} -pub inline fn __builtin_log(val: f64) f64 { - return @log(val); -} -pub inline fn __builtin_logf(val: f32) f32 { - return @log(val); -} -pub inline fn __builtin_log2(val: f64) f64 { - return @log2(val); -} -pub inline fn __builtin_log2f(val: f32) f32 { - return @log2(val); -} -pub inline fn __builtin_log10(val: f64) f64 { - return @log10(val); -} -pub inline fn __builtin_log10f(val: f32) f32 { - return @log10(val); -} - -// Standard C Library bug: The absolute value of the most negative integer remains negative. -pub inline fn __builtin_abs(val: c_int) c_int { - return if (val == std.math.minInt(c_int)) val else @intCast(@abs(val)); -} -pub inline fn __builtin_labs(val: c_long) c_long { - return if (val == std.math.minInt(c_long)) val else @intCast(@abs(val)); -} -pub inline fn __builtin_llabs(val: c_longlong) c_longlong { - return if (val == std.math.minInt(c_longlong)) val else @intCast(@abs(val)); -} -pub inline fn __builtin_fabs(val: f64) f64 { - return @abs(val); -} -pub inline fn __builtin_fabsf(val: f32) f32 { - return @abs(val); -} - -pub inline fn __builtin_floor(val: f64) f64 { - return @floor(val); -} -pub inline fn __builtin_floorf(val: f32) f32 { - return @floor(val); -} -pub inline fn __builtin_ceil(val: f64) f64 { - return @ceil(val); -} -pub inline fn __builtin_ceilf(val: f32) f32 { - return @ceil(val); -} -pub inline fn __builtin_trunc(val: f64) f64 { - return @trunc(val); -} -pub inline fn __builtin_truncf(val: f32) f32 { - return @trunc(val); -} -pub inline fn __builtin_round(val: f64) f64 { - return @round(val); -} -pub inline fn __builtin_roundf(val: f32) f32 { - return @round(val); -} - -pub inline fn __builtin_strlen(s: [*c]const u8) usize { - return std.mem.sliceTo(s, 0).len; -} -pub inline fn __builtin_strcmp(s1: [*c]const u8, s2: [*c]const u8) c_int { - return switch (std.mem.orderZ(u8, s1, s2)) { - .lt => -1, - .eq => 0, - .gt => 1, - }; -} - -pub inline fn __builtin_object_size(ptr: ?*const anyopaque, ty: c_int) usize { - _ = ptr; - // clang semantics match gcc's: https://gcc.gnu.org/onlinedocs/gcc/Object-Size-Checking.html - // If it is not possible to determine which objects ptr points to at compile time, - // __builtin_object_size should return (size_t) -1 for type 0 or 1 and (size_t) 0 - // for type 2 or 3. - if (ty == 0 or ty == 1) return @as(usize, @bitCast(-@as(isize, 1))); - if (ty == 2 or ty == 3) return 0; - unreachable; -} - -pub inline fn __builtin___memset_chk( - dst: ?*anyopaque, - val: c_int, - len: usize, - remaining: usize, -) ?*anyopaque { - if (len > remaining) @panic("std.c.builtins.memset_chk called with len > remaining"); - return __builtin_memset(dst, val, len); -} - -pub inline fn __builtin_memset(dst: ?*anyopaque, val: c_int, len: usize) ?*anyopaque { - const dst_cast = @as([*c]u8, @ptrCast(dst)); - @memset(dst_cast[0..len], @as(u8, @bitCast(@as(i8, @truncate(val))))); - return dst; -} - -pub inline fn __builtin___memcpy_chk( - noalias dst: ?*anyopaque, - noalias src: ?*const anyopaque, - len: usize, - remaining: usize, -) ?*anyopaque { - if (len > remaining) @panic("std.c.builtins.memcpy_chk called with len > remaining"); - return __builtin_memcpy(dst, src, len); -} - -pub inline fn __builtin_memcpy( - noalias dst: ?*anyopaque, - noalias src: ?*const anyopaque, - len: usize, -) ?*anyopaque { - if (len > 0) @memcpy( - @as([*]u8, @ptrCast(dst.?))[0..len], - @as([*]const u8, @ptrCast(src.?)), - ); - return dst; -} - -/// The return value of __builtin_expect is `expr`. `c` is the expected value -/// of `expr` and is used as a hint to the compiler in C. Here it is unused. -pub inline fn __builtin_expect(expr: c_long, c: c_long) c_long { - _ = c; - return expr; -} - -/// returns a quiet NaN. Quiet NaNs have many representations; tagp is used to select one in an -/// implementation-defined way. -/// This implementation is based on the description for __builtin_nan provided in the GCC docs at -/// https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html#index-_005f_005fbuiltin_005fnan -/// Comment is reproduced below: -/// Since ISO C99 defines this function in terms of strtod, which we do not implement, a description -/// of the parsing is in order. -/// The string is parsed as by strtol; that is, the base is recognized by leading ‘0’ or ‘0x’ prefixes. -/// The number parsed is placed in the significand such that the least significant bit of the number is -/// at the least significant bit of the significand. -/// The number is truncated to fit the significand field provided. -/// The significand is forced to be a quiet NaN. -/// -/// If tagp contains any non-numeric characters, the function returns a NaN whose significand is zero. -/// If tagp is empty, the function returns a NaN whose significand is zero. -pub inline fn __builtin_nanf(tagp: []const u8) f32 { - const parsed = std.fmt.parseUnsigned(c_ulong, tagp, 0) catch 0; - const bits: u23 = @truncate(parsed); // single-precision float trailing significand is 23 bits - return @bitCast(@as(u32, bits) | @as(u32, @bitCast(std.math.nan(f32)))); -} - -pub inline fn __builtin_huge_valf() f32 { - return std.math.inf(f32); -} - -pub inline fn __builtin_inff() f32 { - return std.math.inf(f32); -} - -pub inline fn __builtin_isnan(x: anytype) c_int { - return @intFromBool(std.math.isNan(x)); -} - -pub inline fn __builtin_isinf(x: anytype) c_int { - return @intFromBool(std.math.isInf(x)); -} - -/// Similar to isinf, except the return value is -1 for an argument of -Inf and 1 for an argument of +Inf. -pub inline fn __builtin_isinf_sign(x: anytype) c_int { - if (!std.math.isInf(x)) return 0; - return if (std.math.isPositiveInf(x)) 1 else -1; -} - -pub inline fn __has_builtin(func: anytype) c_int { - _ = func; - return @intFromBool(true); -} - -pub inline fn __builtin_assume(cond: bool) void { - if (!cond) unreachable; -} - -pub inline fn __builtin_unreachable() noreturn { - unreachable; -} - -pub inline fn __builtin_constant_p(expr: anytype) c_int { - _ = expr; - return @intFromBool(false); -} -pub fn __builtin_mul_overflow(a: anytype, b: anytype, result: *@TypeOf(a, b)) c_int { - const res = @mulWithOverflow(a, b); - result.* = res[0]; - return res[1]; -} - -// __builtin_alloca_with_align is not currently implemented. -// It is used in a run-translated-c test and a test-translate-c test to ensure that non-implemented -// builtins are correctly demoted. If you implement __builtin_alloca_with_align, please update the -// run-translated-c test and the test-translate-c test to use a different non-implemented builtin. -// pub inline fn __builtin_alloca_with_align(size: usize, alignment: usize) *anyopaque {} diff --git a/lib/std/zig/c_translation.zig b/lib/std/zig/c_translation.zig deleted file mode 100644 index be0019c596bd8d01aeaa31a667feb07ea55dc602..0000000000000000000000000000000000000000 --- a/lib/std/zig/c_translation.zig +++ /dev/null @@ -1,699 +0,0 @@ -const std = @import("std"); -const builtin = @import("builtin"); -const testing = std.testing; -const math = std.math; -const mem = std.mem; - -/// Given a type and value, cast the value to the type as c would. -pub fn cast(comptime DestType: type, target: anytype) DestType { - // this function should behave like transCCast in translate-c, except it's for macros - const SourceType = @TypeOf(target); - switch (@typeInfo(DestType)) { - .@"fn" => return castToPtr(*const DestType, SourceType, target), - .pointer => return castToPtr(DestType, SourceType, target), - .optional => |dest_opt| { - if (@typeInfo(dest_opt.child) == .pointer) { - return castToPtr(DestType, SourceType, target); - } else if (@typeInfo(dest_opt.child) == .@"fn") { - return castToPtr(?*const dest_opt.child, SourceType, target); - } - }, - .int => { - switch (@typeInfo(SourceType)) { - .pointer => { - return castInt(DestType, @intFromPtr(target)); - }, - .optional => |opt| { - if (@typeInfo(opt.child) == .pointer) { - return castInt(DestType, @intFromPtr(target)); - } - }, - .int => { - return castInt(DestType, target); - }, - .@"fn" => { - return castInt(DestType, @intFromPtr(&target)); - }, - .bool => { - return @intFromBool(target); - }, - else => {}, - } - }, - .float => { - switch (@typeInfo(SourceType)) { - .int => return @as(DestType, @floatFromInt(target)), - .float => return @as(DestType, @floatCast(target)), - .bool => return @as(DestType, @floatFromInt(@intFromBool(target))), - else => {}, - } - }, - .@"union" => |info| { - inline for (info.fields) |field| { - if (field.type == SourceType) return @unionInit(DestType, field.name, target); - } - @compileError("cast to union type '" ++ @typeName(DestType) ++ "' from type '" ++ @typeName(SourceType) ++ "' which is not present in union"); - }, - .bool => return cast(usize, target) != 0, - else => {}, - } - return @as(DestType, target); -} - -fn castInt(comptime DestType: type, target: anytype) DestType { - const dest = @typeInfo(DestType).int; - const source = @typeInfo(@TypeOf(target)).int; - - if (dest.bits < source.bits) - return @as(DestType, @bitCast(@as(std.meta.Int(source.signedness, dest.bits), @truncate(target)))) - else - return @as(DestType, @bitCast(@as(std.meta.Int(source.signedness, dest.bits), target))); -} - -fn castPtr(comptime DestType: type, target: anytype) DestType { - return @ptrCast(@alignCast(@constCast(@volatileCast(target)))); -} - -fn castToPtr(comptime DestType: type, comptime SourceType: type, target: anytype) DestType { - switch (@typeInfo(SourceType)) { - .int => { - return @as(DestType, @ptrFromInt(castInt(usize, target))); - }, - .comptime_int => { - if (target < 0) - return @as(DestType, @ptrFromInt(@as(usize, @bitCast(@as(isize, @intCast(target)))))) - else - return @as(DestType, @ptrFromInt(@as(usize, @intCast(target)))); - }, - .pointer => { - return castPtr(DestType, target); - }, - .@"fn" => { - return castPtr(DestType, &target); - }, - .optional => |target_opt| { - if (@typeInfo(target_opt.child) == .pointer) { - return castPtr(DestType, target); - } - }, - else => {}, - } - return @as(DestType, target); -} - -fn ptrInfo(comptime PtrType: type) std.builtin.Type.Pointer { - return switch (@typeInfo(PtrType)) { - .optional => |opt_info| @typeInfo(opt_info.child).pointer, - .pointer => |ptr_info| ptr_info, - else => unreachable, - }; -} - -test "cast" { - var i = @as(i64, 10); - - try testing.expect(cast(*u8, 16) == @as(*u8, @ptrFromInt(16))); - try testing.expect(cast(*u64, &i).* == @as(u64, 10)); - try testing.expect(cast(*i64, @as(?*align(1) i64, &i)) == &i); - - try testing.expect(cast(?*u8, 2) == @as(*u8, @ptrFromInt(2))); - try testing.expect(cast(?*i64, @as(*align(1) i64, &i)) == &i); - try testing.expect(cast(?*i64, @as(?*align(1) i64, &i)) == &i); - - try testing.expectEqual(@as(u32, 4), cast(u32, @as(*u32, @ptrFromInt(4)))); - try testing.expectEqual(@as(u32, 4), cast(u32, @as(?*u32, @ptrFromInt(4)))); - try testing.expectEqual(@as(u32, 10), cast(u32, @as(u64, 10))); - - try testing.expectEqual(@as(i32, @bitCast(@as(u32, 0x8000_0000))), cast(i32, @as(u32, 0x8000_0000))); - - try testing.expectEqual(@as(*u8, @ptrFromInt(2)), cast(*u8, @as(*const u8, @ptrFromInt(2)))); - try testing.expectEqual(@as(*u8, @ptrFromInt(2)), cast(*u8, @as(*volatile u8, @ptrFromInt(2)))); - - try testing.expectEqual(@as(?*anyopaque, @ptrFromInt(2)), cast(?*anyopaque, @as(*u8, @ptrFromInt(2)))); - - var foo: c_int = -1; - _ = &foo; - try testing.expect(cast(*anyopaque, -1) == @as(*anyopaque, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1)))))); - try testing.expect(cast(*anyopaque, foo) == @as(*anyopaque, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1)))))); - try testing.expect(cast(?*anyopaque, -1) == @as(?*anyopaque, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1)))))); - try testing.expect(cast(?*anyopaque, foo) == @as(?*anyopaque, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1)))))); - - const FnPtr = ?*align(1) const fn (*anyopaque) void; - try testing.expect(cast(FnPtr, 0) == @as(FnPtr, @ptrFromInt(@as(usize, 0)))); - try testing.expect(cast(FnPtr, foo) == @as(FnPtr, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1)))))); -} - -/// Given a value returns its size as C's sizeof operator would. -pub fn sizeof(target: anytype) usize { - const T: type = if (@TypeOf(target) == type) target else @TypeOf(target); - switch (@typeInfo(T)) { - .float, .int, .@"struct", .@"union", .array, .bool, .vector => return @sizeOf(T), - .@"fn" => { - // sizeof(main) in C returns 1 - return 1; - }, - .null => return @sizeOf(*anyopaque), - .void => { - // Note: sizeof(void) is 1 on clang/gcc and 0 on MSVC. - return 1; - }, - .@"opaque" => { - if (T == anyopaque) { - // Note: sizeof(void) is 1 on clang/gcc and 0 on MSVC. - return 1; - } else { - @compileError("Cannot use C sizeof on opaque type " ++ @typeName(T)); - } - }, - .optional => |opt| { - if (@typeInfo(opt.child) == .pointer) { - return sizeof(opt.child); - } else { - @compileError("Cannot use C sizeof on non-pointer optional " ++ @typeName(T)); - } - }, - .pointer => |ptr| { - if (ptr.size == .slice) { - @compileError("Cannot use C sizeof on slice type " ++ @typeName(T)); - } - // for strings, sizeof("a") returns 2. - // normal pointer decay scenarios from C are handled - // in the .array case above, but strings remain literals - // and are therefore always pointers, so they need to be - // specially handled here. - if (ptr.size == .one and ptr.is_const and @typeInfo(ptr.child) == .array) { - const array_info = @typeInfo(ptr.child).array; - if ((array_info.child == u8 or array_info.child == u16) and array_info.sentinel() == 0) { - // length of the string plus one for the null terminator. - return (array_info.len + 1) * @sizeOf(array_info.child); - } - } - // When zero sized pointers are removed, this case will no - // longer be reachable and can be deleted. - if (@sizeOf(T) == 0) { - return @sizeOf(*anyopaque); - } - return @sizeOf(T); - }, - .comptime_float => return @sizeOf(f64), // TODO c_double #3999 - .comptime_int => { - // TODO to get the correct result we have to translate - // `1073741824 * 4` as `int(1073741824) *% int(4)` since - // sizeof(1073741824 * 4) != sizeof(4294967296). - - // TODO test if target fits in int, long or long long - return @sizeOf(c_int); - }, - else => @compileError("std.meta.sizeof does not support type " ++ @typeName(T)), - } -} - -test "sizeof" { - const S = extern struct { a: u32 }; - - const ptr_size = @sizeOf(*anyopaque); - - try testing.expect(sizeof(u32) == 4); - try testing.expect(sizeof(@as(u32, 2)) == 4); - try testing.expect(sizeof(2) == @sizeOf(c_int)); - - try testing.expect(sizeof(2.0) == @sizeOf(f64)); - - try testing.expect(sizeof(S) == 4); - - try testing.expect(sizeof([_]u32{ 4, 5, 6 }) == 12); - try testing.expect(sizeof([3]u32) == 12); - try testing.expect(sizeof([3:0]u32) == 16); - try testing.expect(sizeof(&[_]u32{ 4, 5, 6 }) == ptr_size); - - try testing.expect(sizeof(*u32) == ptr_size); - try testing.expect(sizeof([*]u32) == ptr_size); - try testing.expect(sizeof([*c]u32) == ptr_size); - try testing.expect(sizeof(?*u32) == ptr_size); - try testing.expect(sizeof(?[*]u32) == ptr_size); - try testing.expect(sizeof(*anyopaque) == ptr_size); - try testing.expect(sizeof(*void) == ptr_size); - try testing.expect(sizeof(null) == ptr_size); - - try testing.expect(sizeof("foobar") == 7); - try testing.expect(sizeof(&[_:0]u16{ 'f', 'o', 'o', 'b', 'a', 'r' }) == 14); - try testing.expect(sizeof(*const [4:0]u8) == 5); - try testing.expect(sizeof(*[4:0]u8) == ptr_size); - try testing.expect(sizeof([*]const [4:0]u8) == ptr_size); - try testing.expect(sizeof(*const *const [4:0]u8) == ptr_size); - try testing.expect(sizeof(*const [4]u8) == ptr_size); - - if (false) { // TODO - try testing.expect(sizeof(&sizeof) == @sizeOf(@TypeOf(&sizeof))); - try testing.expect(sizeof(sizeof) == 1); - } - - try testing.expect(sizeof(void) == 1); - try testing.expect(sizeof(anyopaque) == 1); -} - -pub const CIntLiteralBase = enum { decimal, octal, hex }; - -fn PromoteIntLiteralReturnType(comptime SuffixType: type, comptime number: comptime_int, comptime base: CIntLiteralBase) type { - const signed_decimal = [_]type{ c_int, c_long, c_longlong, c_ulonglong }; - const signed_oct_hex = [_]type{ c_int, c_uint, c_long, c_ulong, c_longlong, c_ulonglong }; - const unsigned = [_]type{ c_uint, c_ulong, c_ulonglong }; - - const list: []const type = if (@typeInfo(SuffixType).int.signedness == .unsigned) - &unsigned - else if (base == .decimal) - &signed_decimal - else - &signed_oct_hex; - - var pos = mem.indexOfScalar(type, list, SuffixType).?; - - while (pos < list.len) : (pos += 1) { - if (number >= math.minInt(list[pos]) and number <= math.maxInt(list[pos])) { - return list[pos]; - } - } - @compileError("Integer literal is too large"); -} - -/// Promote the type of an integer literal until it fits as C would. -pub fn promoteIntLiteral( - comptime SuffixType: type, - comptime number: comptime_int, - comptime base: CIntLiteralBase, -) PromoteIntLiteralReturnType(SuffixType, number, base) { - return number; -} - -test "promoteIntLiteral" { - const signed_hex = promoteIntLiteral(c_int, math.maxInt(c_int) + 1, .hex); - try testing.expectEqual(c_uint, @TypeOf(signed_hex)); - - if (math.maxInt(c_longlong) == math.maxInt(c_int)) return; - - const signed_decimal = promoteIntLiteral(c_int, math.maxInt(c_int) + 1, .decimal); - const unsigned = promoteIntLiteral(c_uint, math.maxInt(c_uint) + 1, .hex); - - if (math.maxInt(c_long) > math.maxInt(c_int)) { - try testing.expectEqual(c_long, @TypeOf(signed_decimal)); - try testing.expectEqual(c_ulong, @TypeOf(unsigned)); - } else { - try testing.expectEqual(c_longlong, @TypeOf(signed_decimal)); - try testing.expectEqual(c_ulonglong, @TypeOf(unsigned)); - } -} - -/// Convert from clang __builtin_shufflevector index to Zig @shuffle index -/// clang requires __builtin_shufflevector index arguments to be integer constants. -/// negative values for `this_index` indicate "don't care". -/// clang enforces that `this_index` is less than the total number of vector elements -/// See https://ziglang.org/documentation/master/#shuffle -/// See https://clang.llvm.org/docs/LanguageExtensions.html#langext-builtin-shufflevector -pub fn shuffleVectorIndex(comptime this_index: c_int, comptime source_vector_len: usize) i32 { - const positive_index = std.math.cast(usize, this_index) orelse return undefined; - if (positive_index < source_vector_len) return @as(i32, @intCast(this_index)); - const b_index = positive_index - source_vector_len; - return ~@as(i32, @intCast(b_index)); -} - -test "shuffleVectorIndex" { - const vector_len: usize = 4; - - _ = shuffleVectorIndex(-1, vector_len); - - try testing.expect(shuffleVectorIndex(0, vector_len) == 0); - try testing.expect(shuffleVectorIndex(1, vector_len) == 1); - try testing.expect(shuffleVectorIndex(2, vector_len) == 2); - try testing.expect(shuffleVectorIndex(3, vector_len) == 3); - - try testing.expect(shuffleVectorIndex(4, vector_len) == -1); - try testing.expect(shuffleVectorIndex(5, vector_len) == -2); - try testing.expect(shuffleVectorIndex(6, vector_len) == -3); - try testing.expect(shuffleVectorIndex(7, vector_len) == -4); -} - -/// Constructs a [*c] pointer with the const and volatile annotations -/// from SelfType for pointing to a C flexible array of ElementType. -pub fn FlexibleArrayType(comptime SelfType: type, comptime ElementType: type) type { - switch (@typeInfo(SelfType)) { - .pointer => |ptr| { - return @Type(.{ .pointer = .{ - .size = .c, - .is_const = ptr.is_const, - .is_volatile = ptr.is_volatile, - .alignment = @alignOf(ElementType), - .address_space = .generic, - .child = ElementType, - .is_allowzero = true, - .sentinel_ptr = null, - } }); - }, - else => |info| @compileError("Invalid self type \"" ++ @tagName(info) ++ "\" for flexible array getter: " ++ @typeName(SelfType)), - } -} - -test "Flexible Array Type" { - const Container = extern struct { - size: usize, - }; - - try testing.expectEqual(FlexibleArrayType(*Container, c_int), [*c]c_int); - try testing.expectEqual(FlexibleArrayType(*const Container, c_int), [*c]const c_int); - try testing.expectEqual(FlexibleArrayType(*volatile Container, c_int), [*c]volatile c_int); - try testing.expectEqual(FlexibleArrayType(*const volatile Container, c_int), [*c]const volatile c_int); -} - -/// C `%` operator for signed integers -/// C standard states: "If the quotient a/b is representable, the expression (a/b)*b + a%b shall equal a" -/// The quotient is not representable if denominator is zero, or if numerator is the minimum integer for -/// the type and denominator is -1. C has undefined behavior for those two cases; this function has safety -/// checked undefined behavior -pub fn signedRemainder(numerator: anytype, denominator: anytype) @TypeOf(numerator, denominator) { - std.debug.assert(@typeInfo(@TypeOf(numerator, denominator)).int.signedness == .signed); - if (denominator > 0) return @rem(numerator, denominator); - return numerator - @divTrunc(numerator, denominator) * denominator; -} - -pub const Macros = struct { - pub fn U_SUFFIX(comptime n: comptime_int) @TypeOf(promoteIntLiteral(c_uint, n, .decimal)) { - return promoteIntLiteral(c_uint, n, .decimal); - } - - fn L_SUFFIX_ReturnType(comptime number: anytype) type { - switch (@typeInfo(@TypeOf(number))) { - .int, .comptime_int => return @TypeOf(promoteIntLiteral(c_long, number, .decimal)), - .float, .comptime_float => return c_longdouble, - else => @compileError("Invalid value for L suffix"), - } - } - pub fn L_SUFFIX(comptime number: anytype) L_SUFFIX_ReturnType(number) { - switch (@typeInfo(@TypeOf(number))) { - .int, .comptime_int => return promoteIntLiteral(c_long, number, .decimal), - .float, .comptime_float => @compileError("TODO: c_longdouble initialization from comptime_float not supported"), - else => @compileError("Invalid value for L suffix"), - } - } - - pub fn UL_SUFFIX(comptime n: comptime_int) @TypeOf(promoteIntLiteral(c_ulong, n, .decimal)) { - return promoteIntLiteral(c_ulong, n, .decimal); - } - - pub fn LL_SUFFIX(comptime n: comptime_int) @TypeOf(promoteIntLiteral(c_longlong, n, .decimal)) { - return promoteIntLiteral(c_longlong, n, .decimal); - } - - pub fn ULL_SUFFIX(comptime n: comptime_int) @TypeOf(promoteIntLiteral(c_ulonglong, n, .decimal)) { - return promoteIntLiteral(c_ulonglong, n, .decimal); - } - - pub fn F_SUFFIX(comptime f: comptime_float) f32 { - return @as(f32, f); - } - - pub fn WL_CONTAINER_OF(ptr: anytype, sample: anytype, comptime member: []const u8) @TypeOf(sample) { - return @fieldParentPtr(member, ptr); - } - - /// A 2-argument function-like macro defined as #define FOO(A, B) (A)(B) - /// could be either: cast B to A, or call A with the value B. - pub fn CAST_OR_CALL(a: anytype, b: anytype) switch (@typeInfo(@TypeOf(a))) { - .type => a, - .@"fn" => |fn_info| fn_info.return_type orelse void, - else => |info| @compileError("Unexpected argument type: " ++ @tagName(info)), - } { - switch (@typeInfo(@TypeOf(a))) { - .type => return cast(a, b), - .@"fn" => return a(b), - else => unreachable, // return type will be a compile error otherwise - } - } - - pub inline fn DISCARD(x: anytype) void { - _ = x; - } -}; - -/// Integer promotion described in C11 6.3.1.1.2 -fn PromotedIntType(comptime T: type) type { - return switch (T) { - bool, c_short => c_int, - c_ushort => if (@sizeOf(c_ushort) == @sizeOf(c_int)) c_uint else c_int, - c_int, c_uint, c_long, c_ulong, c_longlong, c_ulonglong => T, - else => switch (@typeInfo(T)) { - .comptime_int => @compileError("Cannot promote `" ++ @typeName(T) ++ "`; a fixed-size number type is required"), - // promote to c_int if it can represent all values of T - .int => |int_info| if (int_info.bits < @bitSizeOf(c_int)) - c_int - // otherwise, restore the original C type - else if (int_info.bits == @bitSizeOf(c_int)) - if (int_info.signedness == .unsigned) c_uint else c_int - else if (int_info.bits <= @bitSizeOf(c_long)) - if (int_info.signedness == .unsigned) c_ulong else c_long - else if (int_info.bits <= @bitSizeOf(c_longlong)) - if (int_info.signedness == .unsigned) c_ulonglong else c_longlong - else - @compileError("Cannot promote `" ++ @typeName(T) ++ "`; a C ABI type is required"), - else => @compileError("Attempted to promote invalid type `" ++ @typeName(T) ++ "`"), - }, - }; -} - -/// C11 6.3.1.1.1 -fn integerRank(comptime T: type) u8 { - return switch (T) { - bool => 0, - u8, i8 => 1, - c_short, c_ushort => 2, - c_int, c_uint => 3, - c_long, c_ulong => 4, - c_longlong, c_ulonglong => 5, - else => @compileError("integer rank not supported for `" ++ @typeName(T) ++ "`"), - }; -} - -fn ToUnsigned(comptime T: type) type { - return switch (T) { - c_int => c_uint, - c_long => c_ulong, - c_longlong => c_ulonglong, - else => @compileError("Cannot convert `" ++ @typeName(T) ++ "` to unsigned"), - }; -} - -/// "Usual arithmetic conversions" from C11 standard 6.3.1.8 -fn ArithmeticConversion(comptime A: type, comptime B: type) type { - if (A == c_longdouble or B == c_longdouble) return c_longdouble; - if (A == f80 or B == f80) return f80; - if (A == f64 or B == f64) return f64; - if (A == f32 or B == f32) return f32; - - const A_Promoted = PromotedIntType(A); - const B_Promoted = PromotedIntType(B); - comptime { - std.debug.assert(integerRank(A_Promoted) >= integerRank(c_int)); - std.debug.assert(integerRank(B_Promoted) >= integerRank(c_int)); - } - - if (A_Promoted == B_Promoted) return A_Promoted; - - const a_signed = @typeInfo(A_Promoted).int.signedness == .signed; - const b_signed = @typeInfo(B_Promoted).int.signedness == .signed; - - if (a_signed == b_signed) { - return if (integerRank(A_Promoted) > integerRank(B_Promoted)) A_Promoted else B_Promoted; - } - - const SignedType = if (a_signed) A_Promoted else B_Promoted; - const UnsignedType = if (!a_signed) A_Promoted else B_Promoted; - - if (integerRank(UnsignedType) >= integerRank(SignedType)) return UnsignedType; - - if (std.math.maxInt(SignedType) >= std.math.maxInt(UnsignedType)) return SignedType; - - return ToUnsigned(SignedType); -} - -test "ArithmeticConversion" { - // Promotions not necessarily the same for other platforms - if (builtin.target.cpu.arch != .x86_64 or builtin.target.os.tag != .linux) return error.SkipZigTest; - - const Test = struct { - /// Order of operands should not matter for arithmetic conversions - fn checkPromotion(comptime A: type, comptime B: type, comptime Expected: type) !void { - try std.testing.expect(ArithmeticConversion(A, B) == Expected); - try std.testing.expect(ArithmeticConversion(B, A) == Expected); - } - }; - - try Test.checkPromotion(c_longdouble, c_int, c_longdouble); - try Test.checkPromotion(c_int, f64, f64); - try Test.checkPromotion(f32, bool, f32); - - try Test.checkPromotion(bool, c_short, c_int); - try Test.checkPromotion(c_int, c_int, c_int); - try Test.checkPromotion(c_short, c_int, c_int); - - try Test.checkPromotion(c_int, c_long, c_long); - - try Test.checkPromotion(c_ulonglong, c_uint, c_ulonglong); - - try Test.checkPromotion(c_uint, c_int, c_uint); - - try Test.checkPromotion(c_uint, c_long, c_long); - - try Test.checkPromotion(c_ulong, c_longlong, c_ulonglong); - - // stdint.h - try Test.checkPromotion(u8, i8, c_int); - try Test.checkPromotion(u16, i16, c_int); - try Test.checkPromotion(i32, c_int, c_int); - try Test.checkPromotion(u32, c_int, c_uint); - try Test.checkPromotion(i64, c_int, c_long); - try Test.checkPromotion(u64, c_int, c_ulong); - try Test.checkPromotion(isize, c_int, c_long); - try Test.checkPromotion(usize, c_int, c_ulong); -} - -pub const MacroArithmetic = struct { - pub fn div(a: anytype, b: anytype) ArithmeticConversion(@TypeOf(a), @TypeOf(b)) { - const ResType = ArithmeticConversion(@TypeOf(a), @TypeOf(b)); - const a_casted = cast(ResType, a); - const b_casted = cast(ResType, b); - switch (@typeInfo(ResType)) { - .float => return a_casted / b_casted, - .int => return @divTrunc(a_casted, b_casted), - else => unreachable, - } - } - - pub fn rem(a: anytype, b: anytype) ArithmeticConversion(@TypeOf(a), @TypeOf(b)) { - const ResType = ArithmeticConversion(@TypeOf(a), @TypeOf(b)); - const a_casted = cast(ResType, a); - const b_casted = cast(ResType, b); - switch (@typeInfo(ResType)) { - .int => { - if (@typeInfo(ResType).int.signedness == .signed) { - return signedRemainder(a_casted, b_casted); - } else { - return a_casted % b_casted; - } - }, - else => unreachable, - } - } -}; - -test "Macro suffix functions" { - try testing.expect(@TypeOf(Macros.F_SUFFIX(1)) == f32); - - try testing.expect(@TypeOf(Macros.U_SUFFIX(1)) == c_uint); - if (math.maxInt(c_ulong) > math.maxInt(c_uint)) { - try testing.expect(@TypeOf(Macros.U_SUFFIX(math.maxInt(c_uint) + 1)) == c_ulong); - } - if (math.maxInt(c_ulonglong) > math.maxInt(c_ulong)) { - try testing.expect(@TypeOf(Macros.U_SUFFIX(math.maxInt(c_ulong) + 1)) == c_ulonglong); - } - - try testing.expect(@TypeOf(Macros.L_SUFFIX(1)) == c_long); - if (math.maxInt(c_long) > math.maxInt(c_int)) { - try testing.expect(@TypeOf(Macros.L_SUFFIX(math.maxInt(c_int) + 1)) == c_long); - } - if (math.maxInt(c_longlong) > math.maxInt(c_long)) { - try testing.expect(@TypeOf(Macros.L_SUFFIX(math.maxInt(c_long) + 1)) == c_longlong); - } - - try testing.expect(@TypeOf(Macros.UL_SUFFIX(1)) == c_ulong); - if (math.maxInt(c_ulonglong) > math.maxInt(c_ulong)) { - try testing.expect(@TypeOf(Macros.UL_SUFFIX(math.maxInt(c_ulong) + 1)) == c_ulonglong); - } - - try testing.expect(@TypeOf(Macros.LL_SUFFIX(1)) == c_longlong); - try testing.expect(@TypeOf(Macros.ULL_SUFFIX(1)) == c_ulonglong); -} - -test "WL_CONTAINER_OF" { - const S = struct { - a: u32 = 0, - b: u32 = 0, - }; - const x = S{}; - const y = S{}; - const ptr = Macros.WL_CONTAINER_OF(&x.b, &y, "b"); - try testing.expectEqual(&x, ptr); -} - -test "CAST_OR_CALL casting" { - const arg: c_int = 1000; - const casted = Macros.CAST_OR_CALL(u8, arg); - try testing.expectEqual(cast(u8, arg), casted); - - const S = struct { - x: u32 = 0, - }; - var s: S = .{}; - const casted_ptr = Macros.CAST_OR_CALL(*u8, &s); - try testing.expectEqual(cast(*u8, &s), casted_ptr); -} - -test "CAST_OR_CALL calling" { - const Helper = struct { - var last_val: bool = false; - fn returnsVoid(val: bool) void { - last_val = val; - } - fn returnsBool(f: f32) bool { - return f > 0; - } - fn identity(self: c_uint) c_uint { - return self; - } - }; - - Macros.CAST_OR_CALL(Helper.returnsVoid, true); - try testing.expectEqual(true, Helper.last_val); - Macros.CAST_OR_CALL(Helper.returnsVoid, false); - try testing.expectEqual(false, Helper.last_val); - - try testing.expectEqual(Helper.returnsBool(1), Macros.CAST_OR_CALL(Helper.returnsBool, @as(f32, 1))); - try testing.expectEqual(Helper.returnsBool(-1), Macros.CAST_OR_CALL(Helper.returnsBool, @as(f32, -1))); - - try testing.expectEqual(Helper.identity(@as(c_uint, 100)), Macros.CAST_OR_CALL(Helper.identity, @as(c_uint, 100))); -} - -test "Extended C ABI casting" { - if (math.maxInt(c_long) > math.maxInt(c_char)) { - try testing.expect(@TypeOf(Macros.L_SUFFIX(@as(c_char, math.maxInt(c_char) - 1))) == c_long); // c_char - } - if (math.maxInt(c_long) > math.maxInt(c_short)) { - try testing.expect(@TypeOf(Macros.L_SUFFIX(@as(c_short, math.maxInt(c_short) - 1))) == c_long); // c_short - } - - if (math.maxInt(c_long) > math.maxInt(c_ushort)) { - try testing.expect(@TypeOf(Macros.L_SUFFIX(@as(c_ushort, math.maxInt(c_ushort) - 1))) == c_long); //c_ushort - } - - if (math.maxInt(c_long) > math.maxInt(c_int)) { - try testing.expect(@TypeOf(Macros.L_SUFFIX(@as(c_int, math.maxInt(c_int) - 1))) == c_long); // c_int - } - - if (math.maxInt(c_long) > math.maxInt(c_uint)) { - try testing.expect(@TypeOf(Macros.L_SUFFIX(@as(c_uint, math.maxInt(c_uint) - 1))) == c_long); // c_uint - try testing.expect(@TypeOf(Macros.L_SUFFIX(math.maxInt(c_uint) + 1)) == c_long); // comptime_int -> c_long - } - - if (math.maxInt(c_longlong) > math.maxInt(c_long)) { - try testing.expect(@TypeOf(Macros.L_SUFFIX(@as(c_long, math.maxInt(c_long) - 1))) == c_long); // c_long - try testing.expect(@TypeOf(Macros.L_SUFFIX(math.maxInt(c_long) + 1)) == c_longlong); // comptime_int -> c_longlong - } -} - -// Function with complex signature for testing the SDL case -fn complexFunction(_: ?*anyopaque, _: c_uint, _: ?*const fn (?*anyopaque) callconv(.c) c_uint, _: ?*anyopaque, _: c_uint, _: [*c]c_uint) callconv(.c) usize { - return 0; -} - -test "function pointer casting" { - const SDL_FunctionPointer = ?*const fn () callconv(.c) void; - const fn_ptr = cast(SDL_FunctionPointer, complexFunction); - try testing.expect(fn_ptr != null); -} diff --git a/lib/std/zig/c_translation/builtins.zig b/lib/std/zig/c_translation/builtins.zig new file mode 100644 index 0000000000000000000000000000000000000000..c704b65e23652ec0142e7abd007552f0235b0822 --- /dev/null +++ b/lib/std/zig/c_translation/builtins.zig @@ -0,0 +1,301 @@ +const std = @import("std"); + +/// Standard C Library bug: The absolute value of the most negative integer remains negative. +pub inline fn abs(val: c_int) c_int { + return if (val == std.math.minInt(c_int)) val else @intCast(@abs(val)); +} + +pub inline fn assume(cond: bool) void { + if (!cond) unreachable; +} + +pub inline fn bswap16(val: u16) u16 { + return @byteSwap(val); +} + +pub inline fn bswap32(val: u32) u32 { + return @byteSwap(val); +} + +pub inline fn bswap64(val: u64) u64 { + return @byteSwap(val); +} + +pub inline fn ceilf(val: f32) f32 { + return @ceil(val); +} + +pub inline fn ceil(val: f64) f64 { + return @ceil(val); +} + +/// Returns the number of leading 0-bits in x, starting at the most significant bit position. +/// In C if `val` is 0, the result is undefined; in zig it's the number of bits in a c_uint +pub inline fn clz(val: c_uint) c_int { + @setRuntimeSafety(false); + return @as(c_int, @bitCast(@as(c_uint, @clz(val)))); +} + +pub inline fn constant_p(expr: anytype) c_int { + _ = expr; + return @intFromBool(false); +} + +pub inline fn cosf(val: f32) f32 { + return @cos(val); +} + +pub inline fn cos(val: f64) f64 { + return @cos(val); +} + +/// Returns the number of trailing 0-bits in val, starting at the least significant bit position. +/// In C if `val` is 0, the result is undefined; in zig it's the number of bits in a c_uint +pub inline fn ctz(val: c_uint) c_int { + @setRuntimeSafety(false); + return @as(c_int, @bitCast(@as(c_uint, @ctz(val)))); +} + +pub inline fn exp2f(val: f32) f32 { + return @exp2(val); +} + +pub inline fn exp2(val: f64) f64 { + return @exp2(val); +} + +pub inline fn expf(val: f32) f32 { + return @exp(val); +} + +pub inline fn exp(val: f64) f64 { + return @exp(val); +} + +/// The return value of __builtin_expect is `expr`. `c` is the expected value +/// of `expr` and is used as a hint to the compiler in C. Here it is unused. +pub inline fn expect(expr: c_long, c: c_long) c_long { + _ = c; + return expr; +} + +pub inline fn fabsf(val: f32) f32 { + return @abs(val); +} + +pub inline fn fabs(val: f64) f64 { + return @abs(val); +} + +pub inline fn floorf(val: f32) f32 { + return @floor(val); +} + +pub inline fn floor(val: f64) f64 { + return @floor(val); +} + +pub inline fn has_builtin(func: anytype) c_int { + _ = func; + return @intFromBool(true); +} + +pub inline fn huge_valf() f32 { + return std.math.inf(f32); +} + +pub inline fn inff() f32 { + return std.math.inf(f32); +} + +/// Similar to isinf, except the return value is -1 for an argument of -Inf and 1 for an argument of +Inf. +pub inline fn isinf_sign(x: anytype) c_int { + if (!std.math.isInf(x)) return 0; + return if (std.math.isPositiveInf(x)) 1 else -1; +} + +pub inline fn isinf(x: anytype) c_int { + return @intFromBool(std.math.isInf(x)); +} + +pub inline fn isnan(x: anytype) c_int { + return @intFromBool(std.math.isNan(x)); +} + +/// Standard C Library bug: The absolute value of the most negative integer remains negative. +pub inline fn labs(val: c_long) c_long { + return if (val == std.math.minInt(c_long)) val else @intCast(@abs(val)); +} + +/// Standard C Library bug: The absolute value of the most negative integer remains negative. +pub inline fn llabs(val: c_longlong) c_longlong { + return if (val == std.math.minInt(c_longlong)) val else @intCast(@abs(val)); +} + +pub inline fn log10f(val: f32) f32 { + return @log10(val); +} + +pub inline fn log10(val: f64) f64 { + return @log10(val); +} + +pub inline fn log2f(val: f32) f32 { + return @log2(val); +} + +pub inline fn log2(val: f64) f64 { + return @log2(val); +} + +pub inline fn logf(val: f32) f32 { + return @log(val); +} + +pub inline fn log(val: f64) f64 { + return @log(val); +} + +pub inline fn memcpy_chk( + noalias dst: ?*anyopaque, + noalias src: ?*const anyopaque, + len: usize, + remaining: usize, +) ?*anyopaque { + if (len > remaining) @panic("__builtin___memcpy_chk called with len > remaining"); + if (len > 0) @memcpy( + @as([*]u8, @ptrCast(dst.?))[0..len], + @as([*]const u8, @ptrCast(src.?)), + ); + return dst; +} + +pub inline fn memcpy( + noalias dst: ?*anyopaque, + noalias src: ?*const anyopaque, + len: usize, +) ?*anyopaque { + if (len > 0) @memcpy( + @as([*]u8, @ptrCast(dst.?))[0..len], + @as([*]const u8, @ptrCast(src.?)), + ); + return dst; +} + +pub inline fn memset_chk( + dst: ?*anyopaque, + val: c_int, + len: usize, + remaining: usize, +) ?*anyopaque { + if (len > remaining) @panic("__builtin___memset_chk called with len > remaining"); + const dst_cast = @as([*c]u8, @ptrCast(dst)); + @memset(dst_cast[0..len], @as(u8, @bitCast(@as(i8, @truncate(val))))); + return dst; +} + +pub inline fn memset(dst: ?*anyopaque, val: c_int, len: usize) ?*anyopaque { + const dst_cast = @as([*c]u8, @ptrCast(dst)); + @memset(dst_cast[0..len], @as(u8, @bitCast(@as(i8, @truncate(val))))); + return dst; +} + +pub fn mul_overflow(a: anytype, b: anytype, result: *@TypeOf(a, b)) c_int { + const res = @mulWithOverflow(a, b); + result.* = res[0]; + return res[1]; +} + +/// returns a quiet NaN. Quiet NaNs have many representations; tagp is used to select one in an +/// implementation-defined way. +/// This implementation is based on the description for nan provided in the GCC docs at +/// https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html#index-_005f_005fbuiltin_005fnan +/// Comment is reproduced below: +/// Since ISO C99 defines this function in terms of strtod, which we do not implement, a description +/// of the parsing is in order. +/// The string is parsed as by strtol; that is, the base is recognized by leading ‘0’ or ‘0x’ prefixes. +/// The number parsed is placed in the significand such that the least significant bit of the number is +/// at the least significant bit of the significand. +/// The number is truncated to fit the significand field provided. +/// The significand is forced to be a quiet NaN. +/// +/// If tagp contains any non-numeric characters, the function returns a NaN whose significand is zero. +/// If tagp is empty, the function returns a NaN whose significand is zero. +pub inline fn nanf(tagp: []const u8) f32 { + const parsed = std.fmt.parseUnsigned(c_ulong, tagp, 0) catch 0; + const bits: u23 = @truncate(parsed); // single-precision float trailing significand is 23 bits + return @bitCast(@as(u32, bits) | @as(u32, @bitCast(std.math.nan(f32)))); +} + +pub inline fn object_size(ptr: ?*const anyopaque, ty: c_int) usize { + _ = ptr; + // clang semantics match gcc's: https://gcc.gnu.org/onlinedocs/gcc/Object-Size-Checking.html + // If it is not possible to determine which objects ptr points to at compile time, + // object_size should return (size_t) -1 for type 0 or 1 and (size_t) 0 + // for type 2 or 3. + if (ty == 0 or ty == 1) return @as(usize, @bitCast(-@as(isize, 1))); + if (ty == 2 or ty == 3) return 0; + unreachable; +} + +/// popcount of a c_uint will never exceed the capacity of a c_int +pub inline fn popcount(val: c_uint) c_int { + @setRuntimeSafety(false); + return @as(c_int, @bitCast(@as(c_uint, @popCount(val)))); +} + +pub inline fn roundf(val: f32) f32 { + return @round(val); +} + +pub inline fn round(val: f64) f64 { + return @round(val); +} + +pub inline fn signbitf(val: f32) c_int { + return @intFromBool(std.math.signbit(val)); +} + +pub inline fn signbit(val: f64) c_int { + return @intFromBool(std.math.signbit(val)); +} + +pub inline fn sinf(val: f32) f32 { + return @sin(val); +} + +pub inline fn sin(val: f64) f64 { + return @sin(val); +} + +pub inline fn sqrtf(val: f32) f32 { + return @sqrt(val); +} + +pub inline fn sqrt(val: f64) f64 { + return @sqrt(val); +} + +pub inline fn strcmp(s1: [*c]const u8, s2: [*c]const u8) c_int { + return switch (std.mem.orderZ(u8, s1, s2)) { + .lt => -1, + .eq => 0, + .gt => 1, + }; +} + +pub inline fn strlen(s: [*c]const u8) usize { + return std.mem.sliceTo(s, 0).len; +} + +pub inline fn truncf(val: f32) f32 { + return @trunc(val); +} + +pub inline fn trunc(val: f64) f64 { + return @trunc(val); +} + +pub inline fn @"unreachable"() noreturn { + unreachable; +} diff --git a/lib/std/zig/c_translation/helpers.zig b/lib/std/zig/c_translation/helpers.zig new file mode 100644 index 0000000000000000000000000000000000000000..0b804bf316be80427a53e3cf04b1beabcca056c8 --- /dev/null +++ b/lib/std/zig/c_translation/helpers.zig @@ -0,0 +1,413 @@ +const std = @import("std"); + +/// "Usual arithmetic conversions" from C11 standard 6.3.1.8 +pub fn ArithmeticConversion(comptime A: type, comptime B: type) type { + if (A == c_longdouble or B == c_longdouble) return c_longdouble; + if (A == f80 or B == f80) return f80; + if (A == f64 or B == f64) return f64; + if (A == f32 or B == f32) return f32; + + const A_Promoted = PromotedIntType(A); + const B_Promoted = PromotedIntType(B); + comptime { + std.debug.assert(integerRank(A_Promoted) >= integerRank(c_int)); + std.debug.assert(integerRank(B_Promoted) >= integerRank(c_int)); + } + + if (A_Promoted == B_Promoted) return A_Promoted; + + const a_signed = @typeInfo(A_Promoted).int.signedness == .signed; + const b_signed = @typeInfo(B_Promoted).int.signedness == .signed; + + if (a_signed == b_signed) { + return if (integerRank(A_Promoted) > integerRank(B_Promoted)) A_Promoted else B_Promoted; + } + + const SignedType = if (a_signed) A_Promoted else B_Promoted; + const UnsignedType = if (!a_signed) A_Promoted else B_Promoted; + + if (integerRank(UnsignedType) >= integerRank(SignedType)) return UnsignedType; + + if (std.math.maxInt(SignedType) >= std.math.maxInt(UnsignedType)) return SignedType; + + return ToUnsigned(SignedType); +} + +/// Integer promotion described in C11 6.3.1.1.2 +fn PromotedIntType(comptime T: type) type { + return switch (T) { + bool, c_short => c_int, + c_ushort => if (@sizeOf(c_ushort) == @sizeOf(c_int)) c_uint else c_int, + c_int, c_uint, c_long, c_ulong, c_longlong, c_ulonglong => T, + else => switch (@typeInfo(T)) { + .comptime_int => @compileError("Cannot promote `" ++ @typeName(T) ++ "`; a fixed-size number type is required"), + // promote to c_int if it can represent all values of T + .int => |int_info| if (int_info.bits < @bitSizeOf(c_int)) + c_int + // otherwise, restore the original C type + else if (int_info.bits == @bitSizeOf(c_int)) + if (int_info.signedness == .unsigned) c_uint else c_int + else if (int_info.bits <= @bitSizeOf(c_long)) + if (int_info.signedness == .unsigned) c_ulong else c_long + else if (int_info.bits <= @bitSizeOf(c_longlong)) + if (int_info.signedness == .unsigned) c_ulonglong else c_longlong + else + @compileError("Cannot promote `" ++ @typeName(T) ++ "`; a C ABI type is required"), + else => @compileError("Attempted to promote invalid type `" ++ @typeName(T) ++ "`"), + }, + }; +} + +/// C11 6.3.1.1.1 +fn integerRank(comptime T: type) u8 { + return switch (T) { + bool => 0, + u8, i8 => 1, + c_short, c_ushort => 2, + c_int, c_uint => 3, + c_long, c_ulong => 4, + c_longlong, c_ulonglong => 5, + else => @compileError("integer rank not supported for `" ++ @typeName(T) ++ "`"), + }; +} + +fn ToUnsigned(comptime T: type) type { + return switch (T) { + c_int => c_uint, + c_long => c_ulong, + c_longlong => c_ulonglong, + else => @compileError("Cannot convert `" ++ @typeName(T) ++ "` to unsigned"), + }; +} + +/// Constructs a [*c] pointer with the const and volatile annotations +/// from SelfType for pointing to a C flexible array of ElementType. +pub fn FlexibleArrayType(comptime SelfType: type, comptime ElementType: type) type { + switch (@typeInfo(SelfType)) { + .pointer => |ptr| { + return @Type(.{ .pointer = .{ + .size = .c, + .is_const = ptr.is_const, + .is_volatile = ptr.is_volatile, + .alignment = @alignOf(ElementType), + .address_space = .generic, + .child = ElementType, + .is_allowzero = true, + .sentinel_ptr = null, + } }); + }, + else => |info| @compileError("Invalid self type \"" ++ @tagName(info) ++ "\" for flexible array getter: " ++ @typeName(SelfType)), + } +} + +/// Promote the type of an integer literal until it fits as C would. +pub fn promoteIntLiteral( + comptime SuffixType: type, + comptime number: comptime_int, + comptime base: CIntLiteralBase, +) PromoteIntLiteralReturnType(SuffixType, number, base) { + return number; +} + +const CIntLiteralBase = enum { decimal, octal, hex }; + +fn PromoteIntLiteralReturnType(comptime SuffixType: type, comptime number: comptime_int, comptime base: CIntLiteralBase) type { + const signed_decimal = [_]type{ c_int, c_long, c_longlong, c_ulonglong }; + const signed_oct_hex = [_]type{ c_int, c_uint, c_long, c_ulong, c_longlong, c_ulonglong }; + const unsigned = [_]type{ c_uint, c_ulong, c_ulonglong }; + + const list: []const type = if (@typeInfo(SuffixType).int.signedness == .unsigned) + &unsigned + else if (base == .decimal) + &signed_decimal + else + &signed_oct_hex; + + var pos = std.mem.indexOfScalar(type, list, SuffixType).?; + while (pos < list.len) : (pos += 1) { + if (number >= std.math.minInt(list[pos]) and number <= std.math.maxInt(list[pos])) { + return list[pos]; + } + } + + @compileError("Integer literal is too large"); +} + +/// Convert from clang __builtin_shufflevector index to Zig @shuffle index +/// clang requires __builtin_shufflevector index arguments to be integer constants. +/// negative values for `this_index` indicate "don't care". +/// clang enforces that `this_index` is less than the total number of vector elements +/// See https://ziglang.org/documentation/master/#shuffle +/// See https://clang.llvm.org/docs/LanguageExtensions.html#langext-builtin-shufflevector +pub fn shuffleVectorIndex(comptime this_index: c_int, comptime source_vector_len: usize) i32 { + const positive_index = std.math.cast(usize, this_index) orelse return undefined; + if (positive_index < source_vector_len) return @as(i32, @intCast(this_index)); + const b_index = positive_index - source_vector_len; + return ~@as(i32, @intCast(b_index)); +} + +/// C `%` operator for signed integers +/// C standard states: "If the quotient a/b is representable, the expression (a/b)*b + a%b shall equal a" +/// The quotient is not representable if denominator is zero, or if numerator is the minimum integer for +/// the type and denominator is -1. C has undefined behavior for those two cases; this function has safety +/// checked undefined behavior +pub fn signedRemainder(numerator: anytype, denominator: anytype) @TypeOf(numerator, denominator) { + std.debug.assert(@typeInfo(@TypeOf(numerator, denominator)).int.signedness == .signed); + if (denominator > 0) return @rem(numerator, denominator); + return numerator - @divTrunc(numerator, denominator) * denominator; +} + +/// Given a type and value, cast the value to the type as c would. +pub fn cast(comptime DestType: type, target: anytype) DestType { + // this function should behave like transCCast in translate-c, except it's for macros + const SourceType = @TypeOf(target); + switch (@typeInfo(DestType)) { + .@"fn" => return castToPtr(*const DestType, SourceType, target), + .pointer => return castToPtr(DestType, SourceType, target), + .optional => |dest_opt| { + if (@typeInfo(dest_opt.child) == .pointer) { + return castToPtr(DestType, SourceType, target); + } else if (@typeInfo(dest_opt.child) == .@"fn") { + return castToPtr(?*const dest_opt.child, SourceType, target); + } + }, + .int => { + switch (@typeInfo(SourceType)) { + .pointer => { + return castInt(DestType, @intFromPtr(target)); + }, + .optional => |opt| { + if (@typeInfo(opt.child) == .pointer) { + return castInt(DestType, @intFromPtr(target)); + } + }, + .int => { + return castInt(DestType, target); + }, + .@"fn" => { + return castInt(DestType, @intFromPtr(&target)); + }, + .bool => { + return @intFromBool(target); + }, + else => {}, + } + }, + .float => { + switch (@typeInfo(SourceType)) { + .int => return @as(DestType, @floatFromInt(target)), + .float => return @as(DestType, @floatCast(target)), + .bool => return @as(DestType, @floatFromInt(@intFromBool(target))), + else => {}, + } + }, + .@"union" => |info| { + inline for (info.fields) |field| { + if (field.type == SourceType) return @unionInit(DestType, field.name, target); + } + + @compileError("cast to union type '" ++ @typeName(DestType) ++ "' from type '" ++ @typeName(SourceType) ++ "' which is not present in union"); + }, + .bool => return cast(usize, target) != 0, + else => {}, + } + + return @as(DestType, target); +} + +fn castInt(comptime DestType: type, target: anytype) DestType { + const dest = @typeInfo(DestType).int; + const source = @typeInfo(@TypeOf(target)).int; + + const Int = @Type(.{ .int = .{ .bits = dest.bits, .signedness = source.signedness } }); + + if (dest.bits < source.bits) + return @as(DestType, @bitCast(@as(Int, @truncate(target)))) + else + return @as(DestType, @bitCast(@as(Int, target))); +} + +fn castPtr(comptime DestType: type, target: anytype) DestType { + return @constCast(@volatileCast(@alignCast(@ptrCast(target)))); +} + +fn castToPtr(comptime DestType: type, comptime SourceType: type, target: anytype) DestType { + switch (@typeInfo(SourceType)) { + .int => { + return @as(DestType, @ptrFromInt(castInt(usize, target))); + }, + .comptime_int => { + if (target < 0) + return @as(DestType, @ptrFromInt(@as(usize, @bitCast(@as(isize, @intCast(target)))))) + else + return @as(DestType, @ptrFromInt(@as(usize, @intCast(target)))); + }, + .pointer => { + return castPtr(DestType, target); + }, + .@"fn" => { + return castPtr(DestType, &target); + }, + .optional => |target_opt| { + if (@typeInfo(target_opt.child) == .pointer) { + return castPtr(DestType, target); + } + }, + else => {}, + } + + return @as(DestType, target); +} + +/// Given a value returns its size as C's sizeof operator would. +pub fn sizeof(target: anytype) usize { + const T: type = if (@TypeOf(target) == type) target else @TypeOf(target); + switch (@typeInfo(T)) { + .float, .int, .@"struct", .@"union", .array, .bool, .vector => return @sizeOf(T), + .@"fn" => { + // sizeof(main) in C returns 1 + return 1; + }, + .null => return @sizeOf(*anyopaque), + .void => { + // Note: sizeof(void) is 1 on clang/gcc and 0 on MSVC. + return 1; + }, + .@"opaque" => { + if (T == anyopaque) { + // Note: sizeof(void) is 1 on clang/gcc and 0 on MSVC. + return 1; + } else { + @compileError("Cannot use C sizeof on opaque type " ++ @typeName(T)); + } + }, + .optional => |opt| { + if (@typeInfo(opt.child) == .pointer) { + return sizeof(opt.child); + } else { + @compileError("Cannot use C sizeof on non-pointer optional " ++ @typeName(T)); + } + }, + .pointer => |ptr| { + if (ptr.size == .slice) { + @compileError("Cannot use C sizeof on slice type " ++ @typeName(T)); + } + + // for strings, sizeof("a") returns 2. + // normal pointer decay scenarios from C are handled + // in the .array case above, but strings remain literals + // and are therefore always pointers, so they need to be + // specially handled here. + if (ptr.size == .one and ptr.is_const and @typeInfo(ptr.child) == .array) { + const array_info = @typeInfo(ptr.child).array; + if ((array_info.child == u8 or array_info.child == u16) and array_info.sentinel() == 0) { + // length of the string plus one for the null terminator. + return (array_info.len + 1) * @sizeOf(array_info.child); + } + } + + // When zero sized pointers are removed, this case will no + // longer be reachable and can be deleted. + if (@sizeOf(T) == 0) { + return @sizeOf(*anyopaque); + } + + return @sizeOf(T); + }, + .comptime_float => return @sizeOf(f64), // TODO c_double #3999 + .comptime_int => { + // TODO to get the correct result we have to translate + // `1073741824 * 4` as `int(1073741824) *% int(4)` since + // sizeof(1073741824 * 4) != sizeof(4294967296). + + // TODO test if target fits in int, long or long long + return @sizeOf(c_int); + }, + else => @compileError("__helpers.sizeof does not support type " ++ @typeName(T)), + } +} + +pub fn div(a: anytype, b: anytype) ArithmeticConversion(@TypeOf(a), @TypeOf(b)) { + const ResType = ArithmeticConversion(@TypeOf(a), @TypeOf(b)); + const a_casted = cast(ResType, a); + const b_casted = cast(ResType, b); + switch (@typeInfo(ResType)) { + .float => return a_casted / b_casted, + .int => return @divTrunc(a_casted, b_casted), + else => unreachable, + } +} + +pub fn rem(a: anytype, b: anytype) ArithmeticConversion(@TypeOf(a), @TypeOf(b)) { + const ResType = ArithmeticConversion(@TypeOf(a), @TypeOf(b)); + const a_casted = cast(ResType, a); + const b_casted = cast(ResType, b); + switch (@typeInfo(ResType)) { + .int => { + if (@typeInfo(ResType).int.signedness == .signed) { + return signedRemainder(a_casted, b_casted); + } else { + return a_casted % b_casted; + } + }, + else => unreachable, + } +} + +/// A 2-argument function-like macro defined as #define FOO(A, B) (A)(B) +/// could be either: cast B to A, or call A with the value B. +pub fn CAST_OR_CALL(a: anytype, b: anytype) switch (@typeInfo(@TypeOf(a))) { + .type => a, + .@"fn" => |fn_info| fn_info.return_type orelse void, + else => |info| @compileError("Unexpected argument type: " ++ @tagName(info)), +} { + switch (@typeInfo(@TypeOf(a))) { + .type => return cast(a, b), + .@"fn" => return a(b), + else => unreachable, // return type will be a compile error otherwise + } +} + +pub inline fn DISCARD(x: anytype) void { + _ = x; +} + +pub fn F_SUFFIX(comptime f: comptime_float) f32 { + return @as(f32, f); +} + +fn L_SUFFIX_ReturnType(comptime number: anytype) type { + switch (@typeInfo(@TypeOf(number))) { + .int, .comptime_int => return @TypeOf(promoteIntLiteral(c_long, number, .decimal)), + .float, .comptime_float => return c_longdouble, + else => @compileError("Invalid value for L suffix"), + } +} + +pub fn L_SUFFIX(comptime number: anytype) L_SUFFIX_ReturnType(number) { + switch (@typeInfo(@TypeOf(number))) { + .int, .comptime_int => return promoteIntLiteral(c_long, number, .decimal), + .float, .comptime_float => @compileError("TODO: c_longdouble initialization from comptime_float not supported"), + else => @compileError("Invalid value for L suffix"), + } +} + +pub fn LL_SUFFIX(comptime n: comptime_int) @TypeOf(promoteIntLiteral(c_longlong, n, .decimal)) { + return promoteIntLiteral(c_longlong, n, .decimal); +} + +pub fn U_SUFFIX(comptime n: comptime_int) @TypeOf(promoteIntLiteral(c_uint, n, .decimal)) { + return promoteIntLiteral(c_uint, n, .decimal); +} + +pub fn UL_SUFFIX(comptime n: comptime_int) @TypeOf(promoteIntLiteral(c_ulong, n, .decimal)) { + return promoteIntLiteral(c_ulong, n, .decimal); +} + +pub fn ULL_SUFFIX(comptime n: comptime_int) @TypeOf(promoteIntLiteral(c_ulonglong, n, .decimal)) { + return promoteIntLiteral(c_ulonglong, n, .decimal); +} + +pub fn WL_CONTAINER_OF(ptr: anytype, sample: anytype, comptime member: []const u8) @TypeOf(sample) { + return @fieldParentPtr(member, ptr); +}