diff --git a/lib/compiler/aro_translate_c/ast.zig b/lib/compiler/aro_translate_c/ast.zig index 853fcb748cb5..c46e2e16b2e1 100644 --- a/lib/compiler/aro_translate_c/ast.zig +++ b/lib/compiler/aro_translate_c/ast.zig @@ -63,6 +63,7 @@ pub const Node = extern union { warning, @"struct", @"union", + @"enum", @"comptime", @"defer", array_init, @@ -227,6 +228,8 @@ pub const Node = extern union { /// [1]type{val} ** count array_filler, + labeled_break, + pub const last_no_payload_tag = Tag.@"break"; pub const no_payload_count = @intFromEnum(last_no_payload_tag) + 1; @@ -349,6 +352,7 @@ pub const Node = extern union { .type, .helpers_macro, .import_c_builtin, + .labeled_break, => Payload.Value, .discard => Payload.Discard, .@"if" => Payload.If, @@ -359,6 +363,7 @@ pub const Node = extern union { .var_decl => Payload.VarDecl, .func => Payload.Func, .@"struct", .@"union" => Payload.Record, + .@"enum" => Payload.Enum, .tuple => Payload.TupleInit, .container_init => Payload.ContainerInit, .container_init_dot => Payload.ContainerInitDot, @@ -581,6 +586,11 @@ pub const Payload = struct { }; }; + pub const Enum = struct { + base: Payload, + data: []const []const u8, + }; + pub const TupleInit = struct { base: Payload, data: []Node, @@ -1965,6 +1975,40 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex { return renderFieldAccess(c, lhs, payload.field_name); }, .@"struct", .@"union" => return renderRecord(c, node), + .@"enum" => { + const payload = node.castTag(.@"enum").?.data; + + const enum_tok = try c.addToken(.keyword_enum, "enum"); + _ = try c.addToken(.l_brace, "{"); + const members = try c.gpa.alloc(NodeIndex, payload.len); + defer c.gpa.free(members); + + for (payload, members) |field, *member| { + const name_tok = try c.addIdentifier(field); + + member.* = try c.addNode(.{ + .tag = .container_field_init, + .main_token = name_tok, + .data = .{ + .lhs = 0, + .rhs = 0, + }, + }); + _ = try c.addToken(.comma, ","); + } + + _ = try c.addToken(.r_brace, "}"); + + const span = try c.listToSpan(members); + return c.addNode(.{ + .tag = .container_decl_trailing, + .main_token = enum_tok, + .data = .{ + .lhs = span.start, + .rhs = span.end, + }, + }); + }, .enum_constant => { const payload = node.castTag(.enum_constant).?.data; @@ -2113,6 +2157,20 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex { }, }; }, + .labeled_break => { + const payload = node.castTag(.labeled_break).?.data; + const tok = try c.addToken(.keyword_break, "break"); + _ = try c.addToken(.colon, ":"); + const break_label = try c.addIdentifier(payload); + return c.addNode(.{ + .tag = .@"break", + .main_token = tok, + .data = .{ + .lhs = break_label, + .rhs = 0, + }, + }); + }, .@"anytype" => unreachable, // Handled in renderParams } } @@ -2458,6 +2516,7 @@ fn renderNodeGrouped(c: *Context, node: Node) !NodeIndex { .@"if", .@"struct", .@"union", + .@"enum", .array_init, .vector_zero_init, .tuple, @@ -2513,6 +2572,7 @@ fn renderNodeGrouped(c: *Context, node: Node) !NodeIndex { .assign, .helpers_macro, .import_c_builtin, + .labeled_break, => { // these should never appear in places where grouping might be needed. unreachable; diff --git a/src/clang.zig b/src/clang.zig index 04c142b3e338..7a2a2d8725dc 100644 --- a/src/clang.zig +++ b/src/clang.zig @@ -612,6 +612,11 @@ pub const GenericSelectionExpr = opaque { extern fn ZigClangGenericSelectionExpr_getResultExpr(*const GenericSelectionExpr) *const Expr; }; +pub const GotoStmt = opaque { + pub const getLabel = ZigClangGotoStmt_getLabel; + extern fn ZigClangGotoStmt_getLabel(*const GotoStmt) *NamedDecl; +}; + pub const IfStmt = opaque { pub const getThen = ZigClangIfStmt_getThen; extern fn ZigClangIfStmt_getThen(*const IfStmt) *const Stmt; @@ -650,6 +655,14 @@ pub const IntegerLiteral = opaque { extern fn ZigClangIntegerLiteral_getSignum(*const IntegerLiteral, *c_int, *const ASTContext) bool; }; +pub const LabelStmt = opaque { + pub const getName = ZigClangLabelStmt_getName; + extern fn ZigClangLabelStmt_getName(*const LabelStmt) [*:0]const u8; + + pub const getSubStmt = ZigClangLabelStmt_getSubStmt; + extern fn ZigClangLabelStmt_getSubStmt(*const LabelStmt) *const Stmt; +}; + /// This is just used as a namespace for a static method on clang's Lexer class; we don't directly /// deal with Lexer objects pub const Lexer = struct { diff --git a/src/translate_c.zig b/src/translate_c.zig index 14a0def274d9..be04986f92b7 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -56,6 +56,9 @@ pub const Context = struct { pattern_list: PatternList, + /// only non-null when evaluation function bodys + goto: ?GotoContext = null, + fn getMangle(c: *Context) u32 { c.mangle_count += 1; return c.mangle_count; @@ -487,6 +490,35 @@ fn visitFnDecl(c: *Context, fn_decl: *const clang.FunctionDecl) Error!void { param_id += 1; } + c.goto = createGotoContext(c, body_stmt, &block_scope) catch |err| switch (err) { + error.OutOfMemory => |e| return e, + error.UnsupportedTranslation, + error.UnsupportedType, + => { + proto_node.data.is_extern = true; + proto_node.data.is_export = false; + proto_node.data.is_inline = false; + try warn(c, &c.global_scope.base, fn_decl_loc, "unable to translate function, demoted to extern", .{}); + return addTopLevelDecl(c, fn_name, Node.initPayload(&proto_node.base)); + }, + }; + defer c.goto = null; + + for (c.goto.?.label_variable_names.values()) |variable| { + try block_scope.statements.append(try Tag.var_decl.create(c.arena, .{ + .is_pub = false, + .is_const = false, + .is_extern = false, + .is_export = false, + .is_threadlocal = false, + .linksection_string = null, + .alignment = null, + .name = variable, + .type = try Tag.type.create(c.arena, "bool"), + .init = Tag.false_literal.init(), + })); + } + const casted_body = @as(*const clang.CompoundStmt, @ptrCast(body_stmt)); transCompoundStmtInline(c, casted_body, &block_scope) catch |err| switch (err) { error.OutOfMemory => |e| return e, @@ -1147,7 +1179,7 @@ fn transStmt( .BinaryOperatorClass => return transBinaryOperator(c, scope, @as(*const clang.BinaryOperator, @ptrCast(stmt)), result_used), .CompoundStmtClass => return transCompoundStmt(c, scope, @as(*const clang.CompoundStmt, @ptrCast(stmt))), .CStyleCastExprClass => return transCStyleCastExprClass(c, scope, @as(*const clang.CStyleCastExpr, @ptrCast(stmt)), result_used), - .DeclStmtClass => return transDeclStmt(c, scope, @as(*const clang.DeclStmt, @ptrCast(stmt))), + .DeclStmtClass => return transDeclStmt(c, scope, @as(*const clang.DeclStmt, @ptrCast(stmt)), false), .DeclRefExprClass => return transDeclRefExpr(c, scope, @as(*const clang.DeclRefExpr, @ptrCast(stmt))), .ImplicitCastExprClass => return transImplicitCastExpr(c, scope, @as(*const clang.ImplicitCastExpr, @ptrCast(stmt)), result_used), .IntegerLiteralClass => return transIntegerLiteral(c, scope, @as(*const clang.IntegerLiteral, @ptrCast(stmt)), result_used, .with_as), @@ -1217,9 +1249,10 @@ fn transStmt( const choose_expr = @as(*const clang.ChooseExpr, @ptrCast(stmt)); return transExpr(c, scope, choose_expr.getChosenSubExpr(), result_used); }, + .GotoStmtClass => return transGotoStmt(c, scope, @ptrCast(stmt)), + .LabelStmtClass => return transLabelStmt(c, scope, @ptrCast(stmt)), // When adding new cases here, see comment for maybeBlockify() .GCCAsmStmtClass, - .GotoStmtClass, .IndirectGotoStmtClass, .AttributedStmtClass, .AddrLabelExprClass, @@ -1228,7 +1261,6 @@ fn transStmt( .UserDefinedLiteralClass, .BuiltinBitCastExprClass, .DesignatedInitExprClass, - .LabelStmtClass, => return fail(c, error.UnsupportedTranslation, stmt.getBeginLoc(), "TODO implement translation of stmt class {s}", .{@tagName(sc)}), else => return fail(c, error.UnsupportedTranslation, stmt.getBeginLoc(), "unsupported stmt class {s}", .{@tagName(sc)}), } @@ -1626,6 +1658,176 @@ fn transCompoundStmtInline( ) TransError!void { var it = stmt.body_begin(); const end_it = stmt.body_end(); + if (c.goto.?.transformations.get(@ptrCast(stmt))) |transformations| { + var first_inner_stmt_after_if_to_variables: std.AutoArrayHashMapUnmanaged(*const clang.Stmt, std.ArrayListUnmanaged([]const u8)) = .{}; + defer first_inner_stmt_after_if_to_variables.deinit(c.gpa); + + var upwards_inner_stmt_break_target_transformations: std.AutoHashMapUnmanaged(*const clang.Stmt, std.ArrayListUnmanaged(*const GotoContext.Transformation)) = .{}; + defer upwards_inner_stmt_break_target_transformations.deinit(c.gpa); + var downwards_inner_stmt_break_target_transformations: std.AutoHashMapUnmanaged(*const clang.Stmt, std.ArrayListUnmanaged(*const GotoContext.Transformation)) = .{}; + defer downwards_inner_stmt_break_target_transformations.deinit(c.gpa); + + var stmt_order: std.AutoHashMapUnmanaged(*const clang.Stmt, usize) = .{}; + defer stmt_order.deinit(c.gpa); + + var ite = it; + while (ite != end_it) : (ite += 1) { + try stmt_order.put(c.gpa, ite[0], @intFromPtr(ite)); + } + + var require_in_loop: std.AutoHashMapUnmanaged(*const clang.Stmt, void) = .{}; + defer require_in_loop.deinit(c.gpa); + + for (transformations) |*transformation| { + if (transformation.inner_stmt != it[0]) { + const variables = try first_inner_stmt_after_if_to_variables.getOrPut(c.gpa, transformation.inner_stmt); + if (!variables.found_existing) { + variables.value_ptr.* = .{}; + } + try variables.value_ptr.append(c.gpa, transformation.variable); + } + + switch (transformation.type) { + .simple => {}, + .break_target => |*break_target| { + if (stmt_order.get(transformation.inner_stmt).? < stmt_order.get(break_target.from).?) { + try require_in_loop.put(c.gpa, transformation.inner_stmt, {}); + try require_in_loop.put(c.gpa, break_target.from, {}); + + const break_target_transformations = try upwards_inner_stmt_break_target_transformations.getOrPut(c.gpa, break_target.from); + if (!break_target_transformations.found_existing) { + break_target_transformations.value_ptr.* = .{}; + } + try break_target_transformations.value_ptr.append(c.gpa, transformation); + } else { + const break_target_transformations = try downwards_inner_stmt_break_target_transformations.getOrPut(c.gpa, break_target.from); + if (!break_target_transformations.found_existing) { + break_target_transformations.value_ptr.* = .{}; + } + try break_target_transformations.value_ptr.append(c.gpa, transformation); + } + }, + } + } + + var while_block: ?Scope.Block = null; + var if_block: ?Scope.Block = null; + + // Either block or &while_block + var control_flow_block: *Scope.Block = block; + + while (first_inner_stmt_after_if_to_variables.count() != 0 or + upwards_inner_stmt_break_target_transformations.count() != 0 or + downwards_inner_stmt_break_target_transformations.count() != 0 or + require_in_loop.count() != 0) : (it += 1) + { + assert(it != end_it); + + const required_in_loop = require_in_loop.remove(it[0]); + + if (required_in_loop and while_block == null and if_block == null) { + while_block = try Scope.Block.init(c, &block.base, true); + control_flow_block = &while_block.?; + } + + if (if_block == null) { + if_block = try Scope.Block.init(c, &control_flow_block.base, false); + } + + var end_if = false; + + if (upwards_inner_stmt_break_target_transformations.getPtr(it[0])) |break_target_transformations| { + defer { + break_target_transformations.deinit(c.gpa); + assert(upwards_inner_stmt_break_target_transformations.remove(it[0])); + } + + for (break_target_transformations.items) |transformation| { + transformation.type.break_target.label.* = while_block.?.label.?; + } + + end_if = require_in_loop.count() == 0; + } + if (downwards_inner_stmt_break_target_transformations.getPtr(it[0])) |break_target_transformations| { + defer { + break_target_transformations.deinit(c.gpa); + assert(downwards_inner_stmt_break_target_transformations.remove(it[0])); + } + + end_if = true; + + var labeled_block = try Scope.Block.init(c, &if_block.?.base, true); + defer labeled_block.deinit(); + + for (break_target_transformations.items) |transformation| { + transformation.type.break_target.label.* = labeled_block.label.?; + } + + try transStmtToScopeAndExeBlock(c, &block.base, &labeled_block, it[0]); + + try if_block.?.statements.append(try labeled_block.complete(c)); + } else { + try transStmtToScopeAndExeBlock(c, &block.base, &if_block.?, it[0]); + } + + if (end_if or (it + 1 != end_it and first_inner_stmt_after_if_to_variables.contains((it + 1)[0]))) { + if (if_block.?.statements.items.len != 0) { + var ncond: ?Node = null; + for (first_inner_stmt_after_if_to_variables.values()) |*variables| { + for (variables.items) |variable| { + const ident = try Tag.identifier.create(c.arena, variable); + ncond = if (ncond) |nc| + try Tag.@"or".create(c.arena, .{ + .lhs = ident, + .rhs = nc, + }) + else + ident; + } + } + + const if_block_node = try if_block.?.complete(c); + + try control_flow_block.statements.append( + if (ncond) |nc| + try Tag.@"if".create( + c.arena, + .{ + .cond = try Tag.not.create(c.arena, nc), + .then = if_block_node, + .@"else" = null, + }, + ) + else + if_block_node, + ); + } + if_block.?.deinit(); + if_block = null; + + if (while_block != null and require_in_loop.count() == 0) { + try while_block.?.statements.append(Tag.@"break".init()); + + try block.statements.append(try Tag.while_true.create( + c.arena, + try while_block.?.complete(c), + )); + + while_block.?.deinit(); + while_block = null; + control_flow_block = block; + } + + if (it + 1 != end_it) { + if (first_inner_stmt_after_if_to_variables.getPtr((it + 1)[0])) |variables| { + variables.deinit(c.gpa); + assert(first_inner_stmt_after_if_to_variables.swapRemove((it + 1)[0])); + } + } + } + } + } + while (it != end_it) : (it += 1) { const result = try transStmt(c, &block.base, it[0], .unused); switch (result.tag()) { @@ -1781,6 +1983,7 @@ fn transDeclStmtOne( scope: *Scope, decl: *const clang.Decl, block_scope: *Scope.Block, + late_init: bool, ) TransError!void { switch (decl.getKind()) { .Var => { @@ -1792,7 +1995,6 @@ fn transDeclStmtOne( return transLocalExternStmt(c, scope, var_decl, block_scope); } - const decl_init = var_decl.getInit(); const loc = decl.getLocation(); const qual_type = var_decl.getTypeSourceInfo_getType(); @@ -1804,7 +2006,13 @@ fn transDeclStmtOne( } const is_static_local = var_decl.isStaticLocal(); - const is_const = qual_type.isConstQualified(); + const is_const = !late_init and qual_type.isConstQualified(); + + const decl_init = if (!late_init or is_static_local) + var_decl.getInit() + else + null; + const type_node = try transQualTypeMaybeInitialized(c, scope, qual_type, decl_init, loc); var init_node = if (decl_init) |expr| @@ -1877,13 +2085,13 @@ fn transDeclStmtOne( } } -fn transDeclStmt(c: *Context, scope: *Scope, stmt: *const clang.DeclStmt) TransError!Node { +fn transDeclStmt(c: *Context, scope: *Scope, stmt: *const clang.DeclStmt, late_init: bool) TransError!Node { const block_scope = try scope.findBlockScope(c); var it = stmt.decl_begin(); const end_it = stmt.decl_end(); while (it != end_it) : (it += 1) { - try transDeclStmtOne(c, scope, it[0], block_scope); + try transDeclStmtOne(c, scope, it[0], block_scope, late_init); } return Tag.declaration.init(); } @@ -2891,18 +3099,81 @@ fn transIfStmt( ) TransError!Node { // if (c) t // if (c) t else e + + const then_stmt = stmt.getThen(); + const else_stmt = stmt.getElse(); + + var if_vars: std.ArrayListUnmanaged([]const u8) = .{}; + defer if_vars.deinit(c.gpa); + var else_vars: std.ArrayListUnmanaged([]const u8) = .{}; + defer else_vars.deinit(c.gpa); + + // while_block_and_vars: non-null if a loop is required + var while_block_and_vars: ?struct { + block: Scope.Block, + vars: std.ArrayListUnmanaged([]const u8) = .{}, + } = null; + defer if (while_block_and_vars) |*w| { + w.block.deinit(); + w.vars.deinit(c.gpa); + }; + + // Either scope or &while_block_and_vars.?.block.base + var control_flow_scope: *Scope = scope; + + if (c.goto.?.transformations.get(@ptrCast(stmt))) |transformations| { + for (transformations) |*transformation| { + if (transformation.inner_stmt == then_stmt) { + try if_vars.append(c.gpa, transformation.variable); + } else { + try else_vars.append(c.gpa, transformation.variable); + assert(transformation.inner_stmt == else_stmt); + } + + switch (transformation.type) { + .simple => {}, + .break_target => |*bt| { + if (while_block_and_vars == null) { + while_block_and_vars = .{ + .block = try Scope.Block.init(c, scope, true), + }; + control_flow_scope = &while_block_and_vars.?.block.base; + } + + bt.label.* = while_block_and_vars.?.block.label.?; + + try while_block_and_vars.?.vars.append(c.gpa, transformation.variable); + + assert(bt.from == if (transformation.inner_stmt == then_stmt) else_stmt else then_stmt); + }, + } + } + } + var cond_scope = Scope.Condition{ .base = .{ - .parent = scope, + .parent = control_flow_scope, .id = .condition, }, }; defer cond_scope.deinit(); const cond_expr = @as(*const clang.Expr, @ptrCast(stmt.getCond())); - const cond = try transBoolExpr(c, &cond_scope.base, cond_expr, .used); + var cond = try transBoolExpr(c, &cond_scope.base, cond_expr, .used); + + for (if_vars.items) |variable| { + cond = try Tag.@"or".create(c.arena, .{ + .lhs = try Tag.identifier.create(c.arena, variable), + .rhs = cond, + }); + } + + for (else_vars.items) |variable| { + cond = try Tag.@"and".create(c.arena, .{ + .lhs = try Tag.not.create(c.arena, try Tag.identifier.create(c.arena, variable)), + .rhs = cond, + }); + } - const then_stmt = stmt.getThen(); - const else_stmt = stmt.getElse(); const then_class = then_stmt.getStmtClass(); // block needed to keep else statement from attaching to inner while const must_blockify = (else_stmt != null) and switch (then_class) { @@ -2911,15 +3182,37 @@ fn transIfStmt( }; const then_body = if (must_blockify) - try blockify(c, scope, then_stmt) + try blockify(c, control_flow_scope, then_stmt) else - try maybeBlockify(c, scope, then_stmt); + try maybeBlockify(c, control_flow_scope, then_stmt); const else_body = if (else_stmt) |expr| - try maybeBlockify(c, scope, expr) + try maybeBlockify(c, control_flow_scope, expr) else null; - return Tag.@"if".create(c.arena, .{ .cond = cond, .then = then_body, .@"else" = else_body }); + + const if_node = try Tag.@"if".create(c.arena, .{ .cond = cond, .then = then_body, .@"else" = else_body }); + + if (while_block_and_vars) |*w| { + try w.block.statements.append(if_node); + + var ncond: ?Node = null; + for (w.vars.items) |variable| { + const ident = try Tag.identifier.create(c.arena, variable); + ncond = if (ncond) |nc| + try Tag.@"or".create(c.arena, .{ + .lhs = ident, + .rhs = nc, + }) + else + ident; + } + try w.block.statements.append(try Tag.if_not_break.create(c.arena, ncond.?)); + + return try Tag.while_true.create(c.arena, try w.block.complete(c)); + } else { + return if_node; + } } fn transWhileLoop( @@ -2935,7 +3228,16 @@ fn transWhileLoop( }; defer cond_scope.deinit(); const cond_expr = @as(*const clang.Expr, @ptrCast(stmt.getCond())); - const cond = try transBoolExpr(c, &cond_scope.base, cond_expr, .used); + var cond = try transBoolExpr(c, &cond_scope.base, cond_expr, .used); + + if (c.goto.?.transformations.get(@ptrCast(stmt))) |transformations| { + for (transformations) |*transformation| { + cond = try Tag.@"or".create(c.arena, .{ + .lhs = try Tag.identifier.create(c.arena, transformation.variable), + .rhs = cond, + }); + } + } var loop_scope = Scope{ .parent = scope, @@ -3023,8 +3325,36 @@ fn transForLoop( if (stmt.getInit()) |init| { block_scope = try Scope.Block.init(c, scope, false); loop_scope.parent = &block_scope.?.base; - const init_node = try transStmt(c, &block_scope.?.base, init, .unused); - if (init_node.tag() != .declaration) try block_scope.?.statements.append(init_node); + + if (c.goto.?.transformations.get(@ptrCast(stmt))) |transformations| { + var if_block = try Scope.Block.init(c, &block_scope.?.base, false); + defer if_block.deinit(); + try transStmtToScopeAndExeBlock(c, &block_scope.?.base, &if_block, init); + + var ncond: ?Node = null; + for (transformations) |*transformation| { + const ident = try Tag.identifier.create(c.arena, transformation.variable); + ncond = if (ncond) |nc| + try Tag.@"or".create(c.arena, .{ + .lhs = ident, + .rhs = nc, + }) + else + ident; + } + + try block_scope.?.statements.append(try Tag.@"if".create( + c.arena, + .{ + .cond = try Tag.not.create(c.arena, ncond.?), + .then = try if_block.complete(c), + .@"else" = null, + }, + )); + } else { + const init_node = try transStmt(c, &block_scope.?.base, init, .unused); + if (init_node.tag() != .declaration) try block_scope.?.statements.append(init_node); + } } var cond_scope = Scope.Condition{ .base = .{ @@ -3034,11 +3364,20 @@ fn transForLoop( }; defer cond_scope.deinit(); - const cond = if (stmt.getCond()) |cond| + var cond = if (stmt.getCond()) |cond| try transBoolExpr(c, &cond_scope.base, cond, .used) else Tag.true_literal.init(); + if (c.goto.?.transformations.get(@ptrCast(stmt))) |transformations| { + for (transformations) |*transformation| { + cond = try Tag.@"or".create(c.arena, .{ + .lhs = try Tag.identifier.create(c.arena, transformation.variable), + .rhs = cond, + }); + } + } + const cont_expr = if (stmt.getInc()) |incr| try transExpr(c, &cond_scope.base, incr, .unused) else @@ -3059,15 +3398,60 @@ fn transSwitch( scope: *Scope, stmt: *const clang.SwitchStmt, ) TransError!Node { + var outer_scope = try Scope.Block.init(c, scope, false); + defer outer_scope.deinit(); + var loop_scope = Scope{ - .parent = scope, + .parent = &outer_scope.base, .id = .loop, }; - var block_scope = try Scope.Block.init(c, &loop_scope, false); - defer block_scope.deinit(); + var block_scope: ?Scope.Block = null; + defer if (block_scope) |*bs| bs.deinit(); + + // null if no enum is required + var required_enum: ?struct { + inner_stmt_to_enum_field: std.AutoHashMapUnmanaged(*const clang.Stmt, []const u8) = .{}, + inner_switch_cases: std.ArrayListUnmanaged(Node) = .{}, + enum_fields: std.ArrayListUnmanaged([]const u8) = .{}, + } = null; + defer if (required_enum) |*re| { + re.inner_stmt_to_enum_field.deinit(c.gpa); + re.inner_switch_cases.deinit(c.gpa); + re.enum_fields.deinit(c.gpa); + }; - const base_scope = &block_scope.base; + const transformations = c.goto.?.transformations.get(@ptrCast(stmt)) orelse &.{}; + + for (transformations) |*transformation| { + if (transformation.inner_stmt.getStmtClass() != .CaseStmtClass) { + if (required_enum == null) { + required_enum = .{}; + } + + const field_name = try required_enum.?.inner_stmt_to_enum_field.getOrPut(c.gpa, transformation.inner_stmt); + if (!field_name.found_existing) { + field_name.value_ptr.* = try std.fmt.allocPrint(c.arena, "goto_case_{d}", .{required_enum.?.inner_stmt_to_enum_field.count()}); + } + } + + switch (transformation.type) { + .simple => {}, + .break_target => |*bt| { + if (block_scope == null) { + block_scope = try Scope.Block.init(c, &loop_scope, true); + } + + bt.label.* = block_scope.?.label.?; + }, + } + } + + if (block_scope == null) { + block_scope = try Scope.Block.init(c, &loop_scope, false); + } + + const base_scope = &block_scope.?.base; var cond_scope = Scope.Condition{ .base = .{ @@ -3076,7 +3460,7 @@ fn transSwitch( }, }; defer cond_scope.deinit(); - const switch_expr = try transExpr(c, &cond_scope.base, stmt.getCond(), .used); + var switch_expr = try transExpr(c, &cond_scope.base, stmt.getCond(), .used); var cases = std.ArrayList(Node).init(c.gpa); defer cases.deinit(); @@ -3094,16 +3478,60 @@ fn transSwitch( .CaseStmtClass => { var items = std.ArrayList(Node).init(c.gpa); defer items.deinit(); + + // In the case there is at least one case statement, this case statement + // is used for the goto transformation instead of a default statement. + // Because of this, no check for a goto_case_X enum literal is needed. + assert(required_enum == null or !required_enum.?.inner_stmt_to_enum_field.contains(it[0])); + + const case = if (required_enum) |*re| blk: { + const enum_field = try std.fmt.allocPrint(c.arena, "case_{d}", .{re.enum_fields.items.len}); + try re.enum_fields.append(c.gpa, enum_field); + break :blk try Tag.enum_literal.create(c.arena, enum_field); + } else undefined; + const sub = try transCaseStmt(c, base_scope, it[0], &items); const res = try transSwitchProngStmt(c, base_scope, sub, it, end_it); if (items.items.len == 0) { has_default = true; - const switch_else = try Tag.switch_else.create(c.arena, res); - try cases.append(switch_else); + + if (required_enum) |*re| { + try re.inner_switch_cases.append( + c.gpa, + try Tag.switch_else.create( + c.arena, + case, + ), + ); + + try cases.append( + try Tag.switch_prong.create(c.arena, .{ + .cases = try c.arena.dupe(Node, &.{case}), + .cond = res, + }), + ); + } else { + const switch_else = try Tag.switch_else.create(c.arena, res); + try cases.append(switch_else); + } } else { + var values = try c.arena.dupe(Node, items.items); + + if (required_enum) |*re| { + try re.inner_switch_cases.append( + c.gpa, + try Tag.switch_prong.create(c.arena, .{ + .cases = values, + .cond = case, + }), + ); + + values = try c.arena.dupe(Node, &.{case}); + } + const switch_prong = try Tag.switch_prong.create(c.arena, .{ - .cases = try c.arena.dupe(Node, items.items), + .cases = values, .cond = res, }); try cases.append(switch_prong); @@ -3121,28 +3549,104 @@ fn transSwitch( }; const res = try transSwitchProngStmt(c, base_scope, sub, it, end_it); - - const switch_else = try Tag.switch_else.create(c.arena, res); - try cases.append(switch_else); + if (required_enum) |*re| { + const field_name = re.inner_stmt_to_enum_field.get(it[0]) orelse "default"; + const case = try Tag.enum_literal.create( + c.arena, + field_name, + ); + try re.enum_fields.append(c.gpa, field_name); + try re.inner_switch_cases.append( + c.gpa, + try Tag.switch_else.create(c.arena, case), + ); + + try cases.append( + try Tag.switch_prong.create(c.arena, .{ + .cases = try c.arena.dupe(Node, &.{case}), + .cond = res, + }), + ); + } else { + const switch_else = try Tag.switch_else.create(c.arena, res); + try cases.append(switch_else); + } + }, + .DeclStmtClass => { + _ = try transDeclStmt(c, &outer_scope.base, @ptrCast(it[0]), true); + }, + else => { + if (required_enum) |*re| { + if (re.inner_stmt_to_enum_field.get(it[0])) |field_name| { + const res = try transSwitchProngStmt(c, base_scope, it[0], it, end_it); + + const case = try Tag.enum_literal.create(c.arena, field_name); + try re.enum_fields.append(c.gpa, field_name); + + try cases.append( + try Tag.switch_prong.create(c.arena, .{ + .cases = try c.arena.dupe(Node, &.{case}), + .cond = res, + }), + ); + } + } }, - else => {}, // collected in transSwitchProngStmt } } if (!has_default) { - const else_prong = try Tag.switch_else.create(c.arena, Tag.empty_block.init()); - try cases.append(else_prong); + const else_prong = try Tag.switch_else.create(c.arena, Tag.@"break".init()); + if (required_enum) |*re| { + try re.inner_switch_cases.append(c.gpa, else_prong); + } else { + try cases.append(else_prong); + } + } + + for (transformations) |*transformation| { + if (transformation.inner_stmt.getStmtClass() == .CaseStmtClass) { + switch_expr = try Tag.@"if".create(c.arena, .{ + .cond = try Tag.identifier.create(c.arena, transformation.variable), + .then = try transExprCoercing(c, base_scope, @as(*const clang.CaseStmt, @ptrCast(transformation.inner_stmt)).getLHS(), .used), + .@"else" = switch_expr, + }); + } + } + + if (required_enum) |*re| { + switch_expr = try Tag.@"switch".create(c.arena, .{ + .cond = switch_expr, + .cases = try c.arena.dupe(Node, re.inner_switch_cases.items), + }); + + for (transformations) |*transformation| { + if (transformation.inner_stmt.getStmtClass() != .CaseStmtClass) { + switch_expr = try Tag.@"if".create(c.arena, .{ + .cond = try Tag.identifier.create(c.arena, transformation.variable), + .then = try Tag.enum_literal.create(c.arena, re.inner_stmt_to_enum_field.get(transformation.inner_stmt).?), + .@"else" = switch_expr, + }); + } + } + + switch_expr = try Tag.as.create(c.arena, .{ + .lhs = try Tag.@"enum".create(c.arena, try c.arena.dupe([]const u8, re.enum_fields.items)), + .rhs = switch_expr, + }); } const switch_node = try Tag.@"switch".create(c.arena, .{ .cond = switch_expr, .cases = try c.arena.dupe(Node, cases.items), }); - try block_scope.statements.append(switch_node); - try block_scope.statements.append(Tag.@"break".init()); - const while_body = try block_scope.complete(c); + try block_scope.?.statements.append(switch_node); + try block_scope.?.statements.append(Tag.@"break".init()); + const while_body = try block_scope.?.complete(c); + + try outer_scope.statements.append(try Tag.while_true.create(c.arena, while_body)); - return Tag.while_true.create(c.arena, while_body); + return outer_scope.complete(c); } /// Collects all items for this case, returns the first statement after the labels. @@ -3260,6 +3764,9 @@ fn transSwitchProngStmtInline( return; } }, + .DeclStmtClass => { + try transDeclStmtAsAssignments(c, block, @ptrCast(it[0])); + }, else => { const result = try transStmt(c, &block.base, it[0], .unused); switch (result.tag()) { @@ -4893,6 +5400,136 @@ fn transType(c: *Context, scope: *Scope, ty: *const clang.Type, source_loc: clan } } +fn transGotoStmt(c: *Context, scope: *Scope, stmt: *const clang.GotoStmt) TransError!Node { + var block = try Scope.Block.init(c, scope, false); + defer block.deinit(); + + const variable = c.goto.?.label_variable_names.get(try c.str(stmt.getLabel().getName_bytes_begin())) orelse + return fail( + c, + error.UnsupportedTranslation, + @as(*const clang.Stmt, @ptrCast(stmt)).getBeginLoc(), + "TODO complex goto with label '{s}'", + .{try c.str(stmt.getLabel().getName_bytes_begin())}, + ); + + try block.statements.append(try transCreateNodeInfixOp( + c, + .assign, + try Tag.identifier.create(c.arena, variable), + Tag.true_literal.init(), + .used, + )); + + try block.statements.append(try Tag.labeled_break.create(c.arena, c.goto.?.goto_targets.get(stmt).?.*.?)); + + return try block.complete(c); +} + +fn transLabelStmt(c: *Context, scope: *Scope, stmt: *const clang.LabelStmt) TransError!Node { + var block: ?Scope.Block = null; + var cond: ?Node = null; + defer if (block) |*blk| blk.deinit(); + + const inner_stmt = stmt.getSubStmt(); + + var is_break_target = false; + + if (c.goto.?.transformations.get(@ptrCast(stmt))) |transformations| { + for (transformations) |*transformation| { + const this_cond = try Tag.identifier.create(c.arena, transformation.variable); + + cond = if (cond) |co| + try Tag.@"or".create(c.arena, .{ + .lhs = this_cond, + .rhs = co, + }) + else + this_cond; + + switch (transformation.type) { + .simple => {}, + .break_target => |*bt| { + assert(@as(*const clang.LabelStmt, @ptrCast(transformation.inner_stmt)) == stmt); + assert(bt.from == inner_stmt); + + is_break_target = true; + + if (block == null) { + block = try Scope.Block.init(c, scope, true); + } + + bt.label.* = block.?.label.?; + }, + } + } + } + + if (block == null) { + block = try Scope.Block.init(c, scope, false); + } + + if (c.goto.?.label_variable_names.get(try c.str(stmt.getName()))) |variable| { + try block.?.statements.append(try transCreateNodeInfixOp( + c, + .assign, + try Tag.identifier.create(c.arena, variable), + Tag.false_literal.init(), + .used, + )); + } + + try block.?.statements.append(try transStmt(c, &block.?.base, inner_stmt, .unused)); + + if (is_break_target) { + try block.?.statements.append(try Tag.if_not_break.create(c.arena, cond.?)); + return try Tag.while_true.create(c.arena, try block.?.complete(c)); + } else { + return try block.?.complete(c); + } +} + +// fn transDeclStmt(c: *Context, scope: *Scope, stmt: *const clang.DeclStmt, late_init: bool) TransError!Node { + +fn transDeclStmtAsAssignments(c: *Context, block: *Scope.Block, stmt: *const clang.DeclStmt) TransError!void { + var it = stmt.decl_begin(); + const end_it = stmt.decl_end(); + while (it != end_it) : (it += 1) { + if (it[0].getKind() == .Var) { + const decl: *const clang.VarDecl = @ptrCast(it[0]); + const qual_type = decl.getTypeSourceInfo_getType(); + + if (decl.getStorageClass() != .Extern and !decl.isStaticLocal()) { + if (decl.getInit()) |init| { + const name = try c.str(@as(*const clang.NamedDecl, @ptrCast(decl)).getName_bytes_begin()); + + var rhs_node = try transExprCoercing(c, &block.base, init, .used); + if (!qualTypeIsBoolean(qual_type) and isBoolRes(rhs_node)) { + rhs_node = try Tag.int_from_bool.create(c.arena, rhs_node); + } + try block.statements.append( + try transCreateNodeInfixOp(c, .assign, try Tag.identifier.create(c.arena, block.base.getAlias(name)), rhs_node, .used), + ); + } + } + } + } +} + +fn transStmtToScopeAndExeBlock(c: *Context, scope: *Scope, exe_block: *Scope.Block, stmt: *const clang.Stmt) TransError!void { + if (stmt.getStmtClass() == .DeclStmtClass) { + const decl_stmt: *const clang.DeclStmt = @ptrCast(stmt); + _ = try transDeclStmt(c, scope, decl_stmt, true); + + try transDeclStmtAsAssignments(c, exe_block, @ptrCast(stmt)); + } else { + const result = try transStmt(c, &exe_block.base, stmt, .unused); + if (result.tag() != .empty_block) { + try exe_block.statements.append(result); + } + } +} + fn qualTypeWasDemotedToOpaque(c: *Context, qt: clang.QualType) bool { const ty = qt.getTypePtr(); switch (qt.getTypeClass()) { @@ -6590,3 +7227,488 @@ fn getFnProto(c: *Context, ref: Node) ?*ast.Payload.Func { } return null; } + +fn addMacros(c: *Context) !void { + var it = c.global_scope.macro_table.iterator(); + while (it.next()) |entry| { + if (getFnProto(c, entry.value_ptr.*)) |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. + try addTopLevelDecl(c, entry.key_ptr.*, try transCreateNodeMacroFn(c, entry.key_ptr.*, entry.value_ptr.*, proto_node)); + } else { + try addTopLevelDecl(c, entry.key_ptr.*, entry.value_ptr.*); + } + } +} + +const GotoContext = struct { + /// A transformation which allows execution of a goto without using a goto statement. + /// Every c-label has a variable of type bool. If it's value is true, the execution is + /// between the goto and the c-label. + const Transformation = struct { + variable: []const u8, + inner_stmt: *const clang.Stmt, + type: union(enum) { + simple, + + break_target: struct { + /// zig-label for the `from` statement (initialized late) + label: *?[]const u8, + from: *const clang.Stmt, + }, + }, + }; + + /// the transformations needed for a statement + transformations: std.AutoHashMapUnmanaged(*const clang.Stmt, []Transformation) = .{}, + + /// Maps c-label names to the names of the bool variables + label_variable_names: std.StringArrayHashMapUnmanaged([]const u8) = .{}, + + /// Maps a goto statement to a zig-label (same pointer as transformation.type.break_target.label) + goto_targets: std.AutoArrayHashMapUnmanaged(*const clang.GotoStmt, *?[]const u8) = .{}, +}; + +fn createGotoContext( + c: *Context, + stmt: *const clang.Stmt, + block: *Scope.Block, +) TransError!GotoContext { + var label_branches: std.StringArrayHashMapUnmanaged(std.ArrayListUnmanaged(*const clang.Stmt)) = .{}; + defer { + for (label_branches.values()) |*label_branch| { + label_branch.deinit(c.gpa); + } + label_branches.deinit(c.gpa); + } + + var goto_branches: std.ArrayListUnmanaged(std.ArrayListUnmanaged(*const clang.Stmt)) = .{}; + defer { + for (goto_branches.items) |*goto_branch| { + goto_branch.deinit(c.gpa); + } + goto_branches.deinit(c.gpa); + } + + var transformations: std.AutoArrayHashMapUnmanaged(*const clang.Stmt, std.ArrayListUnmanaged(GotoContext.Transformation)) = .{}; + defer transformations.deinit(c.gpa); + + var goto: GotoContext = .{}; + + try createGotoContextStmt(c, block, stmt, &transformations, &goto.label_variable_names, &goto.goto_targets, &label_branches, &goto_branches); + + if (goto_branches.items.len != 0) { + // This should not be possible to happen. + // https://github.com/llvm/llvm-project/issues/63682 + return fail( + c, + error.UnsupportedTranslation, + goto_branches.items[0].items[0].getBeginLoc(), + "TODO complex goto with label '{s}'", + .{try c.str(@as(*const clang.GotoStmt, @ptrCast(goto_branches.items[0].items[0])).getLabel().getName_bytes_begin())}, + ); + } + + var iter = transformations.iterator(); + + while (iter.next()) |transformation_entry| { + try goto.transformations.put(c.arena, transformation_entry.key_ptr.*, try transformation_entry.value_ptr.toOwnedSlice(c.arena)); + } + return goto; +} + +fn createGotoContextStmt( + c: *Context, + block: *Scope.Block, + stmt: *const clang.Stmt, + transformations: *std.AutoArrayHashMapUnmanaged(*const clang.Stmt, std.ArrayListUnmanaged(GotoContext.Transformation)), + label_variable_names: *std.StringArrayHashMapUnmanaged([]const u8), + goto_targets: *std.AutoArrayHashMapUnmanaged(*const clang.GotoStmt, *?[]const u8), + label_branches: *std.StringArrayHashMapUnmanaged(std.ArrayListUnmanaged(*const clang.Stmt)), + goto_branches: *std.ArrayListUnmanaged(std.ArrayListUnmanaged(*const clang.Stmt)), +) Error!void { + const sc = stmt.getStmtClass(); + switch (sc) { + .CompoundStmtClass => { + const compound_stmt: *const clang.CompoundStmt = @ptrCast(stmt); + + var it = compound_stmt.body_begin(); + const end_it = compound_stmt.body_end(); + + const label_branch_count_before = label_branches.count(); + const goto_branch_count_before = goto_branches.items.len; + if (it + 1 == end_it) { + try createGotoContextStmt( + c, + block, + it[0], + transformations, + label_variable_names, + goto_targets, + label_branches, + goto_branches, + ); + } else if (it != end_it) { + while (it != end_it) : (it += 1) { + try createGotoContextStmt( + c, + block, + it[0], + transformations, + label_variable_names, + goto_targets, + label_branches, + goto_branches, + ); + } + + try createGotoContextCombineStmts( + c, + block, + stmt, + transformations, + label_variable_names, + goto_targets, + label_branches, + goto_branches, + label_branch_count_before, + goto_branch_count_before, + ); + } + try createGotoContextAppendStmtToBranches( + c, + stmt, + label_branches, + goto_branches, + label_branch_count_before, + goto_branch_count_before, + ); + }, + .IfStmtClass => { + const if_stmt: *const clang.IfStmt = @ptrCast(stmt); + + const label_branch_count_before = label_branches.count(); + const goto_branch_count_before = goto_branches.items.len; + + try createGotoContextStmt( + c, + block, + if_stmt.getThen(), + transformations, + label_variable_names, + goto_targets, + label_branches, + goto_branches, + ); + + if (if_stmt.getElse()) |else_stmt| { + try createGotoContextStmt( + c, + block, + else_stmt, + transformations, + label_variable_names, + goto_targets, + label_branches, + goto_branches, + ); + + try createGotoContextCombineStmts( + c, + block, + stmt, + transformations, + label_variable_names, + goto_targets, + label_branches, + goto_branches, + label_branch_count_before, + goto_branch_count_before, + ); + } + + try createGotoContextAppendStmtToBranches( + c, + stmt, + label_branches, + goto_branches, + label_branch_count_before, + goto_branch_count_before, + ); + }, + .WhileStmtClass, .DoStmtClass, .ForStmtClass => { + const inner_stmt: *const clang.Stmt = switch (sc) { + .WhileStmtClass => @as(*const clang.WhileStmt, @ptrCast(stmt)).getBody(), + .DoStmtClass => @as(*const clang.DoStmt, @ptrCast(stmt)).getBody(), + .ForStmtClass => @as(*const clang.ForStmt, @ptrCast(stmt)).getBody(), + else => unreachable, + }; + const label_branch_count_before = label_branches.count(); + const goto_branch_count_before = goto_branches.items.len; + + try createGotoContextStmt( + c, + block, + inner_stmt, + transformations, + label_variable_names, + goto_targets, + label_branches, + goto_branches, + ); + + try createGotoContextAppendStmtToBranches( + c, + stmt, + label_branches, + goto_branches, + label_branch_count_before, + goto_branch_count_before, + ); + }, + .SwitchStmtClass => { + const body = @as(*const clang.SwitchStmt, @ptrCast(stmt)).getBody(); + assert(body.getStmtClass() == .CompoundStmtClass); + const compound_stmt = @as(*const clang.CompoundStmt, @ptrCast(body)); + var it = compound_stmt.body_begin(); + const end_it = compound_stmt.body_end(); + + const label_branch_count_before = label_branches.count(); + const goto_branch_count_before = goto_branches.items.len; + + while (it != end_it) : (it += 1) { + switch (it[0].getStmtClass()) { + .CaseStmtClass, .DefaultStmtClass => { + const inner_label_branch_count_before = label_branches.count(); + const inner_goto_branch_count_before = goto_branches.items.len; + + var sub: *const clang.Stmt = it[0]; + + // a case statement if possible, a default statement otherwise + var case_or_default = sub; + + while (true) switch (sub.getStmtClass()) { + .CaseStmtClass => { + case_or_default = sub; + sub = @as(*const clang.CaseStmt, @ptrCast(sub)).getSubStmt(); + }, + .DefaultStmtClass => sub = @as(*const clang.DefaultStmt, @ptrCast(sub)).getSubStmt(), + else => break, + }; + + try createGotoContextStmt( + c, + block, + sub, + transformations, + label_variable_names, + goto_targets, + label_branches, + goto_branches, + ); + + // transformations of case/default statements get ignored + + try createGotoContextAppendStmtToBranches( + c, + case_or_default, + label_branches, + goto_branches, + inner_label_branch_count_before, + inner_goto_branch_count_before, + ); + }, + else => { + try createGotoContextStmt( + c, + block, + it[0], + transformations, + label_variable_names, + goto_targets, + label_branches, + goto_branches, + ); + }, + } + } + + try createGotoContextCombineStmts( + c, + block, + stmt, + transformations, + label_variable_names, + goto_targets, + label_branches, + goto_branches, + label_branch_count_before, + goto_branch_count_before, + ); + + try createGotoContextAppendStmtToBranches( + c, + stmt, + label_branches, + goto_branches, + label_branch_count_before, + goto_branch_count_before, + ); + }, + .GotoStmtClass => { + var new_goto_branch: *std.ArrayListUnmanaged(*const clang.Stmt) = try goto_branches.addOne(c.gpa); + new_goto_branch.* = .{}; + try new_goto_branch.append(c.gpa, stmt); + }, + .LabelStmtClass => { + const label_stmt: *const clang.LabelStmt = @ptrCast(stmt); + + const label_branch_count_before = label_branches.count(); + const goto_branch_count_before = goto_branches.items.len; + + try createGotoContextStmt( + c, + block, + label_stmt.getSubStmt(), + transformations, + label_variable_names, + goto_targets, + label_branches, + goto_branches, + ); + + const label_str = try c.str(label_stmt.getName()); + const new_label_branch = try label_branches.getOrPut(c.gpa, label_str); + assert(!new_label_branch.found_existing); + new_label_branch.value_ptr.* = .{}; + + // A not simple transformation of a label to itself will have itself as the inner_stmt and label_stmt.getSubStmt() as the from stmt + try new_label_branch.value_ptr.append(c.gpa, stmt); + try createGotoContextCombineStmts( + c, + block, + stmt, + transformations, + label_variable_names, + goto_targets, + label_branches, + goto_branches, + label_branch_count_before, + goto_branch_count_before, + ); + if (new_label_branch.value_ptr.items.len != 0) { + assert(new_label_branch.value_ptr.pop() == stmt); + } + + try createGotoContextAppendStmtToBranches( + c, + stmt, + label_branches, + goto_branches, + label_branch_count_before, + goto_branch_count_before, + ); + }, + else => {}, + } +} + +fn createGotoContextCombineStmts( + c: *Context, + block: *Scope.Block, + stmt: *const clang.Stmt, + transformations: *std.AutoArrayHashMapUnmanaged(*const clang.Stmt, std.ArrayListUnmanaged(GotoContext.Transformation)), + label_variable_names: *std.StringArrayHashMapUnmanaged([]const u8), + goto_targets: *std.AutoArrayHashMapUnmanaged(*const clang.GotoStmt, *?[]const u8), + label_branches: *std.StringArrayHashMapUnmanaged(std.ArrayListUnmanaged(*const clang.Stmt)), + goto_branches: *std.ArrayListUnmanaged(std.ArrayListUnmanaged(*const clang.Stmt)), + label_branch_count_before: usize, + goto_branch_count_before: usize, +) Error!void { + var from_stmt_to_goto_target: std.AutoHashMapUnmanaged(*const clang.Stmt, *?[]const u8) = .{}; + defer from_stmt_to_goto_target.deinit(c.gpa); + + var goto_branch_i: usize = goto_branch_count_before; + while (goto_branch_i < goto_branches.items.len) { + const goto_branch: *std.ArrayListUnmanaged(*const clang.Stmt) = &goto_branches.items[goto_branch_i]; + goto_branch_i += 1; + const goto_stmt: *const clang.GotoStmt = @ptrCast(goto_branch.items[0]); + const label_str = try c.str(goto_stmt.getLabel().getName_bytes_begin()); + if (label_branches.getIndex(label_str)) |label_branch_i| { + if (label_branch_i >= label_branch_count_before) { + const goto_target = try from_stmt_to_goto_target.getOrPut(c.gpa, goto_branch.getLast()); + if (!goto_target.found_existing) { + goto_target.value_ptr.* = try c.arena.create(?[]const u8); + goto_target.value_ptr.*.* = null; + } + + try goto_targets.put(c.arena, goto_stmt, goto_target.value_ptr.*); + + const variable = try label_variable_names.getOrPut(c.arena, label_str); + if (!variable.found_existing) { + const bare_variable = try std.fmt.allocPrint(c.arena, "goto_{s}", .{label_str}); + variable.value_ptr.* = try block.makeMangledName(c, bare_variable); + } + + const label_branch = &label_branches.values()[label_branch_i]; + + assert(label_branch.items.len > 0); + + const loop_transformation: GotoContext.Transformation = .{ + .variable = variable.value_ptr.*, + .inner_stmt = label_branch.getLast(), + .type = .{ .break_target = .{ + .label = goto_target.value_ptr.*, + .from = goto_branch.getLast(), + } }, + }; + + const stmt_transformations = try transformations.getOrPut(c.gpa, stmt); + if (!stmt_transformations.found_existing) { + stmt_transformations.value_ptr.* = .{}; + } + try stmt_transformations.value_ptr.append(c.arena, loop_transformation); + + for (0..(label_branch.items.len - 1)) |i| { + const branch_transformations = try transformations.getOrPut(c.gpa, label_branch.items[i + 1]); + if (!branch_transformations.found_existing) { + branch_transformations.value_ptr.* = .{}; + } + try branch_transformations.value_ptr.append(c.arena, GotoContext.Transformation{ + .variable = variable.value_ptr.*, + .inner_stmt = label_branch.items[i], + .type = .simple, + }); + } + + if (label_branch.items.len > 1) { + label_branch.items[0] = label_branch.pop(); + label_branch.shrinkRetainingCapacity(1); + } + + // remove goto + goto_branch.deinit(c.gpa); + goto_branch_i -= 1; + _ = goto_branches.swapRemove(goto_branch_i); + } + } + } +} + +fn createGotoContextAppendStmtToBranches( + c: *Context, + stmt: *const clang.Stmt, + label_branches: *std.StringArrayHashMapUnmanaged(std.ArrayListUnmanaged(*const clang.Stmt)), + goto_branches: *std.ArrayListUnmanaged(std.ArrayListUnmanaged(*const clang.Stmt)), + label_branch_count_before: usize, + goto_branch_count_before: usize, +) Error!void { + for (label_branches.values()[label_branch_count_before..]) |*label_branch| { + try label_branch.append(c.gpa, stmt); + } + + for (goto_branches.items[goto_branch_count_before..]) |*goto_branch| { + try goto_branch.append(c.gpa, stmt); + } +} diff --git a/src/zig_clang.cpp b/src/zig_clang.cpp index 635bad13747f..5dd9f3d8791d 100644 --- a/src/zig_clang.cpp +++ b/src/zig_clang.cpp @@ -4045,6 +4045,20 @@ const struct ZigClangAPSInt *ZigClangEnumConstantDecl_getInitVal(const struct Zi return reinterpret_cast(result); } +struct ZigClangNamedDecl *ZigClangGotoStmt_getLabel(const struct ZigClangGotoStmt *self) { + auto casted = reinterpret_cast(self); + return reinterpret_cast(casted->getLabel()); +} + +const char *ZigClangLabelStmt_getName(const struct ZigClangLabelStmt *self) { + return reinterpret_cast(self)->getName(); +} + +const struct ZigClangStmt *ZigClangLabelStmt_getSubStmt(const struct ZigClangLabelStmt *self) { + auto casted = reinterpret_cast(self); + return reinterpret_cast(casted->getSubStmt()); +} + // Get a pointer to a static variable in libc++ from LLVM and make sure that // it matches our own. // diff --git a/src/zig_clang.h b/src/zig_clang.h index 6d7ebc64f541..6af4069da502 100644 --- a/src/zig_clang.h +++ b/src/zig_clang.h @@ -173,6 +173,8 @@ struct ZigClangValueDecl; struct ZigClangVarDecl; struct ZigClangWhileStmt; struct ZigClangInitListExpr; +struct ZigClangGotoStmt; +struct ZigClangLabelStmt; typedef struct ZigClangStmt *const * ZigClangCompoundStmt_const_body_iterator; typedef struct ZigClangDecl *const * ZigClangDeclStmt_const_decl_iterator; @@ -1715,4 +1717,10 @@ ZIG_EXTERN_C unsigned ZigClangFieldDecl_getFieldIndex(const struct ZigClangField ZIG_EXTERN_C const struct ZigClangAPSInt *ZigClangEnumConstantDecl_getInitVal(const struct ZigClangEnumConstantDecl *); ZIG_EXTERN_C bool ZigClangIsLLVMUsingSeparateLibcxx(); + +ZIG_EXTERN_C struct ZigClangNamedDecl *ZigClangGotoStmt_getLabel(const struct ZigClangGotoStmt *); + +ZIG_EXTERN_C const char *ZigClangLabelStmt_getName(const struct ZigClangLabelStmt *); +ZIG_EXTERN_C const struct ZigClangStmt *ZigClangLabelStmt_getSubStmt(const struct ZigClangLabelStmt *); + #endif diff --git a/test/cases/run_translated_c/goto_tests.c b/test/cases/run_translated_c/goto_tests.c new file mode 100644 index 000000000000..9e50b8475db8 --- /dev/null +++ b/test/cases/run_translated_c/goto_tests.c @@ -0,0 +1,596 @@ +int downwards_0(void) +{ + goto l; + return 1; +l: + return 0; +} + +int downwards_1(void) +{ + int ret = 0; + goto l; + ret = 1; +l: + return ret; +} + +int upwards(void) +{ + int ret = 1; +l: + if (ret != 1) + { + return ret; + } + + ret--; + goto l; +} + +int two_labels(void) +{ + int ret = 1; + goto l; + return 1; +k: + return ret; +l: + ret = 0; + goto k; +} + +int simple_scope_0(void) +{ + goto l; + return 1; + { + return 1; + l: + return 0; + } + return 1; +} + +int simple_scope_1(void) +{ + { + goto l; + return 1; + } + return 1; +l: + return 0; +} + +int into_if_0(void) +{ + int b = 0; + goto l; + return 1; + if (b) + { + return 1; + l: + return 0; + } + return 1; +} + +int into_if_1(void) +{ + int b = 0; + if (b) + { + return 1; + l: + return 0; + } + goto l; +} + +int out_of_if_0(void) +{ + if (1) + { + goto l; + return 1; + } + return 1; +l: + return 0; +} + +int out_of_if_1(void) +{ + goto l; +k: + return 0; +l: + if (1) + { + goto k; + return 1; + } + return 1; +} + +int in_and_out_of_if(void) +{ + if (1) + { + goto l; + k: + return 0; + } + return 1; +l: + goto k; + return 1; +} + +int many_labels_and_gotos(void) +{ + int i = 1; + int j = 1; +l: + if (i) + { + goto k; + } + if (j) + { + j = 0; + goto k; + } + + return i || j; + +k: + if (j) + { + i = 0; + goto l; + } + goto l; + + return 1; +} + +int into_else(void) +{ + int i = 1; + goto l; + if (i) + return 1; + else + l: + return 0; + return 1; +} + +int if_to_else(void) +{ + int ret = 1; + if (ret = 0, 1) + { + goto l; + return 1; + } + else + { + return 1; + l: + return ret; + } + return 1; +} + +int else_to_if(void) +{ + if (0) + { + return 1; + l: + return 0; + } + else + { + goto l; + return 1; + } + return 1; +} + +int if_cond(void) +{ + int ret = 0; + goto l; + if (ret = 1) + { + return 1; + l: + return ret; + } + return 1; +} + +int else_cond(void) +{ + int ret = 0; + goto l; + if (ret = 1, 0) + { + return 1; + } + else + { + return 1; + l: + return ret; + } + return 1; +} + +int into_while_loop_0(void) +{ + int ret = 0; + goto l; + while (ret = 1, 0) + { + return 1; + l: + return 0; + } + return 1; +} + +int into_while_loop_1(void) +{ + while (0) + { + return 1; + l: + return 0; + } + goto l; + return 1; +} + +int out_of_while_loop_0(void) +{ + while (1) + { + goto l; + return 1; + } + return 1; +l: + return 0; +} + +int out_of_while_loop_1(void) +{ + int i = 1; +l: + while (i) + { + i = 0; + goto l; + return 1; + } + return 0; +} + +int into_for_loop_0(void) +{ + int ret = 0; + goto l; + for (ret = 1; ret = 1, 0; ret = 1) + { + return 1; + l: + return ret; + } + return 1; +} + +int into_for_loop_1(void) +{ + int ret = 2; + for (ret = 2; ret = 1, 0; ret = 2) + { + return 1; + l: + return ret; + } + + if (ret != 1) + { + return 1; + } + ret = 0; + + goto l; + return 1; +} + +int out_of_for_loop_0(void) +{ + int ret0 = 1; + int ret1 = 1; + int ret2 = 0; + for (ret0 = 0; ret1 = 0, 1; ret2 = 1) + { + goto l; + return 1; + } + return 1; +l: + return ret0 || ret1 || ret2; +} + +int out_of_for_loop_1(void) +{ + int i = 1; + int ret0 = 1; + int ret1 = 1; + int ret2 = 0; +l: + for (ret0 = 0; ret1 = 0, i; ret2 = 1) + { + i = 0; + goto l; + return 1; + } + return i == 0 && (ret0 || ret1 || ret2); +} + +int into_switch_case(void) +{ + goto l; + switch (0) + { + return 1; + case 1: + l: + return 0; + default: + return 1; + } + return 1; +} + +int into_switch_case_not_at_the_beginning(void) +{ + int ret = 0; + goto l; + switch (0) + { + return 1; + case 1: + ret = 1; + l: + return ret; + default: + return 1; + } + return 1; +} + +int into_switch_between_cases(void) +{ + int ret = 0; + goto l; + switch (0) + { + return 1; + case 1: + return 1; + l: + return ret; + default: + return 1; + } + return 1; +} + +int into_switch_start(void) +{ + goto l; + switch (0) + { + l: + return 0; + default: + return 1; + } + return 1; +} + +int into_switch_default(void) +{ + goto l; + switch (0) + { + case 0: + return 1; + default: + l: + return 0; + } + return 1; +} + +int switch_from_case_to_case(void) +{ + switch (0) + { + case 0: + goto l; + return 1; + case 1: + l: + return 0; + default: + return 1; + } + return 1; +} + +int switch_from_case_to_same_case(void) +{ + int i = 1; + switch (0) + { + case 0: + l:; + if (i) + { + i = 0; + goto l; + } + return 0; + default: + return 1; + } + return 1; +} + +int switch_from_case_to_case_not_at_the_beginning(void) +{ + int ret = 1; + switch (0) + { + case 0: + ret = 0; + goto l; + return 1; + case 1: + ret = 1; + l: + return ret; + default: + return 1; + } + return 1; +} + +int switch_from_case_to_between_cases(void) +{ + int ret = 1; + switch (0) + { + case 0: + ret = 0; + goto l; + return 1; + case 1: + return 1; + l: + return ret; + default: + return 1; + } + return 1; +} + +int switch_from_case_to_start(void) +{ + int ret = 1; + switch (0) + { + l: + return ret; + case 0: + ret = 0; + goto l; + return 1; + default: + return 1; + } + return 1; +} + +int switch_from_case_to_default(void) +{ + switch (0) + { + case 0: + goto l; + return 1; + default: + l: + return 0; + } + return 1; +} + +int gcd(void) +{ + int a = 48; + int b = 18; + +loop: + if (a == b) + goto finish; + + if (a < b) + goto b_minus_a; + + a = a - b; + goto loop; + +b_minus_a: + b = b - a; + + goto loop; + +finish: + + return a == 6 ? 0 : 1; +} + +int main() +{ + return downwards_0() || + downwards_1() || + upwards() || + two_labels() || + simple_scope_0() || + simple_scope_1() || + into_if_0() || + into_if_1() || + out_of_if_0() || + out_of_if_1() || + in_and_out_of_if() || + many_labels_and_gotos() || + into_else() || + if_to_else() || + else_to_if() || + if_cond() || + else_cond() || + into_while_loop_0() || + into_while_loop_1() || + out_of_while_loop_0() || + out_of_while_loop_1() || + into_for_loop_0() || + into_for_loop_1() || + out_of_for_loop_0() || + out_of_for_loop_1() || + into_switch_case() || + into_switch_case_not_at_the_beginning() || + into_switch_between_cases() || + into_switch_start() || + into_switch_default() || + switch_from_case_to_case() || + switch_from_case_to_same_case() || + switch_from_case_to_case_not_at_the_beginning() || + switch_from_case_to_between_cases() || + switch_from_case_to_start() || + switch_from_case_to_default() || + gcd(); +} + +// run-translated-c +// c_frontend=clang diff --git a/test/cases/translate_c/goto_tests.c b/test/cases/translate_c/goto_tests.c new file mode 100644 index 000000000000..400bec52ae1c --- /dev/null +++ b/test/cases/translate_c/goto_tests.c @@ -0,0 +1,217 @@ +void cleanup1(void); +void cleanup2(void); +void cleanup3(void); + +int doSth(void); +void doSth2(void); +int getState(void); +int getIters(void); + +void foo(void) +{ + + if (doSth() != 0) + { + goto cleanup_3; + } + +loop:; + int state = getState(); + + switch (state) + { + label: + if (doSth() != 0) + goto cleanup_2; + case 0: + if (doSth() != 0) + { + goto cleanup_3; + } + break; + case 1:; + int iters = getIters(); + for (int i = 0; i < iters; i++) + { + label2: + doSth2(); + } + + goto label; + case 2: + iters = 2048; + goto label2; + } + + goto loop; + +a_unused_label: +cleanup_1: + cleanup1(); +cleanup_2: + cleanup2(); +cleanup_3: + cleanup3(); +} +// translate-c +// target=x86_64-linux +// c_frontend=clang +// +// pub export fn foo() void { +// var goto_label: bool = false; +// var goto_label2: bool = false; +// var goto_cleanup_3: bool = false; +// var goto_loop: bool = false; +// var goto_cleanup_2: bool = false; +// if (!(goto_cleanup_2 or (goto_loop or (goto_cleanup_3 or goto_cleanup_3)))) { +// blk: { +// if (doSth() != @as(c_int, 0)) { +// { +// goto_cleanup_3 = true; +// break :blk; +// } +// } +// } +// } +// var state: c_int = undefined; +// _ = &state; +// while (true) blk: { +// if (!(goto_cleanup_2 or (goto_cleanup_3 or goto_cleanup_3))) { +// { +// goto_loop = false; +// {} +// } +// state = getState(); +// blk_1: { +// { +// var iters: c_int = undefined; +// _ = &iters; +// while (true) blk_2: { +// switch (@as(enum { +// goto_case_1, +// case_1, +// case_2, +// goto_case_2, +// case_4, +// }, if (goto_label2) .goto_case_2 else if (goto_label) .goto_case_1 else switch (state) { +// @as(c_int, 0) => .case_1, +// @as(c_int, 1) => .case_2, +// @as(c_int, 2) => .case_4, +// else => break, +// })) { +// .goto_case_1 => { +// { +// goto_label = false; +// if (doSth() != @as(c_int, 0)) { +// { +// goto_cleanup_2 = true; +// break :blk_1; +// } +// } +// } +// if (doSth() != @as(c_int, 0)) { +// { +// goto_cleanup_3 = true; +// break :blk_1; +// } +// } +// break; +// }, +// .case_1 => { +// if (doSth() != @as(c_int, 0)) { +// { +// goto_cleanup_3 = true; +// break :blk_1; +// } +// } +// break; +// }, +// .case_2 => { +// {} +// iters = getIters(); +// { +// var i: c_int = undefined; +// _ = &i; +// if (!goto_label2) { +// i = 0; +// } +// while (goto_label2 or (i < iters)) : (i += 1) { +// { +// goto_label2 = false; +// doSth2(); +// } +// } +// } +// { +// goto_label = true; +// break :blk_2; +// } +// iters = 2048; +// { +// goto_label2 = true; +// break :blk_2; +// } +// }, +// .goto_case_2 => { +// { +// var i: c_int = undefined; +// _ = &i; +// if (!goto_label2) { +// i = 0; +// } +// while (goto_label2 or (i < iters)) : (i += 1) { +// { +// goto_label2 = false; +// doSth2(); +// } +// } +// } +// { +// goto_label = true; +// break :blk_2; +// } +// iters = 2048; +// { +// goto_label2 = true; +// break :blk_2; +// } +// }, +// .case_4 => { +// iters = 2048; +// { +// goto_label2 = true; +// break :blk_2; +// } +// }, +// } +// break; +// } +// } +// } +// } +// if (!(goto_cleanup_2 or (goto_cleanup_3 or goto_cleanup_3))) { +// { +// goto_loop = true; +// break :blk; +// } +// } +// break; +// } +// if (!(goto_cleanup_2 or (goto_cleanup_3 or goto_cleanup_3))) { +// { +// { +// cleanup1(); +// } +// } +// } +// if (!(goto_cleanup_3 or goto_cleanup_3)) { +// { +// goto_cleanup_2 = false; +// cleanup2(); +// } +// } +// { +// goto_cleanup_3 = false; +// cleanup3(); +// } +// } diff --git a/test/translate_c.zig b/test/translate_c.zig index 431289f9e369..4f4eefa139fd 100644 --- a/test/translate_c.zig +++ b/test/translate_c.zig @@ -2024,49 +2024,53 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\ _ = &i; \\ var res: c_int = 0; \\ _ = &res; - \\ while (true) { - \\ switch (i) { - \\ @as(c_int, 0) => { - \\ res = 1; - \\ res = 2; - \\ res = @as(c_int, 3) * i; - \\ break; - \\ }, - \\ @as(c_int, 1)...@as(c_int, 3) => { - \\ res = 2; - \\ res = @as(c_int, 3) * i; - \\ break; - \\ }, - \\ else => { - \\ res = @as(c_int, 3) * i; - \\ break; - \\ }, - \\ @as(c_int, 7) => { - \\ { - \\ res = 7; + \\ { + \\ while (true) { + \\ switch (i) { + \\ @as(c_int, 0) => { + \\ res = 1; + \\ res = 2; + \\ res = @as(c_int, 3) * i; \\ break; - \\ } - \\ }, - \\ @as(c_int, 4), @as(c_int, 5) => { - \\ res = 69; - \\ { - \\ res = 5; - \\ return; - \\ } - \\ }, - \\ @as(c_int, 6) => { - \\ while (true) { - \\ switch (res) { - \\ @as(c_int, 9) => break, - \\ else => {}, - \\ } + \\ }, + \\ @as(c_int, 1)...@as(c_int, 3) => { + \\ res = 2; + \\ res = @as(c_int, 3) * i; + \\ break; + \\ }, + \\ else => { + \\ res = @as(c_int, 3) * i; \\ break; - \\ } - \\ res = 1; - \\ return; - \\ }, + \\ }, + \\ @as(c_int, 7) => { + \\ { + \\ res = 7; + \\ break; + \\ } + \\ }, + \\ @as(c_int, 4), @as(c_int, 5) => { + \\ res = 69; + \\ { + \\ res = 5; + \\ return; + \\ } + \\ }, + \\ @as(c_int, 6) => { + \\ { + \\ while (true) { + \\ switch (res) { + \\ @as(c_int, 9) => break, + \\ else => break, + \\ } + \\ break; + \\ } + \\ } + \\ res = 1; + \\ return; + \\ }, + \\ } + \\ break; \\ } - \\ break; \\ } \\} });