| author | |
| committer | |
| log | 11f6916f9bd9fbaf81a4bd60fd375eff8db78374 |
| tree | 6d7fc1c9bd2e8c4082b44059c8cf708e06e96127 |
| parent | 66c0fe4f90e5767a608efc77dbcecf2a5c8a5173 |
| parent | 75acfcf0eaa306b3a8872e50cb735e1d5eb18c52 |
| signature | Signed by PGP key 4AEE18F83AFDEB23 |
Stage2 cbe: more control flow8 files changed, 864 insertions(+), 101 deletions(-)
src/Module.zig+2-2| ... | ... | @@ -2215,7 +2215,7 @@ pub fn addSwitchBr( |
| 2215 | 2215 | self: *Module, |
| 2216 | 2216 | block: *Scope.Block, |
| 2217 | 2217 | src: usize, |
| 2218 | target_ptr: *Inst, | |
| 2218 | target: *Inst, | |
| 2219 | 2219 | cases: []Inst.SwitchBr.Case, |
| 2220 | 2220 | else_body: ir.Body, |
| 2221 | 2221 | ) !*Inst { |
| ... | ... | @@ -2226,7 +2226,7 @@ pub fn addSwitchBr( |
| 2226 | 2226 | .ty = Type.initTag(.noreturn), |
| 2227 | 2227 | .src = src, |
| 2228 | 2228 | }, |
| 2229 | .target_ptr = target_ptr, | |
| 2229 | .target = target, | |
| 2230 | 2230 | .cases = cases, |
| 2231 | 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 | 309 | .Catch => return catchExpr(mod, scope, rl, node.castTag(.Catch).?), |
| 310 | 310 | .Comptime => return comptimeKeyword(mod, scope, rl, node.castTag(.Comptime).?), |
| 311 | 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 | 313 | .ContainerDecl => return containerDecl(mod, scope, rl, node.castTag(.ContainerDecl).?), |
| 314 | 314 | |
| 315 | 315 | .Defer => return mod.failNode(scope, node, "TODO implement astgen.expr for .Defer", .{}), |
| ... | ... | @@ -2246,6 +2246,317 @@ fn forExpr( |
| 2246 | 2246 | ); |
| 2247 | 2247 | } |
| 2248 | 2248 | |
| 2249 | fn 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 | ||
| 2259 | fn 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 | ||
| 2270 | fn 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 | ||
| 2526 | fn 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 | ||
| 2249 | 2560 | fn ret(mod: *Module, scope: *Scope, cfe: *ast.Node.ControlFlowExpression) InnerError!*zir.Inst { |
| 2250 | 2561 | const tree = scope.tree(); |
| 2251 | 2562 | const src = tree.token_locs[cfe.ltoken].start; |
src/codegen/c.zig+212-96| ... | ... | @@ -1,12 +1,12 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | 2 | const mem = std.mem; |
| 3 | 3 | const log = std.log.scoped(.c); |
| 4 | const Writer = std.ArrayList(u8).Writer; | |
| 5 | 4 | |
| 6 | 5 | const link = @import("../link.zig"); |
| 7 | 6 | const Module = @import("../Module.zig"); |
| 8 | 7 | const Compilation = @import("../Compilation.zig"); |
| 9 | const Inst = @import("../ir.zig").Inst; | |
| 8 | const ir = @import("../ir.zig"); | |
| 9 | const Inst = ir.Inst; | |
| 10 | 10 | const Value = @import("../value.zig").Value; |
| 11 | 11 | const Type = @import("../type.zig").Type; |
| 12 | 12 | const TypedValue = @import("../TypedValue.zig"); |
| ... | ... | @@ -41,6 +41,8 @@ pub const Object = struct { |
| 41 | 41 | value_map: CValueMap, |
| 42 | 42 | next_arg_index: usize = 0, |
| 43 | 43 | next_local_index: usize = 0, |
| 44 | next_block_index: usize = 0, | |
| 45 | indent_writer: std.io.AutoIndentingStream(std.ArrayList(u8).Writer), | |
| 44 | 46 | |
| 45 | 47 | fn resolveInst(o: *Object, inst: *Inst) !CValue { |
| 46 | 48 | if (inst.value()) |_| { |
| ... | ... | @@ -57,31 +59,28 @@ pub const Object = struct { |
| 57 | 59 | |
| 58 | 60 | fn allocLocal(o: *Object, ty: Type, mutability: Mutability) !CValue { |
| 59 | 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 | 63 | return local_value; |
| 62 | 64 | } |
| 63 | 65 | |
| 64 | fn indent(o: *Object) !void { | |
| 65 | const indent_size = 4; | |
| 66 | const indent_level = 1; | |
| 67 | const indent_amt = indent_size * indent_level; | |
| 68 | try o.code.writer().writeByteNTimes(' ', indent_amt); | |
| 66 | fn writer(o: *Object) std.io.AutoIndentingStream(std.ArrayList(u8).Writer).Writer { | |
| 67 | return o.indent_writer.writer(); | |
| 69 | 68 | } |
| 70 | 69 | |
| 71 | fn writeCValue(o: *Object, writer: Writer, c_value: CValue) !void { | |
| 70 | fn writeCValue(o: *Object, w: anytype, c_value: CValue) !void { | |
| 72 | 71 | switch (c_value) { |
| 73 | 72 | .none => unreachable, |
| 74 | .local => |i| return writer.print("t{d}", .{i}), | |
| 75 | .local_ref => |i| return writer.print("&t{d}", .{i}), | |
| 76 | .constant => |inst| return o.dg.renderValue(writer, inst.ty, inst.value().?), | |
| 77 | .arg => |i| return writer.print("a{d}", .{i}), | |
| 78 | .decl => |decl| return writer.writeAll(mem.span(decl.name)), | |
| 73 | .local => |i| return w.print("t{d}", .{i}), | |
| 74 | .local_ref => |i| return w.print("&t{d}", .{i}), | |
| 75 | .constant => |inst| return o.dg.renderValue(w, inst.ty, inst.value().?), | |
| 76 | .arg => |i| return w.print("a{d}", .{i}), | |
| 77 | .decl => |decl| return w.writeAll(mem.span(decl.name)), | |
| 79 | 78 | } |
| 80 | 79 | } |
| 81 | 80 | |
| 82 | 81 | fn renderTypeAndName( |
| 83 | 82 | o: *Object, |
| 84 | writer: Writer, | |
| 83 | w: anytype, | |
| 85 | 84 | ty: Type, |
| 86 | 85 | name: CValue, |
| 87 | 86 | mutability: Mutability, |
| ... | ... | @@ -97,15 +96,15 @@ pub const Object = struct { |
| 97 | 96 | render_ty = render_ty.elemType(); |
| 98 | 97 | } |
| 99 | 98 | |
| 100 | try o.dg.renderType(writer, render_ty); | |
| 99 | try o.dg.renderType(w, render_ty); | |
| 101 | 100 | |
| 102 | 101 | const const_prefix = switch (mutability) { |
| 103 | 102 | .Const => "const ", |
| 104 | 103 | .Mut => "", |
| 105 | 104 | }; |
| 106 | try writer.print(" {s}", .{const_prefix}); | |
| 107 | try o.writeCValue(writer, name); | |
| 108 | try writer.writeAll(suffix.items); | |
| 105 | try w.print(" {s}", .{const_prefix}); | |
| 106 | try o.writeCValue(w, name); | |
| 107 | try w.writeAll(suffix.items); | |
| 109 | 108 | } |
| 110 | 109 | }; |
| 111 | 110 | |
| ... | ... | @@ -126,10 +125,13 @@ pub const DeclGen = struct { |
| 126 | 125 | |
| 127 | 126 | fn renderValue( |
| 128 | 127 | dg: *DeclGen, |
| 129 | writer: Writer, | |
| 128 | writer: anytype, | |
| 130 | 129 | t: Type, |
| 131 | 130 | val: Value, |
| 132 | 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 | 135 | switch (t.zigTypeTag()) { |
| 134 | 136 | .Int => { |
| 135 | 137 | if (t.isSignedInt()) |
| ... | ... | @@ -197,13 +199,14 @@ pub const DeclGen = struct { |
| 197 | 199 | }, |
| 198 | 200 | } |
| 199 | 201 | }, |
| 202 | .Bool => return writer.print("{}", .{val.toBool()}), | |
| 200 | 203 | else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement value {s}", .{ |
| 201 | 204 | @tagName(e), |
| 202 | 205 | }), |
| 203 | 206 | } |
| 204 | 207 | } |
| 205 | 208 | |
| 206 | fn renderFunctionSignature(dg: *DeclGen, w: Writer, is_global: bool) !void { | |
| 209 | fn renderFunctionSignature(dg: *DeclGen, w: anytype, is_global: bool) !void { | |
| 207 | 210 | if (!is_global) { |
| 208 | 211 | try w.writeAll("static "); |
| 209 | 212 | } |
| ... | ... | @@ -227,7 +230,7 @@ pub const DeclGen = struct { |
| 227 | 230 | try w.writeByte(')'); |
| 228 | 231 | } |
| 229 | 232 | |
| 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 | 234 | switch (t.zigTypeTag()) { |
| 232 | 235 | .NoReturn => { |
| 233 | 236 | try w.writeAll("zig_noreturn void"); |
| ... | ... | @@ -257,8 +260,8 @@ pub const DeclGen = struct { |
| 257 | 260 | .int_signed, .int_unsigned => { |
| 258 | 261 | const info = t.intInfo(dg.module.getTarget()); |
| 259 | 262 | const sign_prefix = switch (info.signedness) { |
| 260 | .signed => "i", | |
| 261 | .unsigned => "", | |
| 263 | .signed => "", | |
| 264 | .unsigned => "u", | |
| 262 | 265 | }; |
| 263 | 266 | inline for (.{ 8, 16, 32, 64, 128 }) |nbits| { |
| 264 | 267 | if (info.bits <= nbits) { |
| ... | ... | @@ -290,6 +293,7 @@ pub const DeclGen = struct { |
| 290 | 293 | try dg.renderType(w, t.elemType()); |
| 291 | 294 | try w.writeAll(" *"); |
| 292 | 295 | }, |
| 296 | .Null, .Undefined => unreachable, // must be const or comptime | |
| 293 | 297 | else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement type {s}", .{ |
| 294 | 298 | @tagName(e), |
| 295 | 299 | }), |
| ... | ... | @@ -324,58 +328,20 @@ pub fn genDecl(o: *Object) !void { |
| 324 | 328 | try fwd_decl_writer.writeAll(";\n"); |
| 325 | 329 | |
| 326 | 330 | const func: *Module.Fn = func_payload.data; |
| 327 | const instructions = func.body.instructions; | |
| 328 | const writer = o.code.writer(); | |
| 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 | } | |
| 331 | try o.indent_writer.insertNewline(); | |
| 332 | try o.dg.renderFunctionSignature(o.writer(), is_global); | |
| 335 | 333 | |
| 336 | try writer.writeAll(" {"); | |
| 337 | ||
| 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 | } | |
| 334 | try o.writer().writeByte(' '); | |
| 335 | try genBody(o, func.body); | |
| 370 | 336 | |
| 371 | try writer.writeAll("}\n"); | |
| 337 | try o.indent_writer.insertNewline(); | |
| 372 | 338 | } else if (tv.val.tag() == .extern_fn) { |
| 373 | const writer = o.code.writer(); | |
| 339 | const writer = o.writer(); | |
| 374 | 340 | try writer.writeAll("ZIG_EXTERN_C "); |
| 375 | 341 | try o.dg.renderFunctionSignature(writer, true); |
| 376 | 342 | try writer.writeAll(";\n"); |
| 377 | 343 | } else { |
| 378 | const writer = o.code.writer(); | |
| 344 | const writer = o.writer(); | |
| 379 | 345 | try writer.writeAll("static "); |
| 380 | 346 | |
| 381 | 347 | // TODO ask the Decl if it is const |
| ... | ... | @@ -410,11 +376,69 @@ pub fn genHeader(dg: *DeclGen) error{ AnalysisFail, OutOfMemory }!void { |
| 410 | 376 | } |
| 411 | 377 | } |
| 412 | 378 | |
| 379 | pub 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 | ||
| 413 | 438 | fn genAlloc(o: *Object, alloc: *Inst.NoOp) !CValue { |
| 414 | const writer = o.code.writer(); | |
| 439 | const writer = o.writer(); | |
| 415 | 440 | |
| 416 | 441 | // First line: the variable used as data storage. |
| 417 | try o.indent(); | |
| 418 | 442 | const elem_type = alloc.base.ty.elemType(); |
| 419 | 443 | const mutability: Mutability = if (alloc.base.ty.isConstPtr()) .Const else .Mut; |
| 420 | 444 | const local = try o.allocLocal(elem_type, mutability); |
| ... | ... | @@ -430,15 +454,13 @@ fn genArg(o: *Object) CValue { |
| 430 | 454 | } |
| 431 | 455 | |
| 432 | 456 | fn genRetVoid(o: *Object) !CValue { |
| 433 | try o.indent(); | |
| 434 | try o.code.writer().print("return;\n", .{}); | |
| 457 | try o.writer().print("return;\n", .{}); | |
| 435 | 458 | return CValue.none; |
| 436 | 459 | } |
| 437 | 460 | |
| 438 | 461 | fn genLoad(o: *Object, inst: *Inst.UnOp) !CValue { |
| 439 | 462 | const operand = try o.resolveInst(inst.operand); |
| 440 | const writer = o.code.writer(); | |
| 441 | try o.indent(); | |
| 463 | const writer = o.writer(); | |
| 442 | 464 | const local = try o.allocLocal(inst.base.ty, .Const); |
| 443 | 465 | switch (operand) { |
| 444 | 466 | .local_ref => |i| { |
| ... | ... | @@ -458,8 +480,7 @@ fn genLoad(o: *Object, inst: *Inst.UnOp) !CValue { |
| 458 | 480 | |
| 459 | 481 | fn genRet(o: *Object, inst: *Inst.UnOp) !CValue { |
| 460 | 482 | const operand = try o.resolveInst(inst.operand); |
| 461 | try o.indent(); | |
| 462 | const writer = o.code.writer(); | |
| 483 | const writer = o.writer(); | |
| 463 | 484 | try writer.writeAll("return "); |
| 464 | 485 | try o.writeCValue(writer, operand); |
| 465 | 486 | try writer.writeAll(";\n"); |
| ... | ... | @@ -472,8 +493,7 @@ fn genIntCast(o: *Object, inst: *Inst.UnOp) !CValue { |
| 472 | 493 | |
| 473 | 494 | const from = try o.resolveInst(inst.operand); |
| 474 | 495 | |
| 475 | try o.indent(); | |
| 476 | const writer = o.code.writer(); | |
| 496 | const writer = o.writer(); | |
| 477 | 497 | const local = try o.allocLocal(inst.base.ty, .Const); |
| 478 | 498 | try writer.writeAll(" = ("); |
| 479 | 499 | try o.dg.renderType(writer, inst.base.ty); |
| ... | ... | @@ -488,8 +508,7 @@ fn genStore(o: *Object, inst: *Inst.BinOp) !CValue { |
| 488 | 508 | const dest_ptr = try o.resolveInst(inst.lhs); |
| 489 | 509 | const src_val = try o.resolveInst(inst.rhs); |
| 490 | 510 | |
| 491 | try o.indent(); | |
| 492 | const writer = o.code.writer(); | |
| 511 | const writer = o.writer(); | |
| 493 | 512 | switch (dest_ptr) { |
| 494 | 513 | .local_ref => |i| { |
| 495 | 514 | const dest: CValue = .{ .local = i }; |
| ... | ... | @@ -516,8 +535,7 @@ fn genBinOp(o: *Object, inst: *Inst.BinOp, operator: []const u8) !CValue { |
| 516 | 535 | const lhs = try o.resolveInst(inst.lhs); |
| 517 | 536 | const rhs = try o.resolveInst(inst.rhs); |
| 518 | 537 | |
| 519 | try o.indent(); | |
| 520 | const writer = o.code.writer(); | |
| 538 | const writer = o.writer(); | |
| 521 | 539 | const local = try o.allocLocal(inst.base.ty, .Const); |
| 522 | 540 | |
| 523 | 541 | try writer.writeAll(" = "); |
| ... | ... | @@ -529,6 +547,22 @@ fn genBinOp(o: *Object, inst: *Inst.BinOp, operator: []const u8) !CValue { |
| 529 | 547 | return local; |
| 530 | 548 | } |
| 531 | 549 | |
| 550 | fn 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 | ||
| 532 | 566 | fn genCall(o: *Object, inst: *Inst.Call) !CValue { |
| 533 | 567 | if (inst.func.castTag(.constant)) |func_inst| { |
| 534 | 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 | 577 | const unused_result = inst.base.isUnused(); |
| 544 | 578 | var result_local: CValue = .none; |
| 545 | 579 | |
| 546 | try o.indent(); | |
| 547 | const writer = o.code.writer(); | |
| 580 | const writer = o.writer(); | |
| 548 | 581 | if (unused_result) { |
| 549 | 582 | if (ret_ty.hasCodeGenBits()) { |
| 550 | 583 | try writer.print("(void)", .{}); |
| ... | ... | @@ -581,14 +614,53 @@ fn genDbgStmt(o: *Object, inst: *Inst.NoOp) !CValue { |
| 581 | 614 | } |
| 582 | 615 | |
| 583 | 616 | fn 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 | ||
| 639 | fn 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 | ||
| 655 | fn 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 | } |
| 586 | 659 | |
| 587 | 660 | fn genBitcast(o: *Object, inst: *Inst.UnOp) !CValue { |
| 588 | 661 | const operand = try o.resolveInst(inst.operand); |
| 589 | 662 | |
| 590 | const writer = o.code.writer(); | |
| 591 | try o.indent(); | |
| 663 | const writer = o.writer(); | |
| 592 | 664 | if (inst.base.ty.zigTypeTag() == .Pointer and inst.operand.ty.zigTypeTag() == .Pointer) { |
| 593 | 665 | const local = try o.allocLocal(inst.base.ty, .Const); |
| 594 | 666 | try writer.writeAll(" = ("); |
| ... | ... | @@ -602,7 +674,6 @@ fn genBitcast(o: *Object, inst: *Inst.UnOp) !CValue { |
| 602 | 674 | |
| 603 | 675 | const local = try o.allocLocal(inst.base.ty, .Mut); |
| 604 | 676 | try writer.writeAll(";\n"); |
| 605 | try o.indent(); | |
| 606 | 677 | |
| 607 | 678 | try writer.writeAll("memcpy(&"); |
| 608 | 679 | try o.writeCValue(writer, local); |
| ... | ... | @@ -616,14 +687,61 @@ fn genBitcast(o: *Object, inst: *Inst.UnOp) !CValue { |
| 616 | 687 | } |
| 617 | 688 | |
| 618 | 689 | fn genBreakpoint(o: *Object, inst: *Inst.NoOp) !CValue { |
| 619 | try o.indent(); | |
| 620 | try o.code.writer().writeAll("zig_breakpoint();\n"); | |
| 690 | try o.writer().writeAll("zig_breakpoint();\n"); | |
| 621 | 691 | return CValue.none; |
| 622 | 692 | } |
| 623 | 693 | |
| 624 | 694 | fn genUnreach(o: *Object, inst: *Inst.NoOp) !CValue { |
| 625 | try o.indent(); | |
| 626 | try o.code.writer().writeAll("zig_unreachable();\n"); | |
| 695 | try o.writer().writeAll("zig_unreachable();\n"); | |
| 696 | return CValue.none; | |
| 697 | } | |
| 698 | ||
| 699 | fn 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 | ||
| 706 | fn 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 | ||
| 721 | fn 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 | 745 | return CValue.none; |
| 628 | 746 | } |
| 629 | 747 | |
| ... | ... | @@ -631,13 +749,12 @@ fn genAsm(o: *Object, as: *Inst.Assembly) !CValue { |
| 631 | 749 | if (as.base.isUnused() and !as.is_volatile) |
| 632 | 750 | return CValue.none; |
| 633 | 751 | |
| 634 | const writer = o.code.writer(); | |
| 752 | const writer = o.writer(); | |
| 635 | 753 | for (as.inputs) |i, index| { |
| 636 | 754 | if (i[0] == '{' and i[i.len - 1] == '}') { |
| 637 | 755 | const reg = i[1 .. i.len - 1]; |
| 638 | 756 | const arg = as.args[index]; |
| 639 | 757 | const arg_c_value = try o.resolveInst(arg); |
| 640 | try o.indent(); | |
| 641 | 758 | try writer.writeAll("register "); |
| 642 | 759 | try o.dg.renderType(writer, arg.ty); |
| 643 | 760 | |
| ... | ... | @@ -648,7 +765,6 @@ fn genAsm(o: *Object, as: *Inst.Assembly) !CValue { |
| 648 | 765 | return o.dg.fail(o.dg.decl.src(), "TODO non-explicit inline asm regs", .{}); |
| 649 | 766 | } |
| 650 | 767 | } |
| 651 | try o.indent(); | |
| 652 | 768 | const volatile_string: []const u8 = if (as.is_volatile) "volatile " else ""; |
| 653 | 769 | try writer.print("__asm {s}(\"{s}\"", .{ volatile_string, as.asm_source }); |
| 654 | 770 | if (as.output) |_| { |
src/ir.zig+2-2| ... | ... | @@ -521,7 +521,7 @@ pub const Inst = struct { |
| 521 | 521 | pub const base_tag = Tag.switchbr; |
| 522 | 522 | |
| 523 | 523 | base: Inst, |
| 524 | target_ptr: *Inst, | |
| 524 | target: *Inst, | |
| 525 | 525 | cases: []Case, |
| 526 | 526 | /// Set of instructions whose lifetimes end at the start of one of the cases. |
| 527 | 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 | 544 | var i = index; |
| 545 | 545 | |
| 546 | 546 | if (i < 1) |
| 547 | return self.target_ptr; | |
| 547 | return self.target; | |
| 548 | 548 | i -= 1; |
| 549 | 549 | |
| 550 | 550 | return null; |
src/link/C.zig+2| ... | ... | @@ -95,7 +95,9 @@ pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void { |
| 95 | 95 | .gpa = module.gpa, |
| 96 | 96 | .code = code.toManaged(module.gpa), |
| 97 | 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 | 101 | defer object.value_map.deinit(); |
| 100 | 102 | defer object.code.deinit(); |
| 101 | 103 | defer object.dg.fwd_decl.deinit(); |
src/zir.zig+60| ... | ... | @@ -338,6 +338,12 @@ pub const Inst = struct { |
| 338 | 338 | enum_type, |
| 339 | 339 | /// Does nothing; returns a void value. |
| 340 | 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, | |
| 341 | 347 | |
| 342 | 348 | pub fn Type(tag: Tag) type { |
| 343 | 349 | return switch (tag) { |
| ... | ... | @@ -435,6 +441,7 @@ pub const Inst = struct { |
| 435 | 441 | .error_union_type, |
| 436 | 442 | .merge_error_sets, |
| 437 | 443 | .slice_start, |
| 444 | .switch_range, | |
| 438 | 445 | => BinOp, |
| 439 | 446 | |
| 440 | 447 | .block, |
| ... | ... | @@ -478,6 +485,7 @@ pub const Inst = struct { |
| 478 | 485 | .enum_type => EnumType, |
| 479 | 486 | .union_type => UnionType, |
| 480 | 487 | .struct_type => StructType, |
| 488 | .switchbr => SwitchBr, | |
| 481 | 489 | }; |
| 482 | 490 | } |
| 483 | 491 | |
| ... | ... | @@ -605,6 +613,8 @@ pub const Inst = struct { |
| 605 | 613 | .union_type, |
| 606 | 614 | .struct_type, |
| 607 | 615 | .void_value, |
| 616 | .switch_range, | |
| 617 | .switchbr, | |
| 608 | 618 | => false, |
| 609 | 619 | |
| 610 | 620 | .@"break", |
| ... | ... | @@ -1171,6 +1181,36 @@ pub const Inst = struct { |
| 1171 | 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 | }; |
| 1175 | 1215 | |
| 1176 | 1216 | pub const ErrorMsg = struct { |
| ... | ... | @@ -1431,6 +1471,26 @@ const Writer = struct { |
| 1431 | 1471 | } |
| 1432 | 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 | 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 | 154 | .bool_and => return zirBoolOp(mod, scope, old_inst.castTag(.bool_and).?), |
| 155 | 155 | .bool_or => return zirBoolOp(mod, scope, old_inst.castTag(.bool_or).?), |
| 156 | 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).?), | |
| 157 | 159 | |
| 158 | 160 | .container_field_named, |
| 159 | 161 | .container_field_typed, |
| ... | ... | @@ -1535,6 +1537,232 @@ fn zirSliceStart(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError! |
| 1535 | 1537 | return mod.analyzeSlice(scope, inst.base.src, array_ptr, start, null, null); |
| 1536 | 1538 | } |
| 1537 | 1539 | |
| 1540 | fn 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 | ||
| 1563 | fn 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 | ||
| 1633 | fn 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 | ||
| 1538 | 1766 | fn zirImport(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { |
| 1539 | 1767 | const tracy = trace(@src()); |
| 1540 | 1768 | defer tracy.end(); |
test/stage2/cbe.zig+46| ... | ... | @@ -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 | 234 | ctx.c("empty start function", linux_x64, |
| 189 | 235 | \\export fn _start() noreturn { |
| 190 | 236 | \\ unreachable; |