authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-02-01 12:15:52-08:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2021-02-01 12:15:52-08:00
log11f6916f9bd9fbaf81a4bd60fd375eff8db78374
tree6d7fc1c9bd2e8c4082b44059c8cf708e06e96127
parent66c0fe4f90e5767a608efc77dbcecf2a5c8a5173
parent75acfcf0eaa306b3a8872e50cb735e1d5eb18c52
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #7892 from Vexu/stage2-cbe

Stage2 cbe: more control flow

8 files changed, 864 insertions(+), 101 deletions(-)

src/Module.zig+2-2
...@@ -2215,7 +2215,7 @@ pub fn addSwitchBr(...@@ -2215,7 +2215,7 @@ pub fn addSwitchBr(
2215 self: *Module,2215 self: *Module,
2216 block: *Scope.Block,2216 block: *Scope.Block,
2217 src: usize,2217 src: usize,
2218 target_ptr: *Inst,2218 target: *Inst,
2219 cases: []Inst.SwitchBr.Case,2219 cases: []Inst.SwitchBr.Case,
2220 else_body: ir.Body,2220 else_body: ir.Body,
2221) !*Inst {2221) !*Inst {
...@@ -2226,7 +2226,7 @@ pub fn addSwitchBr(...@@ -2226,7 +2226,7 @@ pub fn addSwitchBr(
2226 .ty = Type.initTag(.noreturn),2226 .ty = Type.initTag(.noreturn),
2227 .src = src,2227 .src = src,
2228 },2228 },
2229 .target_ptr = target_ptr,2229 .target = target,
2230 .cases = cases,2230 .cases = cases,
2231 .else_body = else_body,2231 .else_body = else_body,
2232 };2232 };
src/astgen.zig+312-1
...@@ -309,7 +309,7 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr...@@ -309,7 +309,7 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr
309 .Catch => return catchExpr(mod, scope, rl, node.castTag(.Catch).?),309 .Catch => return catchExpr(mod, scope, rl, node.castTag(.Catch).?),
310 .Comptime => return comptimeKeyword(mod, scope, rl, node.castTag(.Comptime).?),310 .Comptime => return comptimeKeyword(mod, scope, rl, node.castTag(.Comptime).?),
311 .OrElse => return orelseExpr(mod, scope, rl, node.castTag(.OrElse).?),311 .OrElse => return orelseExpr(mod, scope, rl, node.castTag(.OrElse).?),
312 .Switch => return mod.failNode(scope, node, "TODO implement astgen.expr for .Switch", .{}),312 .Switch => return switchExpr(mod, scope, rl, node.castTag(.Switch).?),
313 .ContainerDecl => return containerDecl(mod, scope, rl, node.castTag(.ContainerDecl).?),313 .ContainerDecl => return containerDecl(mod, scope, rl, node.castTag(.ContainerDecl).?),
314314
315 .Defer => return mod.failNode(scope, node, "TODO implement astgen.expr for .Defer", .{}),315 .Defer => return mod.failNode(scope, node, "TODO implement astgen.expr for .Defer", .{}),
...@@ -2246,6 +2246,317 @@ fn forExpr(...@@ -2246,6 +2246,317 @@ fn forExpr(
2246 );2246 );
2247}2247}
22482248
2249fn switchCaseUsesRef(node: *ast.Node.Switch) bool {
2250 for (node.cases()) |uncasted_case| {
2251 const case = uncasted_case.castTag(.SwitchCase).?;
2252 const uncasted_payload = case.payload orelse continue;
2253 const payload = uncasted_payload.castTag(.PointerPayload).?;
2254 if (payload.ptr_token) |_| return true;
2255 }
2256 return false;
2257}
2258
2259fn getRangeNode(node: *ast.Node) ?*ast.Node.SimpleInfixOp {
2260 var cur = node;
2261 while (true) {
2262 switch (cur.tag) {
2263 .Range => return @fieldParentPtr(ast.Node.SimpleInfixOp, "base", cur),
2264 .GroupedExpression => cur = @fieldParentPtr(ast.Node.GroupedExpression, "base", cur).expr,
2265 else => return null,
2266 }
2267 }
2268}
2269
2270fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node.Switch) InnerError!*zir.Inst {
2271 const tree = scope.tree();
2272 const switch_src = tree.token_locs[switch_node.switch_token].start;
2273 const use_ref = switchCaseUsesRef(switch_node);
2274
2275 var block_scope: Scope.GenZIR = .{
2276 .parent = scope,
2277 .decl = scope.ownerDecl().?,
2278 .arena = scope.arena(),
2279 .force_comptime = scope.isComptime(),
2280 .instructions = .{},
2281 };
2282 setBlockResultLoc(&block_scope, rl);
2283 defer block_scope.instructions.deinit(mod.gpa);
2284
2285 var items = std.ArrayList(*zir.Inst).init(mod.gpa);
2286 defer items.deinit();
2287
2288 // first we gather all the switch items and check else/'_' prongs
2289 var else_src: ?usize = null;
2290 var underscore_src: ?usize = null;
2291 var first_range: ?*zir.Inst = null;
2292 var simple_case_count: usize = 0;
2293 for (switch_node.cases()) |uncasted_case| {
2294 const case = uncasted_case.castTag(.SwitchCase).?;
2295 const case_src = tree.token_locs[case.firstToken()].start;
2296 assert(case.items_len != 0);
2297
2298 // Check for else/_ prong, those are handled last.
2299 if (case.items_len == 1 and case.items()[0].tag == .SwitchElse) {
2300 if (else_src) |src| {
2301 const msg = msg: {
2302 const msg = try mod.errMsg(
2303 scope,
2304 case_src,
2305 "multiple else prongs in switch expression",
2306 .{},
2307 );
2308 errdefer msg.destroy(mod.gpa);
2309 try mod.errNote(scope, src, msg, "previous else prong is here", .{});
2310 break :msg msg;
2311 };
2312 return mod.failWithOwnedErrorMsg(scope, msg);
2313 }
2314 else_src = case_src;
2315 continue;
2316 } else if (case.items_len == 1 and case.items()[0].tag == .Identifier and
2317 mem.eql(u8, tree.tokenSlice(case.items()[0].firstToken()), "_"))
2318 {
2319 if (underscore_src) |src| {
2320 const msg = msg: {
2321 const msg = try mod.errMsg(
2322 scope,
2323 case_src,
2324 "multiple '_' prongs in switch expression",
2325 .{},
2326 );
2327 errdefer msg.destroy(mod.gpa);
2328 try mod.errNote(scope, src, msg, "previous '_' prong is here", .{});
2329 break :msg msg;
2330 };
2331 return mod.failWithOwnedErrorMsg(scope, msg);
2332 }
2333 underscore_src = case_src;
2334 continue;
2335 }
2336
2337 if (else_src) |some_else| {
2338 if (underscore_src) |some_underscore| {
2339 const msg = msg: {
2340 const msg = try mod.errMsg(
2341 scope,
2342 switch_src,
2343 "else and '_' prong in switch expression",
2344 .{},
2345 );
2346 errdefer msg.destroy(mod.gpa);
2347 try mod.errNote(scope, some_else, msg, "else prong is here", .{});
2348 try mod.errNote(scope, some_underscore, msg, "'_' prong is here", .{});
2349 break :msg msg;
2350 };
2351 return mod.failWithOwnedErrorMsg(scope, msg);
2352 }
2353 }
2354
2355 if (case.items_len == 1 and getRangeNode(case.items()[0]) == null) simple_case_count += 1;
2356
2357 // generate all the switch items as comptime expressions
2358 for (case.items()) |item| {
2359 if (getRangeNode(item)) |range| {
2360 const start = try comptimeExpr(mod, &block_scope.base, .none, range.lhs);
2361 const end = try comptimeExpr(mod, &block_scope.base, .none, range.rhs);
2362 const range_src = tree.token_locs[range.op_token].start;
2363 const range_inst = try addZIRBinOp(mod, &block_scope.base, range_src, .switch_range, start, end);
2364 try items.append(range_inst);
2365 } else {
2366 const item_inst = try comptimeExpr(mod, &block_scope.base, .none, item);
2367 try items.append(item_inst);
2368 }
2369 }
2370 }
2371
2372 var special_prong: zir.Inst.SwitchBr.SpecialProng = .none;
2373 if (else_src != null) special_prong = .@"else";
2374 if (underscore_src != null) special_prong = .underscore;
2375 var cases = try block_scope.arena.alloc(zir.Inst.SwitchBr.Case, simple_case_count);
2376
2377 const target_ptr = if (use_ref) try expr(mod, &block_scope.base, .ref, switch_node.expr) else null;
2378 const target = if (target_ptr) |some|
2379 try addZIRUnOp(mod, &block_scope.base, some.src, .deref, some)
2380 else
2381 try expr(mod, &block_scope.base, .none, switch_node.expr);
2382 const switch_inst = try addZIRInst(mod, &block_scope.base, switch_src, zir.Inst.SwitchBr, .{
2383 .target = target,
2384 .cases = cases,
2385 .items = try block_scope.arena.dupe(*zir.Inst, items.items),
2386 .else_body = undefined, // populated below
2387 }, .{
2388 .range = first_range,
2389 .special_prong = special_prong,
2390 });
2391
2392 const block = try addZIRInstBlock(mod, scope, switch_src, .block, .{
2393 .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items),
2394 });
2395
2396 var case_scope: Scope.GenZIR = .{
2397 .parent = scope,
2398 .decl = block_scope.decl,
2399 .arena = block_scope.arena,
2400 .force_comptime = block_scope.force_comptime,
2401 .instructions = .{},
2402 };
2403 defer case_scope.instructions.deinit(mod.gpa);
2404
2405 var else_scope: Scope.GenZIR = .{
2406 .parent = scope,
2407 .decl = case_scope.decl,
2408 .arena = case_scope.arena,
2409 .force_comptime = case_scope.force_comptime,
2410 .instructions = .{},
2411 };
2412 defer else_scope.instructions.deinit(mod.gpa);
2413
2414 // Now generate all but the special cases
2415 var special_case: ?*ast.Node.SwitchCase = null;
2416 var items_index: usize = 0;
2417 var case_index: usize = 0;
2418 for (switch_node.cases()) |uncasted_case| {
2419 const case = uncasted_case.castTag(.SwitchCase).?;
2420 const case_src = tree.token_locs[case.firstToken()].start;
2421 // reset without freeing to reduce allocations.
2422 case_scope.instructions.items.len = 0;
2423
2424 // Check for else/_ prong, those are handled last.
2425 if (case.items_len == 1 and case.items()[0].tag == .SwitchElse) {
2426 special_case = case;
2427 continue;
2428 } else if (case.items_len == 1 and case.items()[0].tag == .Identifier and
2429 mem.eql(u8, tree.tokenSlice(case.items()[0].firstToken()), "_"))
2430 {
2431 special_case = case;
2432 continue;
2433 }
2434
2435 // If this is a simple one item prong then it is handled by the switchbr.
2436 if (case.items_len == 1 and getRangeNode(case.items()[0]) == null) {
2437 const item = items.items[items_index];
2438 items_index += 1;
2439 try switchCaseExpr(mod, &case_scope.base, block_scope.break_result_loc, block, case, target, target_ptr);
2440
2441 cases[case_index] = .{
2442 .item = item,
2443 .body = .{ .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items) },
2444 };
2445 case_index += 1;
2446 continue;
2447 }
2448
2449 // TODO if the case has few items and no ranges it might be better
2450 // to just handle them as switch prongs.
2451
2452 // Check if the target matches any of the items.
2453 // 1, 2, 3..6 will result in
2454 // target == 1 or target == 2 or (target >= 3 and target <= 6)
2455 var any_ok: ?*zir.Inst = null;
2456 for (case.items()) |item| {
2457 if (getRangeNode(item)) |range| {
2458 const range_src = tree.token_locs[range.op_token].start;
2459 const range_inst = items.items[items_index].castTag(.switch_range).?;
2460 items_index += 1;
2461
2462 // target >= start and target <= end
2463 const range_start_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .cmp_gte, target, range_inst.positionals.lhs);
2464 const range_end_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .cmp_lte, target, range_inst.positionals.rhs);
2465 const range_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .bool_and, range_start_ok, range_end_ok);
2466
2467 if (any_ok) |some| {
2468 any_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .bool_or, some, range_ok);
2469 } else {
2470 any_ok = range_ok;
2471 }
2472 continue;
2473 }
2474
2475 const item_inst = items.items[items_index];
2476 items_index += 1;
2477 const cpm_ok = try addZIRBinOp(mod, &else_scope.base, item_inst.src, .cmp_eq, target, item_inst);
2478
2479 if (any_ok) |some| {
2480 any_ok = try addZIRBinOp(mod, &else_scope.base, item_inst.src, .bool_or, some, cpm_ok);
2481 } else {
2482 any_ok = cpm_ok;
2483 }
2484 }
2485
2486 const condbr = try addZIRInstSpecial(mod, &case_scope.base, case_src, zir.Inst.CondBr, .{
2487 .condition = any_ok.?,
2488 .then_body = undefined, // populated below
2489 .else_body = undefined, // populated below
2490 }, .{});
2491 const cond_block = try addZIRInstBlock(mod, &else_scope.base, case_src, .block, .{
2492 .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items),
2493 });
2494
2495 // reset cond_scope for then_body
2496 case_scope.instructions.items.len = 0;
2497 try switchCaseExpr(mod, &case_scope.base, block_scope.break_result_loc, block, case, target, target_ptr);
2498 condbr.positionals.then_body = .{
2499 .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items),
2500 };
2501
2502 // reset cond_scope for else_body
2503 case_scope.instructions.items.len = 0;
2504 _ = try addZIRInst(mod, &case_scope.base, case_src, zir.Inst.BreakVoid, .{
2505 .block = cond_block,
2506 }, .{});
2507 condbr.positionals.else_body = .{
2508 .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items),
2509 };
2510 }
2511
2512 // Finally generate else block or a break.
2513 if (special_case) |case| {
2514 try switchCaseExpr(mod, &else_scope.base, block_scope.break_result_loc, block, case, target, target_ptr);
2515 } else {
2516 // Not handling all possible cases is a compile error.
2517 _ = try addZIRNoOp(mod, &else_scope.base, switch_src, .unreachable_unsafe);
2518 }
2519 switch_inst.castTag(.switchbr).?.positionals.else_body = .{
2520 .instructions = try block_scope.arena.dupe(*zir.Inst, else_scope.instructions.items),
2521 };
2522
2523 return &block.base;
2524}
2525
2526fn switchCaseExpr(
2527 mod: *Module,
2528 scope: *Scope,
2529 rl: ResultLoc,
2530 block: *zir.Inst.Block,
2531 case: *ast.Node.SwitchCase,
2532 target: *zir.Inst,
2533 target_ptr: ?*zir.Inst,
2534) !void {
2535 const tree = scope.tree();
2536 const case_src = tree.token_locs[case.firstToken()].start;
2537 const sub_scope = blk: {
2538 const uncasted_payload = case.payload orelse break :blk scope;
2539 const payload = uncasted_payload.castTag(.PointerPayload).?;
2540 const is_ptr = payload.ptr_token != null;
2541 const value_name = tree.tokenSlice(payload.value_symbol.firstToken());
2542 if (mem.eql(u8, value_name, "_")) {
2543 if (is_ptr) {
2544 return mod.failTok(scope, payload.ptr_token.?, "pointer modifier invalid on discard", .{});
2545 }
2546 break :blk scope;
2547 }
2548 return mod.failNode(scope, payload.value_symbol, "TODO implement switch value payload", .{});
2549 };
2550
2551 const case_body = try expr(mod, sub_scope, rl, case.expr);
2552 if (!case_body.tag.isNoReturn()) {
2553 _ = try addZIRInst(mod, sub_scope, case_src, zir.Inst.Break, .{
2554 .block = block,
2555 .operand = case_body,
2556 }, .{});
2557 }
2558}
2559
2249fn ret(mod: *Module, scope: *Scope, cfe: *ast.Node.ControlFlowExpression) InnerError!*zir.Inst {2560fn ret(mod: *Module, scope: *Scope, cfe: *ast.Node.ControlFlowExpression) InnerError!*zir.Inst {
2250 const tree = scope.tree();2561 const tree = scope.tree();
2251 const src = tree.token_locs[cfe.ltoken].start;2562 const src = tree.token_locs[cfe.ltoken].start;
src/codegen/c.zig+212-96
...@@ -1,12 +1,12 @@...@@ -1,12 +1,12 @@
1const std = @import("std");1const std = @import("std");
2const mem = std.mem;2const mem = std.mem;
3const log = std.log.scoped(.c);3const log = std.log.scoped(.c);
4const Writer = std.ArrayList(u8).Writer;
54
6const link = @import("../link.zig");5const link = @import("../link.zig");
7const Module = @import("../Module.zig");6const Module = @import("../Module.zig");
8const Compilation = @import("../Compilation.zig");7const Compilation = @import("../Compilation.zig");
9const Inst = @import("../ir.zig").Inst;8const ir = @import("../ir.zig");
9const Inst = ir.Inst;
10const Value = @import("../value.zig").Value;10const Value = @import("../value.zig").Value;
11const Type = @import("../type.zig").Type;11const Type = @import("../type.zig").Type;
12const TypedValue = @import("../TypedValue.zig");12const TypedValue = @import("../TypedValue.zig");
...@@ -41,6 +41,8 @@ pub const Object = struct {...@@ -41,6 +41,8 @@ pub const Object = struct {
41 value_map: CValueMap,41 value_map: CValueMap,
42 next_arg_index: usize = 0,42 next_arg_index: usize = 0,
43 next_local_index: usize = 0,43 next_local_index: usize = 0,
44 next_block_index: usize = 0,
45 indent_writer: std.io.AutoIndentingStream(std.ArrayList(u8).Writer),
4446
45 fn resolveInst(o: *Object, inst: *Inst) !CValue {47 fn resolveInst(o: *Object, inst: *Inst) !CValue {
46 if (inst.value()) |_| {48 if (inst.value()) |_| {
...@@ -57,31 +59,28 @@ pub const Object = struct {...@@ -57,31 +59,28 @@ pub const Object = struct {
5759
58 fn allocLocal(o: *Object, ty: Type, mutability: Mutability) !CValue {60 fn allocLocal(o: *Object, ty: Type, mutability: Mutability) !CValue {
59 const local_value = o.allocLocalValue();61 const local_value = o.allocLocalValue();
60 try o.renderTypeAndName(o.code.writer(), ty, local_value, mutability);62 try o.renderTypeAndName(o.writer(), ty, local_value, mutability);
61 return local_value;63 return local_value;
62 }64 }
6365
64 fn indent(o: *Object) !void {66 fn writer(o: *Object) std.io.AutoIndentingStream(std.ArrayList(u8).Writer).Writer {
65 const indent_size = 4;67 return o.indent_writer.writer();
66 const indent_level = 1;
67 const indent_amt = indent_size * indent_level;
68 try o.code.writer().writeByteNTimes(' ', indent_amt);
69 }68 }
7069
71 fn writeCValue(o: *Object, writer: Writer, c_value: CValue) !void {70 fn writeCValue(o: *Object, w: anytype, c_value: CValue) !void {
72 switch (c_value) {71 switch (c_value) {
73 .none => unreachable,72 .none => unreachable,
74 .local => |i| return writer.print("t{d}", .{i}),73 .local => |i| return w.print("t{d}", .{i}),
75 .local_ref => |i| return writer.print("&t{d}", .{i}),74 .local_ref => |i| return w.print("&t{d}", .{i}),
76 .constant => |inst| return o.dg.renderValue(writer, inst.ty, inst.value().?),75 .constant => |inst| return o.dg.renderValue(w, inst.ty, inst.value().?),
77 .arg => |i| return writer.print("a{d}", .{i}),76 .arg => |i| return w.print("a{d}", .{i}),
78 .decl => |decl| return writer.writeAll(mem.span(decl.name)),77 .decl => |decl| return w.writeAll(mem.span(decl.name)),
79 }78 }
80 }79 }
8180
82 fn renderTypeAndName(81 fn renderTypeAndName(
83 o: *Object,82 o: *Object,
84 writer: Writer,83 w: anytype,
85 ty: Type,84 ty: Type,
86 name: CValue,85 name: CValue,
87 mutability: Mutability,86 mutability: Mutability,
...@@ -97,15 +96,15 @@ pub const Object = struct {...@@ -97,15 +96,15 @@ pub const Object = struct {
97 render_ty = render_ty.elemType();96 render_ty = render_ty.elemType();
98 }97 }
9998
100 try o.dg.renderType(writer, render_ty);99 try o.dg.renderType(w, render_ty);
101100
102 const const_prefix = switch (mutability) {101 const const_prefix = switch (mutability) {
103 .Const => "const ",102 .Const => "const ",
104 .Mut => "",103 .Mut => "",
105 };104 };
106 try writer.print(" {s}", .{const_prefix});105 try w.print(" {s}", .{const_prefix});
107 try o.writeCValue(writer, name);106 try o.writeCValue(w, name);
108 try writer.writeAll(suffix.items);107 try w.writeAll(suffix.items);
109 }108 }
110};109};
111110
...@@ -126,10 +125,13 @@ pub const DeclGen = struct {...@@ -126,10 +125,13 @@ pub const DeclGen = struct {
126125
127 fn renderValue(126 fn renderValue(
128 dg: *DeclGen,127 dg: *DeclGen,
129 writer: Writer,128 writer: anytype,
130 t: Type,129 t: Type,
131 val: Value,130 val: Value,
132 ) error{ OutOfMemory, AnalysisFail }!void {131 ) error{ OutOfMemory, AnalysisFail }!void {
132 if (val.isUndef()) {
133 return dg.fail(dg.decl.src(), "TODO: C backend: properly handle undefined in all cases (with debug safety?)", .{});
134 }
133 switch (t.zigTypeTag()) {135 switch (t.zigTypeTag()) {
134 .Int => {136 .Int => {
135 if (t.isSignedInt())137 if (t.isSignedInt())
...@@ -197,13 +199,14 @@ pub const DeclGen = struct {...@@ -197,13 +199,14 @@ pub const DeclGen = struct {
197 },199 },
198 }200 }
199 },201 },
202 .Bool => return writer.print("{}", .{val.toBool()}),
200 else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement value {s}", .{203 else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement value {s}", .{
201 @tagName(e),204 @tagName(e),
202 }),205 }),
203 }206 }
204 }207 }
205208
206 fn renderFunctionSignature(dg: *DeclGen, w: Writer, is_global: bool) !void {209 fn renderFunctionSignature(dg: *DeclGen, w: anytype, is_global: bool) !void {
207 if (!is_global) {210 if (!is_global) {
208 try w.writeAll("static ");211 try w.writeAll("static ");
209 }212 }
...@@ -227,7 +230,7 @@ pub const DeclGen = struct {...@@ -227,7 +230,7 @@ pub const DeclGen = struct {
227 try w.writeByte(')');230 try w.writeByte(')');
228 }231 }
229232
230 fn renderType(dg: *DeclGen, w: Writer, t: Type) error{ OutOfMemory, AnalysisFail }!void {233 fn renderType(dg: *DeclGen, w: anytype, t: Type) error{ OutOfMemory, AnalysisFail }!void {
231 switch (t.zigTypeTag()) {234 switch (t.zigTypeTag()) {
232 .NoReturn => {235 .NoReturn => {
233 try w.writeAll("zig_noreturn void");236 try w.writeAll("zig_noreturn void");
...@@ -257,8 +260,8 @@ pub const DeclGen = struct {...@@ -257,8 +260,8 @@ pub const DeclGen = struct {
257 .int_signed, .int_unsigned => {260 .int_signed, .int_unsigned => {
258 const info = t.intInfo(dg.module.getTarget());261 const info = t.intInfo(dg.module.getTarget());
259 const sign_prefix = switch (info.signedness) {262 const sign_prefix = switch (info.signedness) {
260 .signed => "i",263 .signed => "",
261 .unsigned => "",264 .unsigned => "u",
262 };265 };
263 inline for (.{ 8, 16, 32, 64, 128 }) |nbits| {266 inline for (.{ 8, 16, 32, 64, 128 }) |nbits| {
264 if (info.bits <= nbits) {267 if (info.bits <= nbits) {
...@@ -290,6 +293,7 @@ pub const DeclGen = struct {...@@ -290,6 +293,7 @@ pub const DeclGen = struct {
290 try dg.renderType(w, t.elemType());293 try dg.renderType(w, t.elemType());
291 try w.writeAll(" *");294 try w.writeAll(" *");
292 },295 },
296 .Null, .Undefined => unreachable, // must be const or comptime
293 else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement type {s}", .{297 else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement type {s}", .{
294 @tagName(e),298 @tagName(e),
295 }),299 }),
...@@ -324,58 +328,20 @@ pub fn genDecl(o: *Object) !void {...@@ -324,58 +328,20 @@ pub fn genDecl(o: *Object) !void {
324 try fwd_decl_writer.writeAll(";\n");328 try fwd_decl_writer.writeAll(";\n");
325329
326 const func: *Module.Fn = func_payload.data;330 const func: *Module.Fn = func_payload.data;
327 const instructions = func.body.instructions;331 try o.indent_writer.insertNewline();
328 const writer = o.code.writer();332 try o.dg.renderFunctionSignature(o.writer(), is_global);
329 try writer.writeAll("\n");
330 try o.dg.renderFunctionSignature(writer, is_global);
331 if (instructions.len == 0) {
332 try writer.writeAll(" {}\n");
333 return;
334 }
335333
336 try writer.writeAll(" {");334 try o.writer().writeByte(' ');
337335 try genBody(o, func.body);
338 try writer.writeAll("\n");
339 for (instructions) |inst| {
340 const result_value = switch (inst.tag) {
341 .add => try genBinOp(o, inst.castTag(.add).?, " + "),
342 .alloc => try genAlloc(o, inst.castTag(.alloc).?),
343 .arg => genArg(o),
344 .assembly => try genAsm(o, inst.castTag(.assembly).?),
345 .block => try genBlock(o, inst.castTag(.block).?),
346 .bitcast => try genBitcast(o, inst.castTag(.bitcast).?),
347 .breakpoint => try genBreakpoint(o, inst.castTag(.breakpoint).?),
348 .call => try genCall(o, inst.castTag(.call).?),
349 .cmp_eq => try genBinOp(o, inst.castTag(.cmp_eq).?, " == "),
350 .cmp_gt => try genBinOp(o, inst.castTag(.cmp_gt).?, " > "),
351 .cmp_gte => try genBinOp(o, inst.castTag(.cmp_gte).?, " >= "),
352 .cmp_lt => try genBinOp(o, inst.castTag(.cmp_lt).?, " < "),
353 .cmp_lte => try genBinOp(o, inst.castTag(.cmp_lte).?, " <= "),
354 .cmp_neq => try genBinOp(o, inst.castTag(.cmp_neq).?, " != "),
355 .dbg_stmt => try genDbgStmt(o, inst.castTag(.dbg_stmt).?),
356 .intcast => try genIntCast(o, inst.castTag(.intcast).?),
357 .load => try genLoad(o, inst.castTag(.load).?),
358 .ret => try genRet(o, inst.castTag(.ret).?),
359 .retvoid => try genRetVoid(o),
360 .store => try genStore(o, inst.castTag(.store).?),
361 .sub => try genBinOp(o, inst.castTag(.sub).?, " - "),
362 .unreach => try genUnreach(o, inst.castTag(.unreach).?),
363 else => |e| return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement codegen for {}", .{e}),
364 };
365 switch (result_value) {
366 .none => {},
367 else => try o.value_map.putNoClobber(inst, result_value),
368 }
369 }
370336
371 try writer.writeAll("}\n");337 try o.indent_writer.insertNewline();
372 } else if (tv.val.tag() == .extern_fn) {338 } else if (tv.val.tag() == .extern_fn) {
373 const writer = o.code.writer();339 const writer = o.writer();
374 try writer.writeAll("ZIG_EXTERN_C ");340 try writer.writeAll("ZIG_EXTERN_C ");
375 try o.dg.renderFunctionSignature(writer, true);341 try o.dg.renderFunctionSignature(writer, true);
376 try writer.writeAll(";\n");342 try writer.writeAll(";\n");
377 } else {343 } else {
378 const writer = o.code.writer();344 const writer = o.writer();
379 try writer.writeAll("static ");345 try writer.writeAll("static ");
380346
381 // TODO ask the Decl if it is const347 // TODO ask the Decl if it is const
...@@ -410,11 +376,69 @@ pub fn genHeader(dg: *DeclGen) error{ AnalysisFail, OutOfMemory }!void {...@@ -410,11 +376,69 @@ pub fn genHeader(dg: *DeclGen) error{ AnalysisFail, OutOfMemory }!void {
410 }376 }
411}377}
412378
379pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!void {
380 const writer = o.writer();
381 if (body.instructions.len == 0) {
382 try writer.writeAll("{}");
383 return;
384 }
385
386 try writer.writeAll("{\n");
387 o.indent_writer.pushIndent();
388
389 for (body.instructions) |inst| {
390 const result_value = switch (inst.tag) {
391 .constant => unreachable, // excluded from function bodies
392 .add => try genBinOp(o, inst.castTag(.add).?, " + "),
393 .alloc => try genAlloc(o, inst.castTag(.alloc).?),
394 .arg => genArg(o),
395 .assembly => try genAsm(o, inst.castTag(.assembly).?),
396 .block => try genBlock(o, inst.castTag(.block).?),
397 .bitcast => try genBitcast(o, inst.castTag(.bitcast).?),
398 .breakpoint => try genBreakpoint(o, inst.castTag(.breakpoint).?),
399 .call => try genCall(o, inst.castTag(.call).?),
400 .cmp_eq => try genBinOp(o, inst.castTag(.cmp_eq).?, " == "),
401 .cmp_gt => try genBinOp(o, inst.castTag(.cmp_gt).?, " > "),
402 .cmp_gte => try genBinOp(o, inst.castTag(.cmp_gte).?, " >= "),
403 .cmp_lt => try genBinOp(o, inst.castTag(.cmp_lt).?, " < "),
404 .cmp_lte => try genBinOp(o, inst.castTag(.cmp_lte).?, " <= "),
405 .cmp_neq => try genBinOp(o, inst.castTag(.cmp_neq).?, " != "),
406 .dbg_stmt => try genDbgStmt(o, inst.castTag(.dbg_stmt).?),
407 .intcast => try genIntCast(o, inst.castTag(.intcast).?),
408 .load => try genLoad(o, inst.castTag(.load).?),
409 .ret => try genRet(o, inst.castTag(.ret).?),
410 .retvoid => try genRetVoid(o),
411 .store => try genStore(o, inst.castTag(.store).?),
412 .sub => try genBinOp(o, inst.castTag(.sub).?, " - "),
413 .unreach => try genUnreach(o, inst.castTag(.unreach).?),
414 .loop => try genLoop(o, inst.castTag(.loop).?),
415 .condbr => try genCondBr(o, inst.castTag(.condbr).?),
416 .br => try genBr(o, inst.castTag(.br).?),
417 .br_void => try genBrVoid(o, inst.castTag(.br_void).?.block),
418 .switchbr => try genSwitchBr(o, inst.castTag(.switchbr).?),
419 // bool_and and bool_or are non-short-circuit operations
420 .bool_and => try genBinOp(o, inst.castTag(.bool_and).?, " & "),
421 .bool_or => try genBinOp(o, inst.castTag(.bool_or).?, " | "),
422 .bit_and => try genBinOp(o, inst.castTag(.bit_and).?, " & "),
423 .bit_or => try genBinOp(o, inst.castTag(.bit_or).?, " | "),
424 .xor => try genBinOp(o, inst.castTag(.xor).?, " ^ "),
425 .not => try genUnOp(o, inst.castTag(.not).?, "!"),
426 else => |e| return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement codegen for {}", .{e}),
427 };
428 switch (result_value) {
429 .none => {},
430 else => try o.value_map.putNoClobber(inst, result_value),
431 }
432 }
433
434 o.indent_writer.popIndent();
435 try writer.writeAll("}");
436}
437
413fn genAlloc(o: *Object, alloc: *Inst.NoOp) !CValue {438fn genAlloc(o: *Object, alloc: *Inst.NoOp) !CValue {
414 const writer = o.code.writer();439 const writer = o.writer();
415440
416 // First line: the variable used as data storage.441 // First line: the variable used as data storage.
417 try o.indent();
418 const elem_type = alloc.base.ty.elemType();442 const elem_type = alloc.base.ty.elemType();
419 const mutability: Mutability = if (alloc.base.ty.isConstPtr()) .Const else .Mut;443 const mutability: Mutability = if (alloc.base.ty.isConstPtr()) .Const else .Mut;
420 const local = try o.allocLocal(elem_type, mutability);444 const local = try o.allocLocal(elem_type, mutability);
...@@ -430,15 +454,13 @@ fn genArg(o: *Object) CValue {...@@ -430,15 +454,13 @@ fn genArg(o: *Object) CValue {
430}454}
431455
432fn genRetVoid(o: *Object) !CValue {456fn genRetVoid(o: *Object) !CValue {
433 try o.indent();457 try o.writer().print("return;\n", .{});
434 try o.code.writer().print("return;\n", .{});
435 return CValue.none;458 return CValue.none;
436}459}
437460
438fn genLoad(o: *Object, inst: *Inst.UnOp) !CValue {461fn genLoad(o: *Object, inst: *Inst.UnOp) !CValue {
439 const operand = try o.resolveInst(inst.operand);462 const operand = try o.resolveInst(inst.operand);
440 const writer = o.code.writer();463 const writer = o.writer();
441 try o.indent();
442 const local = try o.allocLocal(inst.base.ty, .Const);464 const local = try o.allocLocal(inst.base.ty, .Const);
443 switch (operand) {465 switch (operand) {
444 .local_ref => |i| {466 .local_ref => |i| {
...@@ -458,8 +480,7 @@ fn genLoad(o: *Object, inst: *Inst.UnOp) !CValue {...@@ -458,8 +480,7 @@ fn genLoad(o: *Object, inst: *Inst.UnOp) !CValue {
458480
459fn genRet(o: *Object, inst: *Inst.UnOp) !CValue {481fn genRet(o: *Object, inst: *Inst.UnOp) !CValue {
460 const operand = try o.resolveInst(inst.operand);482 const operand = try o.resolveInst(inst.operand);
461 try o.indent();483 const writer = o.writer();
462 const writer = o.code.writer();
463 try writer.writeAll("return ");484 try writer.writeAll("return ");
464 try o.writeCValue(writer, operand);485 try o.writeCValue(writer, operand);
465 try writer.writeAll(";\n");486 try writer.writeAll(";\n");
...@@ -472,8 +493,7 @@ fn genIntCast(o: *Object, inst: *Inst.UnOp) !CValue {...@@ -472,8 +493,7 @@ fn genIntCast(o: *Object, inst: *Inst.UnOp) !CValue {
472493
473 const from = try o.resolveInst(inst.operand);494 const from = try o.resolveInst(inst.operand);
474495
475 try o.indent();496 const writer = o.writer();
476 const writer = o.code.writer();
477 const local = try o.allocLocal(inst.base.ty, .Const);497 const local = try o.allocLocal(inst.base.ty, .Const);
478 try writer.writeAll(" = (");498 try writer.writeAll(" = (");
479 try o.dg.renderType(writer, inst.base.ty);499 try o.dg.renderType(writer, inst.base.ty);
...@@ -488,8 +508,7 @@ fn genStore(o: *Object, inst: *Inst.BinOp) !CValue {...@@ -488,8 +508,7 @@ fn genStore(o: *Object, inst: *Inst.BinOp) !CValue {
488 const dest_ptr = try o.resolveInst(inst.lhs);508 const dest_ptr = try o.resolveInst(inst.lhs);
489 const src_val = try o.resolveInst(inst.rhs);509 const src_val = try o.resolveInst(inst.rhs);
490510
491 try o.indent();511 const writer = o.writer();
492 const writer = o.code.writer();
493 switch (dest_ptr) {512 switch (dest_ptr) {
494 .local_ref => |i| {513 .local_ref => |i| {
495 const dest: CValue = .{ .local = i };514 const dest: CValue = .{ .local = i };
...@@ -516,8 +535,7 @@ fn genBinOp(o: *Object, inst: *Inst.BinOp, operator: []const u8) !CValue {...@@ -516,8 +535,7 @@ fn genBinOp(o: *Object, inst: *Inst.BinOp, operator: []const u8) !CValue {
516 const lhs = try o.resolveInst(inst.lhs);535 const lhs = try o.resolveInst(inst.lhs);
517 const rhs = try o.resolveInst(inst.rhs);536 const rhs = try o.resolveInst(inst.rhs);
518537
519 try o.indent();538 const writer = o.writer();
520 const writer = o.code.writer();
521 const local = try o.allocLocal(inst.base.ty, .Const);539 const local = try o.allocLocal(inst.base.ty, .Const);
522540
523 try writer.writeAll(" = ");541 try writer.writeAll(" = ");
...@@ -529,6 +547,22 @@ fn genBinOp(o: *Object, inst: *Inst.BinOp, operator: []const u8) !CValue {...@@ -529,6 +547,22 @@ fn genBinOp(o: *Object, inst: *Inst.BinOp, operator: []const u8) !CValue {
529 return local;547 return local;
530}548}
531549
550fn genUnOp(o: *Object, inst: *Inst.UnOp, operator: []const u8) !CValue {
551 if (inst.base.isUnused())
552 return CValue.none;
553
554 const operand = try o.resolveInst(inst.operand);
555
556 const writer = o.writer();
557 const local = try o.allocLocal(inst.base.ty, .Const);
558
559 try writer.print(" = {s}", .{operator});
560 try o.writeCValue(writer, operand);
561 try writer.writeAll(";\n");
562
563 return local;
564}
565
532fn genCall(o: *Object, inst: *Inst.Call) !CValue {566fn genCall(o: *Object, inst: *Inst.Call) !CValue {
533 if (inst.func.castTag(.constant)) |func_inst| {567 if (inst.func.castTag(.constant)) |func_inst| {
534 const fn_decl = if (func_inst.val.castTag(.extern_fn)) |extern_fn|568 const fn_decl = if (func_inst.val.castTag(.extern_fn)) |extern_fn|
...@@ -543,8 +577,7 @@ fn genCall(o: *Object, inst: *Inst.Call) !CValue {...@@ -543,8 +577,7 @@ fn genCall(o: *Object, inst: *Inst.Call) !CValue {
543 const unused_result = inst.base.isUnused();577 const unused_result = inst.base.isUnused();
544 var result_local: CValue = .none;578 var result_local: CValue = .none;
545579
546 try o.indent();580 const writer = o.writer();
547 const writer = o.code.writer();
548 if (unused_result) {581 if (unused_result) {
549 if (ret_ty.hasCodeGenBits()) {582 if (ret_ty.hasCodeGenBits()) {
550 try writer.print("(void)", .{});583 try writer.print("(void)", .{});
...@@ -581,14 +614,53 @@ fn genDbgStmt(o: *Object, inst: *Inst.NoOp) !CValue {...@@ -581,14 +614,53 @@ fn genDbgStmt(o: *Object, inst: *Inst.NoOp) !CValue {
581}614}
582615
583fn genBlock(o: *Object, inst: *Inst.Block) !CValue {616fn genBlock(o: *Object, inst: *Inst.Block) !CValue {
584 return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement blocks", .{});617 const block_id: usize = o.next_block_index;
618 o.next_block_index += 1;
619 const writer = o.writer();
620
621 // store the block id in relocs.capacity as it is not used for anything else in the C backend.
622 inst.codegen.relocs.capacity = block_id;
623 const result = if (inst.base.ty.tag() != .void and !inst.base.isUnused()) blk: {
624 // allocate a location for the result
625 const local = try o.allocLocal(inst.base.ty, .Mut);
626 try writer.writeAll(";\n");
627 break :blk local;
628 } else
629 CValue{ .none = {} };
630
631 inst.codegen.mcv = @bitCast(@import("../codegen.zig").AnyMCValue, result);
632 try genBody(o, inst.body);
633 try o.indent_writer.insertNewline();
634 // label must be followed by an expression, add an empty one.
635 try writer.print("zig_block_{d}:;\n", .{block_id});
636 return result;
637}
638
639fn genBr(o: *Object, inst: *Inst.Br) !CValue {
640 const result = @bitCast(CValue, inst.block.codegen.mcv);
641 const writer = o.writer();
642
643 // If result is .none then the value of the block is unused.
644 if (inst.operand.ty.tag() != .void and result != .none) {
645 const operand = try o.resolveInst(inst.operand);
646 try o.writeCValue(writer, result);
647 try writer.writeAll(" = ");
648 try o.writeCValue(writer, operand);
649 try writer.writeAll(";\n");
650 }
651
652 return genBrVoid(o, inst.block);
653}
654
655fn genBrVoid(o: *Object, block: *Inst.Block) !CValue {
656 try o.writer().print("goto zig_block_{d};\n", .{block.codegen.relocs.capacity});
657 return CValue.none;
585}658}
586659
587fn genBitcast(o: *Object, inst: *Inst.UnOp) !CValue {660fn genBitcast(o: *Object, inst: *Inst.UnOp) !CValue {
588 const operand = try o.resolveInst(inst.operand);661 const operand = try o.resolveInst(inst.operand);
589662
590 const writer = o.code.writer();663 const writer = o.writer();
591 try o.indent();
592 if (inst.base.ty.zigTypeTag() == .Pointer and inst.operand.ty.zigTypeTag() == .Pointer) {664 if (inst.base.ty.zigTypeTag() == .Pointer and inst.operand.ty.zigTypeTag() == .Pointer) {
593 const local = try o.allocLocal(inst.base.ty, .Const);665 const local = try o.allocLocal(inst.base.ty, .Const);
594 try writer.writeAll(" = (");666 try writer.writeAll(" = (");
...@@ -602,7 +674,6 @@ fn genBitcast(o: *Object, inst: *Inst.UnOp) !CValue {...@@ -602,7 +674,6 @@ fn genBitcast(o: *Object, inst: *Inst.UnOp) !CValue {
602674
603 const local = try o.allocLocal(inst.base.ty, .Mut);675 const local = try o.allocLocal(inst.base.ty, .Mut);
604 try writer.writeAll(";\n");676 try writer.writeAll(";\n");
605 try o.indent();
606677
607 try writer.writeAll("memcpy(&");678 try writer.writeAll("memcpy(&");
608 try o.writeCValue(writer, local);679 try o.writeCValue(writer, local);
...@@ -616,14 +687,61 @@ fn genBitcast(o: *Object, inst: *Inst.UnOp) !CValue {...@@ -616,14 +687,61 @@ fn genBitcast(o: *Object, inst: *Inst.UnOp) !CValue {
616}687}
617688
618fn genBreakpoint(o: *Object, inst: *Inst.NoOp) !CValue {689fn genBreakpoint(o: *Object, inst: *Inst.NoOp) !CValue {
619 try o.indent();690 try o.writer().writeAll("zig_breakpoint();\n");
620 try o.code.writer().writeAll("zig_breakpoint();\n");
621 return CValue.none;691 return CValue.none;
622}692}
623693
624fn genUnreach(o: *Object, inst: *Inst.NoOp) !CValue {694fn genUnreach(o: *Object, inst: *Inst.NoOp) !CValue {
625 try o.indent();695 try o.writer().writeAll("zig_unreachable();\n");
626 try o.code.writer().writeAll("zig_unreachable();\n");696 return CValue.none;
697}
698
699fn genLoop(o: *Object, inst: *Inst.Loop) !CValue {
700 try o.writer().writeAll("while (true) ");
701 try genBody(o, inst.body);
702 try o.indent_writer.insertNewline();
703 return CValue.none;
704}
705
706fn genCondBr(o: *Object, inst: *Inst.CondBr) !CValue {
707 const cond = try o.resolveInst(inst.condition);
708 const writer = o.writer();
709
710 try writer.writeAll("if (");
711 try o.writeCValue(writer, cond);
712 try writer.writeAll(") ");
713 try genBody(o, inst.then_body);
714 try writer.writeAll(" else ");
715 try genBody(o, inst.else_body);
716 try o.indent_writer.insertNewline();
717
718 return CValue.none;
719}
720
721fn genSwitchBr(o: *Object, inst: *Inst.SwitchBr) !CValue {
722 const target = try o.resolveInst(inst.target);
723 const writer = o.writer();
724
725 try writer.writeAll("switch (");
726 try o.writeCValue(writer, target);
727 try writer.writeAll(") {\n");
728 o.indent_writer.pushIndent();
729
730 for (inst.cases) |case| {
731 try writer.writeAll("case ");
732 try o.dg.renderValue(writer, inst.target.ty, case.item);
733 try writer.writeAll(": ");
734 // the case body must be noreturn so we don't need to insert a break
735 try genBody(o, case.body);
736 try o.indent_writer.insertNewline();
737 }
738
739 try writer.writeAll("default: ");
740 try genBody(o, inst.else_body);
741 try o.indent_writer.insertNewline();
742
743 o.indent_writer.popIndent();
744 try writer.writeAll("}\n");
627 return CValue.none;745 return CValue.none;
628}746}
629747
...@@ -631,13 +749,12 @@ fn genAsm(o: *Object, as: *Inst.Assembly) !CValue {...@@ -631,13 +749,12 @@ fn genAsm(o: *Object, as: *Inst.Assembly) !CValue {
631 if (as.base.isUnused() and !as.is_volatile)749 if (as.base.isUnused() and !as.is_volatile)
632 return CValue.none;750 return CValue.none;
633751
634 const writer = o.code.writer();752 const writer = o.writer();
635 for (as.inputs) |i, index| {753 for (as.inputs) |i, index| {
636 if (i[0] == '{' and i[i.len - 1] == '}') {754 if (i[0] == '{' and i[i.len - 1] == '}') {
637 const reg = i[1 .. i.len - 1];755 const reg = i[1 .. i.len - 1];
638 const arg = as.args[index];756 const arg = as.args[index];
639 const arg_c_value = try o.resolveInst(arg);757 const arg_c_value = try o.resolveInst(arg);
640 try o.indent();
641 try writer.writeAll("register ");758 try writer.writeAll("register ");
642 try o.dg.renderType(writer, arg.ty);759 try o.dg.renderType(writer, arg.ty);
643760
...@@ -648,7 +765,6 @@ fn genAsm(o: *Object, as: *Inst.Assembly) !CValue {...@@ -648,7 +765,6 @@ fn genAsm(o: *Object, as: *Inst.Assembly) !CValue {
648 return o.dg.fail(o.dg.decl.src(), "TODO non-explicit inline asm regs", .{});765 return o.dg.fail(o.dg.decl.src(), "TODO non-explicit inline asm regs", .{});
649 }766 }
650 }767 }
651 try o.indent();
652 const volatile_string: []const u8 = if (as.is_volatile) "volatile " else "";768 const volatile_string: []const u8 = if (as.is_volatile) "volatile " else "";
653 try writer.print("__asm {s}(\"{s}\"", .{ volatile_string, as.asm_source });769 try writer.print("__asm {s}(\"{s}\"", .{ volatile_string, as.asm_source });
654 if (as.output) |_| {770 if (as.output) |_| {
src/ir.zig+2-2
...@@ -521,7 +521,7 @@ pub const Inst = struct {...@@ -521,7 +521,7 @@ pub const Inst = struct {
521 pub const base_tag = Tag.switchbr;521 pub const base_tag = Tag.switchbr;
522522
523 base: Inst,523 base: Inst,
524 target_ptr: *Inst,524 target: *Inst,
525 cases: []Case,525 cases: []Case,
526 /// Set of instructions whose lifetimes end at the start of one of the cases.526 /// Set of instructions whose lifetimes end at the start of one of the cases.
527 /// In same order as cases, deaths[0..case_0_count, case_0_count .. case_1_count, ... ].527 /// In same order as cases, deaths[0..case_0_count, case_0_count .. case_1_count, ... ].
...@@ -544,7 +544,7 @@ pub const Inst = struct {...@@ -544,7 +544,7 @@ pub const Inst = struct {
544 var i = index;544 var i = index;
545545
546 if (i < 1)546 if (i < 1)
547 return self.target_ptr;547 return self.target;
548 i -= 1;548 i -= 1;
549549
550 return null;550 return null;
src/link/C.zig+2
...@@ -95,7 +95,9 @@ pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void {...@@ -95,7 +95,9 @@ pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void {
95 .gpa = module.gpa,95 .gpa = module.gpa,
96 .code = code.toManaged(module.gpa),96 .code = code.toManaged(module.gpa),
97 .value_map = codegen.CValueMap.init(module.gpa),97 .value_map = codegen.CValueMap.init(module.gpa),
98 .indent_writer = undefined, // set later so we can get a pointer to object.code
98 };99 };
100 object.indent_writer = std.io.autoIndentingStream(4, object.code.writer());
99 defer object.value_map.deinit();101 defer object.value_map.deinit();
100 defer object.code.deinit();102 defer object.code.deinit();
101 defer object.dg.fwd_decl.deinit();103 defer object.dg.fwd_decl.deinit();
src/zir.zig+60
...@@ -338,6 +338,12 @@ pub const Inst = struct {...@@ -338,6 +338,12 @@ pub const Inst = struct {
338 enum_type,338 enum_type,
339 /// Does nothing; returns a void value.339 /// Does nothing; returns a void value.
340 void_value,340 void_value,
341 /// A switch expression.
342 switchbr,
343 /// A range in a switch case, `lhs...rhs`.
344 /// Only checks that `lhs >= rhs` if they are ints, everything else is
345 /// validated by the .switch instruction.
346 switch_range,
341347
342 pub fn Type(tag: Tag) type {348 pub fn Type(tag: Tag) type {
343 return switch (tag) {349 return switch (tag) {
...@@ -435,6 +441,7 @@ pub const Inst = struct {...@@ -435,6 +441,7 @@ pub const Inst = struct {
435 .error_union_type,441 .error_union_type,
436 .merge_error_sets,442 .merge_error_sets,
437 .slice_start,443 .slice_start,
444 .switch_range,
438 => BinOp,445 => BinOp,
439446
440 .block,447 .block,
...@@ -478,6 +485,7 @@ pub const Inst = struct {...@@ -478,6 +485,7 @@ pub const Inst = struct {
478 .enum_type => EnumType,485 .enum_type => EnumType,
479 .union_type => UnionType,486 .union_type => UnionType,
480 .struct_type => StructType,487 .struct_type => StructType,
488 .switchbr => SwitchBr,
481 };489 };
482 }490 }
483491
...@@ -605,6 +613,8 @@ pub const Inst = struct {...@@ -605,6 +613,8 @@ pub const Inst = struct {
605 .union_type,613 .union_type,
606 .struct_type,614 .struct_type,
607 .void_value,615 .void_value,
616 .switch_range,
617 .switchbr,
608 => false,618 => false,
609619
610 .@"break",620 .@"break",
...@@ -1171,6 +1181,36 @@ pub const Inst = struct {...@@ -1171,6 +1181,36 @@ pub const Inst = struct {
1171 none,1181 none,
1172 };1182 };
1173 };1183 };
1184
1185 pub const SwitchBr = struct {
1186 pub const base_tag = Tag.switchbr;
1187 base: Inst,
1188
1189 positionals: struct {
1190 target: *Inst,
1191 /// List of all individual items and ranges
1192 items: []*Inst,
1193 cases: []Case,
1194 else_body: Body,
1195 },
1196 kw_args: struct {
1197 /// Pointer to first range if such exists.
1198 range: ?*Inst = null,
1199 special_prong: SpecialProng = .none,
1200 },
1201
1202 // Not anonymous due to stage1 limitations
1203 pub const SpecialProng = enum {
1204 none,
1205 @"else",
1206 underscore,
1207 };
1208
1209 pub const Case = struct {
1210 item: *Inst,
1211 body: Body,
1212 };
1213 };
1174};1214};
11751215
1176pub const ErrorMsg = struct {1216pub const ErrorMsg = struct {
...@@ -1431,6 +1471,26 @@ const Writer = struct {...@@ -1431,6 +1471,26 @@ const Writer = struct {
1431 }1471 }
1432 try stream.writeByte(']');1472 try stream.writeByte(']');
1433 },1473 },
1474 []Inst.SwitchBr.Case => {
1475 if (param.len == 0) {
1476 return stream.writeAll("{}");
1477 }
1478 try stream.writeAll("{\n");
1479 for (param) |*case, i| {
1480 if (i != 0) {
1481 try stream.writeAll(",\n");
1482 }
1483 try stream.writeByteNTimes(' ', self.indent);
1484 self.indent += 2;
1485 try self.writeParamToStream(stream, &case.item);
1486 try stream.writeAll(" => ");
1487 try self.writeParamToStream(stream, &case.body);
1488 self.indent -= 2;
1489 }
1490 try stream.writeByte('\n');
1491 try stream.writeByteNTimes(' ', self.indent - 2);
1492 try stream.writeByte('}');
1493 },
1434 else => |T| @compileError("unimplemented: rendering parameter of type " ++ @typeName(T)),1494 else => |T| @compileError("unimplemented: rendering parameter of type " ++ @typeName(T)),
1435 }1495 }
1436 }1496 }
src/zir_sema.zig+228
...@@ -154,6 +154,8 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!...@@ -154,6 +154,8 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!
154 .bool_and => return zirBoolOp(mod, scope, old_inst.castTag(.bool_and).?),154 .bool_and => return zirBoolOp(mod, scope, old_inst.castTag(.bool_and).?),
155 .bool_or => return zirBoolOp(mod, scope, old_inst.castTag(.bool_or).?),155 .bool_or => return zirBoolOp(mod, scope, old_inst.castTag(.bool_or).?),
156 .void_value => return mod.constVoid(scope, old_inst.src),156 .void_value => return mod.constVoid(scope, old_inst.src),
157 .switchbr => return zirSwitchBr(mod, scope, old_inst.castTag(.switchbr).?),
158 .switch_range => return zirSwitchRange(mod, scope, old_inst.castTag(.switch_range).?),
157159
158 .container_field_named,160 .container_field_named,
159 .container_field_typed,161 .container_field_typed,
...@@ -1535,6 +1537,232 @@ fn zirSliceStart(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!...@@ -1535,6 +1537,232 @@ fn zirSliceStart(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!
1535 return mod.analyzeSlice(scope, inst.base.src, array_ptr, start, null, null);1537 return mod.analyzeSlice(scope, inst.base.src, array_ptr, start, null, null);
1536}1538}
15371539
1540fn zirSwitchRange(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
1541 const tracy = trace(@src());
1542 defer tracy.end();
1543 const start = try resolveInst(mod, scope, inst.positionals.lhs);
1544 const end = try resolveInst(mod, scope, inst.positionals.rhs);
1545
1546 switch (start.ty.zigTypeTag()) {
1547 .Int, .ComptimeInt => {},
1548 else => return mod.constVoid(scope, inst.base.src),
1549 }
1550 switch (end.ty.zigTypeTag()) {
1551 .Int, .ComptimeInt => {},
1552 else => return mod.constVoid(scope, inst.base.src),
1553 }
1554 // .switch_range must be inside a comptime scope
1555 const start_val = start.value().?;
1556 const end_val = end.value().?;
1557 if (start_val.compare(.gte, end_val)) {
1558 return mod.fail(scope, inst.base.src, "range start value must be smaller than the end value", .{});
1559 }
1560 return mod.constVoid(scope, inst.base.src);
1561}
1562
1563fn zirSwitchBr(mod: *Module, scope: *Scope, inst: *zir.Inst.SwitchBr) InnerError!*Inst {
1564 const tracy = trace(@src());
1565 defer tracy.end();
1566 const target = try resolveInst(mod, scope, inst.positionals.target);
1567 try validateSwitch(mod, scope, target, inst);
1568
1569 if (try mod.resolveDefinedValue(scope, target)) |target_val| {
1570 for (inst.positionals.cases) |case| {
1571 const resolved = try resolveInst(mod, scope, case.item);
1572 const casted = try mod.coerce(scope, target.ty, resolved);
1573 const item = try mod.resolveConstValue(scope, casted);
1574
1575 if (target_val.eql(item)) {
1576 try analyzeBody(mod, scope.cast(Scope.Block).?, case.body);
1577 return mod.constNoReturn(scope, inst.base.src);
1578 }
1579 }
1580 try analyzeBody(mod, scope.cast(Scope.Block).?, inst.positionals.else_body);
1581 return mod.constNoReturn(scope, inst.base.src);
1582 }
1583
1584 if (inst.positionals.cases.len == 0) {
1585 // no cases just analyze else_branch
1586 try analyzeBody(mod, scope.cast(Scope.Block).?, inst.positionals.else_body);
1587 return mod.constNoReturn(scope, inst.base.src);
1588 }
1589
1590 const parent_block = try mod.requireRuntimeBlock(scope, inst.base.src);
1591 const cases = try parent_block.arena.alloc(Inst.SwitchBr.Case, inst.positionals.cases.len);
1592
1593 var case_block: Scope.Block = .{
1594 .parent = parent_block,
1595 .inst_table = parent_block.inst_table,
1596 .func = parent_block.func,
1597 .owner_decl = parent_block.owner_decl,
1598 .src_decl = parent_block.src_decl,
1599 .instructions = .{},
1600 .arena = parent_block.arena,
1601 .inlining = parent_block.inlining,
1602 .is_comptime = parent_block.is_comptime,
1603 .branch_quota = parent_block.branch_quota,
1604 };
1605 defer case_block.instructions.deinit(mod.gpa);
1606
1607 for (inst.positionals.cases) |case, i| {
1608 // Reset without freeing.
1609 case_block.instructions.items.len = 0;
1610
1611 const resolved = try resolveInst(mod, scope, case.item);
1612 const casted = try mod.coerce(scope, target.ty, resolved);
1613 const item = try mod.resolveConstValue(scope, casted);
1614
1615 try analyzeBody(mod, &case_block, case.body);
1616
1617 cases[i] = .{
1618 .item = item,
1619 .body = .{ .instructions = try parent_block.arena.dupe(*Inst, case_block.instructions.items) },
1620 };
1621 }
1622
1623 case_block.instructions.items.len = 0;
1624 try analyzeBody(mod, &case_block, inst.positionals.else_body);
1625
1626 const else_body: ir.Body = .{
1627 .instructions = try parent_block.arena.dupe(*Inst, case_block.instructions.items),
1628 };
1629
1630 return mod.addSwitchBr(parent_block, inst.base.src, target, cases, else_body);
1631}
1632
1633fn validateSwitch(mod: *Module, scope: *Scope, target: *Inst, inst: *zir.Inst.SwitchBr) InnerError!void {
1634 // validate usage of '_' prongs
1635 if (inst.kw_args.special_prong == .underscore and target.ty.zigTypeTag() != .Enum) {
1636 return mod.fail(scope, inst.base.src, "'_' prong only allowed when switching on non-exhaustive enums", .{});
1637 // TODO notes "'_' prong here" inst.positionals.cases[last].src
1638 }
1639
1640 // check that target type supports ranges
1641 if (inst.kw_args.range) |range_inst| {
1642 switch (target.ty.zigTypeTag()) {
1643 .Int, .ComptimeInt => {},
1644 else => {
1645 return mod.fail(scope, target.src, "ranges not allowed when switching on type {}", .{target.ty});
1646 // TODO notes "range used here" range_inst.src
1647 },
1648 }
1649 }
1650
1651 // validate for duplicate items/missing else prong
1652 switch (target.ty.zigTypeTag()) {
1653 .Enum => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Enum", .{}),
1654 .ErrorSet => return mod.fail(scope, inst.base.src, "TODO validateSwitch .ErrorSet", .{}),
1655 .Union => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Union", .{}),
1656 .Int, .ComptimeInt => {
1657 var range_set = @import("RangeSet.zig").init(mod.gpa);
1658 defer range_set.deinit();
1659
1660 for (inst.positionals.items) |item| {
1661 const maybe_src = if (item.castTag(.switch_range)) |range| blk: {
1662 const start_resolved = try resolveInst(mod, scope, range.positionals.lhs);
1663 const start_casted = try mod.coerce(scope, target.ty, start_resolved);
1664 const end_resolved = try resolveInst(mod, scope, range.positionals.rhs);
1665 const end_casted = try mod.coerce(scope, target.ty, end_resolved);
1666
1667 break :blk try range_set.add(
1668 try mod.resolveConstValue(scope, start_casted),
1669 try mod.resolveConstValue(scope, end_casted),
1670 item.src,
1671 );
1672 } else blk: {
1673 const resolved = try resolveInst(mod, scope, item);
1674 const casted = try mod.coerce(scope, target.ty, resolved);
1675 const value = try mod.resolveConstValue(scope, casted);
1676 break :blk try range_set.add(value, value, item.src);
1677 };
1678
1679 if (maybe_src) |previous_src| {
1680 return mod.fail(scope, item.src, "duplicate switch value", .{});
1681 // TODO notes "previous value is here" previous_src
1682 }
1683 }
1684
1685 if (target.ty.zigTypeTag() == .Int) {
1686 var arena = std.heap.ArenaAllocator.init(mod.gpa);
1687 defer arena.deinit();
1688
1689 const start = try target.ty.minInt(&arena, mod.getTarget());
1690 const end = try target.ty.maxInt(&arena, mod.getTarget());
1691 if (try range_set.spans(start, end)) {
1692 if (inst.kw_args.special_prong == .@"else") {
1693 return mod.fail(scope, inst.base.src, "unreachable else prong, all cases already handled", .{});
1694 }
1695 return;
1696 }
1697 }
1698
1699 if (inst.kw_args.special_prong != .@"else") {
1700 return mod.fail(scope, inst.base.src, "switch must handle all possibilities", .{});
1701 }
1702 },
1703 .Bool => {
1704 var true_count: u8 = 0;
1705 var false_count: u8 = 0;
1706 for (inst.positionals.items) |item| {
1707 const resolved = try resolveInst(mod, scope, item);
1708 const casted = try mod.coerce(scope, Type.initTag(.bool), resolved);
1709 if ((try mod.resolveConstValue(scope, casted)).toBool()) {
1710 true_count += 1;
1711 } else {
1712 false_count += 1;
1713 }
1714
1715 if (true_count + false_count > 2) {
1716 return mod.fail(scope, item.src, "duplicate switch value", .{});
1717 }
1718 }
1719 if ((true_count + false_count < 2) and inst.kw_args.special_prong != .@"else") {
1720 return mod.fail(scope, inst.base.src, "switch must handle all possibilities", .{});
1721 }
1722 if ((true_count + false_count == 2) and inst.kw_args.special_prong == .@"else") {
1723 return mod.fail(scope, inst.base.src, "unreachable else prong, all cases already handled", .{});
1724 }
1725 },
1726 .EnumLiteral, .Void, .Fn, .Pointer, .Type => {
1727 if (inst.kw_args.special_prong != .@"else") {
1728 return mod.fail(scope, inst.base.src, "else prong required when switching on type '{}'", .{target.ty});
1729 }
1730
1731 var seen_values = std.HashMap(Value, usize, Value.hash, Value.eql, std.hash_map.DefaultMaxLoadPercentage).init(mod.gpa);
1732 defer seen_values.deinit();
1733
1734 for (inst.positionals.items) |item| {
1735 const resolved = try resolveInst(mod, scope, item);
1736 const casted = try mod.coerce(scope, target.ty, resolved);
1737 const val = try mod.resolveConstValue(scope, casted);
1738
1739 if (try seen_values.fetchPut(val, item.src)) |prev| {
1740 return mod.fail(scope, item.src, "duplicate switch value", .{});
1741 // TODO notes "previous value here" prev.value
1742 }
1743 }
1744 },
1745
1746 .ErrorUnion,
1747 .NoReturn,
1748 .Array,
1749 .Struct,
1750 .Undefined,
1751 .Null,
1752 .Optional,
1753 .BoundFn,
1754 .Opaque,
1755 .Vector,
1756 .Frame,
1757 .AnyFrame,
1758 .ComptimeFloat,
1759 .Float,
1760 => {
1761 return mod.fail(scope, target.src, "invalid switch target type '{}'", .{target.ty});
1762 },
1763 }
1764}
1765
1538fn zirImport(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {1766fn zirImport(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
1539 const tracy = trace(@src());1767 const tracy = trace(@src());
1540 defer tracy.end();1768 defer tracy.end();
test/stage2/cbe.zig+46
...@@ -185,6 +185,52 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -185,6 +185,52 @@ pub fn addCases(ctx: *TestContext) !void {
185 \\}185 \\}
186 , "");186 , "");
187 }187 }
188 {
189 var case = ctx.exeFromCompiledC("control flow", .{});
190
191 // Simple while loop
192 case.addCompareOutput(
193 \\export fn main() c_int {
194 \\ var a: c_int = 0;
195 \\ while (a < 5) : (a+=1) {}
196 \\ return a - 5;
197 \\}
198 , "");
199 case.addCompareOutput(
200 \\export fn main() c_int {
201 \\ var a = true;
202 \\ while (!a) {}
203 \\ return 0;
204 \\}
205 , "");
206
207 // If expression
208 case.addCompareOutput(
209 \\export fn main() c_int {
210 \\ var cond: c_int = 0;
211 \\ var a: c_int = @as(c_int, if (cond == 0)
212 \\ 2
213 \\ else
214 \\ 3) + 9;
215 \\ return a - 11;
216 \\}
217 , "");
218
219 // Switch expression
220 case.addCompareOutput(
221 \\export fn main() c_int {
222 \\ var cond: c_int = 0;
223 \\ var a: c_int = switch (cond) {
224 \\ 1 => 1,
225 \\ 2 => 2,
226 \\ 99...300, 12 => 3,
227 \\ 0 => 4,
228 \\ else => 5,
229 \\ };
230 \\ return a - 4;
231 \\}
232 , "");
233 }
188 ctx.c("empty start function", linux_x64,234 ctx.c("empty start function", linux_x64,
189 \\export fn _start() noreturn {235 \\export fn _start() noreturn {
190 \\ unreachable;236 \\ unreachable;