authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-12-21 21:06:08-08:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2021-12-21 21:06:08-08:00
log5d6380b38d7c83055fe29eb7f8a19b1424276cd7
tree59ff7df2562eb47b34cd5dd3551742200a0f85f0
parent88be5bd81decab792cdb5b749b4bb5cf6d71e877
parentc55f58d8bb38ef356282e43fa010b5e3f8da8a00
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #10384 from joachimschmidt557/stage2-arm-optionals

stage2 ARM: basic implementation of optionals and error unions

2 files changed, 176 insertions(+), 51 deletions(-)

src/arch/arm/CodeGen.zig+107-51
...@@ -1104,7 +1104,13 @@ fn airUnwrapErrErr(self: *Self, inst: Air.Inst.Index) !void {...@@ -1104,7 +1104,13 @@ fn airUnwrapErrErr(self: *Self, inst: Air.Inst.Index) !void {
11041104
1105fn airUnwrapErrPayload(self: *Self, inst: Air.Inst.Index) !void {1105fn airUnwrapErrPayload(self: *Self, inst: Air.Inst.Index) !void {
1106 const ty_op = self.air.instructions.items(.data)[inst].ty_op;1106 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
1107 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement unwrap error union payload for {}", .{self.target.cpu.arch});1107 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
1108 const err_ty = self.air.typeOf(ty_op.operand);
1109 const payload_ty = err_ty.errorUnionPayload();
1110 if (!payload_ty.hasCodeGenBits()) break :result MCValue.none;
1111
1112 return self.fail("TODO implement unwrap error union payload for non-empty payloads", .{});
1113 };
1108 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });1114 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
1109}1115}
11101116
...@@ -2334,39 +2340,81 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void {...@@ -2334,39 +2340,81 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void {
2334 return self.finishAir(inst, .unreach, .{ .none, .none, .none });2340 return self.finishAir(inst, .unreach, .{ .none, .none, .none });
2335}2341}
23362342
2337fn isNull(self: *Self, operand: MCValue) !MCValue {2343fn isNull(self: *Self, ty: Type, operand: MCValue) !MCValue {
2338 _ = operand;2344 if (ty.isPtrLikeOptional()) {
2339 // Here you can specialize this instruction if it makes sense to, otherwise the default2345 assert(ty.abiSize(self.target.*) == 4);
2340 // will call isNonNull and invert the result.2346
2341 return self.fail("TODO call isNonNull and invert the result", .{});2347 const reg_mcv: MCValue = switch (operand) {
2348 .register => operand,
2349 else => .{ .register = try self.copyToTmpRegister(ty, operand) },
2350 };
2351
2352 try self.genArmBinOpCode(undefined, reg_mcv, .{ .immediate = 0 }, false, .cmp_eq, undefined);
2353
2354 return MCValue{ .compare_flags_unsigned = .eq };
2355 } else {
2356 return self.fail("TODO implement non-pointer optionals", .{});
2357 }
2342}2358}
23432359
2344fn isNonNull(self: *Self, operand: MCValue) !MCValue {2360fn isNonNull(self: *Self, ty: Type, operand: MCValue) !MCValue {
2345 _ = operand;2361 const is_null_result = try self.isNull(ty, operand);
2346 // Here you can specialize this instruction if it makes sense to, otherwise the default2362 assert(is_null_result.compare_flags_unsigned == .eq);
2347 // will call isNull and invert the result.2363
2348 return self.fail("TODO call isNull and invert the result", .{});2364 return MCValue{ .compare_flags_unsigned = .neq };
2349}2365}
23502366
2351fn isErr(self: *Self, operand: MCValue) !MCValue {2367fn isErr(self: *Self, ty: Type, operand: MCValue) !MCValue {
2352 _ = operand;2368 _ = operand;
2353 // Here you can specialize this instruction if it makes sense to, otherwise the default2369
2354 // will call isNonNull and invert the result.2370 const error_type = ty.errorUnionSet();
2355 return self.fail("TODO call isNonErr and invert the result", .{});2371 const payload_type = ty.errorUnionPayload();
2372
2373 if (!error_type.hasCodeGenBits()) {
2374 return MCValue{ .immediate = 0 }; // always false
2375 } else if (!payload_type.hasCodeGenBits()) {
2376 if (error_type.abiSize(self.target.*) <= 4) {
2377 const reg_mcv: MCValue = switch (operand) {
2378 .register => operand,
2379 else => .{ .register = try self.copyToTmpRegister(error_type, operand) },
2380 };
2381
2382 try self.genArmBinOpCode(undefined, reg_mcv, .{ .immediate = 0 }, false, .cmp_eq, undefined);
2383
2384 return MCValue{ .compare_flags_unsigned = .gt };
2385 } else {
2386 return self.fail("TODO isErr for errors with size > 4", .{});
2387 }
2388 } else {
2389 return self.fail("TODO isErr for non-empty payloads", .{});
2390 }
2356}2391}
23572392
2358fn isNonErr(self: *Self, operand: MCValue) !MCValue {2393fn isNonErr(self: *Self, ty: Type, operand: MCValue) !MCValue {
2359 _ = operand;2394 const is_err_result = try self.isErr(ty, operand);
2360 // Here you can specialize this instruction if it makes sense to, otherwise the default2395 switch (is_err_result) {
2361 // will call isNull and invert the result.2396 .compare_flags_unsigned => |op| {
2362 return self.fail("TODO call isErr and invert the result", .{});2397 assert(op == .gt);
2398 return MCValue{ .compare_flags_unsigned = .lte };
2399 },
2400 .immediate => |imm| {
2401 assert(imm == 0);
2402 return MCValue{ .immediate = 1 };
2403 },
2404 else => unreachable,
2405 }
2363}2406}
23642407
2365fn airIsNull(self: *Self, inst: Air.Inst.Index) !void {2408fn airIsNull(self: *Self, inst: Air.Inst.Index) !void {
2366 const un_op = self.air.instructions.items(.data)[inst].un_op;2409 const un_op = self.air.instructions.items(.data)[inst].un_op;
2410
2411 try self.spillCompareFlagsIfOccupied();
2412 self.compare_flags_inst = inst;
2413
2367 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {2414 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
2368 const operand = try self.resolveInst(un_op);2415 const operand = try self.resolveInst(un_op);
2369 break :result try self.isNull(operand);2416 const ty = self.air.typeOf(un_op);
2417 break :result try self.isNull(ty, operand);
2370 };2418 };
2371 return self.finishAir(inst, result, .{ un_op, .none, .none });2419 return self.finishAir(inst, result, .{ un_op, .none, .none });
2372}2420}
...@@ -2375,6 +2423,7 @@ fn airIsNullPtr(self: *Self, inst: Air.Inst.Index) !void {...@@ -2375,6 +2423,7 @@ fn airIsNullPtr(self: *Self, inst: Air.Inst.Index) !void {
2375 const un_op = self.air.instructions.items(.data)[inst].un_op;2423 const un_op = self.air.instructions.items(.data)[inst].un_op;
2376 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {2424 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
2377 const operand_ptr = try self.resolveInst(un_op);2425 const operand_ptr = try self.resolveInst(un_op);
2426 const ptr_ty = self.air.typeOf(un_op);
2378 const operand: MCValue = blk: {2427 const operand: MCValue = blk: {
2379 if (self.reuseOperand(inst, un_op, 0, operand_ptr)) {2428 if (self.reuseOperand(inst, un_op, 0, operand_ptr)) {
2380 // The MCValue that holds the pointer can be re-used as the value.2429 // The MCValue that holds the pointer can be re-used as the value.
...@@ -2383,8 +2432,8 @@ fn airIsNullPtr(self: *Self, inst: Air.Inst.Index) !void {...@@ -2383,8 +2432,8 @@ fn airIsNullPtr(self: *Self, inst: Air.Inst.Index) !void {
2383 break :blk try self.allocRegOrMem(inst, true);2432 break :blk try self.allocRegOrMem(inst, true);
2384 }2433 }
2385 };2434 };
2386 try self.load(operand, operand_ptr, self.air.typeOf(un_op));2435 try self.load(operand, operand_ptr, ptr_ty);
2387 break :result try self.isNull(operand);2436 break :result try self.isNull(ptr_ty.elemType(), operand);
2388 };2437 };
2389 return self.finishAir(inst, result, .{ un_op, .none, .none });2438 return self.finishAir(inst, result, .{ un_op, .none, .none });
2390}2439}
...@@ -2393,7 +2442,8 @@ fn airIsNonNull(self: *Self, inst: Air.Inst.Index) !void {...@@ -2393,7 +2442,8 @@ fn airIsNonNull(self: *Self, inst: Air.Inst.Index) !void {
2393 const un_op = self.air.instructions.items(.data)[inst].un_op;2442 const un_op = self.air.instructions.items(.data)[inst].un_op;
2394 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {2443 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
2395 const operand = try self.resolveInst(un_op);2444 const operand = try self.resolveInst(un_op);
2396 break :result try self.isNonNull(operand);2445 const ty = self.air.typeOf(un_op);
2446 break :result try self.isNonNull(ty, operand);
2397 };2447 };
2398 return self.finishAir(inst, result, .{ un_op, .none, .none });2448 return self.finishAir(inst, result, .{ un_op, .none, .none });
2399}2449}
...@@ -2402,6 +2452,7 @@ fn airIsNonNullPtr(self: *Self, inst: Air.Inst.Index) !void {...@@ -2402,6 +2452,7 @@ fn airIsNonNullPtr(self: *Self, inst: Air.Inst.Index) !void {
2402 const un_op = self.air.instructions.items(.data)[inst].un_op;2452 const un_op = self.air.instructions.items(.data)[inst].un_op;
2403 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {2453 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
2404 const operand_ptr = try self.resolveInst(un_op);2454 const operand_ptr = try self.resolveInst(un_op);
2455 const ptr_ty = self.air.typeOf(un_op);
2405 const operand: MCValue = blk: {2456 const operand: MCValue = blk: {
2406 if (self.reuseOperand(inst, un_op, 0, operand_ptr)) {2457 if (self.reuseOperand(inst, un_op, 0, operand_ptr)) {
2407 // The MCValue that holds the pointer can be re-used as the value.2458 // The MCValue that holds the pointer can be re-used as the value.
...@@ -2410,8 +2461,8 @@ fn airIsNonNullPtr(self: *Self, inst: Air.Inst.Index) !void {...@@ -2410,8 +2461,8 @@ fn airIsNonNullPtr(self: *Self, inst: Air.Inst.Index) !void {
2410 break :blk try self.allocRegOrMem(inst, true);2461 break :blk try self.allocRegOrMem(inst, true);
2411 }2462 }
2412 };2463 };
2413 try self.load(operand, operand_ptr, self.air.typeOf(un_op));2464 try self.load(operand, operand_ptr, ptr_ty);
2414 break :result try self.isNonNull(operand);2465 break :result try self.isNonNull(ptr_ty.elemType(), operand);
2415 };2466 };
2416 return self.finishAir(inst, result, .{ un_op, .none, .none });2467 return self.finishAir(inst, result, .{ un_op, .none, .none });
2417}2468}
...@@ -2420,7 +2471,8 @@ fn airIsErr(self: *Self, inst: Air.Inst.Index) !void {...@@ -2420,7 +2471,8 @@ fn airIsErr(self: *Self, inst: Air.Inst.Index) !void {
2420 const un_op = self.air.instructions.items(.data)[inst].un_op;2471 const un_op = self.air.instructions.items(.data)[inst].un_op;
2421 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {2472 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
2422 const operand = try self.resolveInst(un_op);2473 const operand = try self.resolveInst(un_op);
2423 break :result try self.isErr(operand);2474 const ty = self.air.typeOf(un_op);
2475 break :result try self.isErr(ty, operand);
2424 };2476 };
2425 return self.finishAir(inst, result, .{ un_op, .none, .none });2477 return self.finishAir(inst, result, .{ un_op, .none, .none });
2426}2478}
...@@ -2429,6 +2481,7 @@ fn airIsErrPtr(self: *Self, inst: Air.Inst.Index) !void {...@@ -2429,6 +2481,7 @@ fn airIsErrPtr(self: *Self, inst: Air.Inst.Index) !void {
2429 const un_op = self.air.instructions.items(.data)[inst].un_op;2481 const un_op = self.air.instructions.items(.data)[inst].un_op;
2430 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {2482 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
2431 const operand_ptr = try self.resolveInst(un_op);2483 const operand_ptr = try self.resolveInst(un_op);
2484 const ptr_ty = self.air.typeOf(un_op);
2432 const operand: MCValue = blk: {2485 const operand: MCValue = blk: {
2433 if (self.reuseOperand(inst, un_op, 0, operand_ptr)) {2486 if (self.reuseOperand(inst, un_op, 0, operand_ptr)) {
2434 // The MCValue that holds the pointer can be re-used as the value.2487 // The MCValue that holds the pointer can be re-used as the value.
...@@ -2437,8 +2490,8 @@ fn airIsErrPtr(self: *Self, inst: Air.Inst.Index) !void {...@@ -2437,8 +2490,8 @@ fn airIsErrPtr(self: *Self, inst: Air.Inst.Index) !void {
2437 break :blk try self.allocRegOrMem(inst, true);2490 break :blk try self.allocRegOrMem(inst, true);
2438 }2491 }
2439 };2492 };
2440 try self.load(operand, operand_ptr, self.air.typeOf(un_op));2493 try self.load(operand, operand_ptr, ptr_ty);
2441 break :result try self.isErr(operand);2494 break :result try self.isErr(ptr_ty.elemType(), operand);
2442 };2495 };
2443 return self.finishAir(inst, result, .{ un_op, .none, .none });2496 return self.finishAir(inst, result, .{ un_op, .none, .none });
2444}2497}
...@@ -2447,7 +2500,8 @@ fn airIsNonErr(self: *Self, inst: Air.Inst.Index) !void {...@@ -2447,7 +2500,8 @@ fn airIsNonErr(self: *Self, inst: Air.Inst.Index) !void {
2447 const un_op = self.air.instructions.items(.data)[inst].un_op;2500 const un_op = self.air.instructions.items(.data)[inst].un_op;
2448 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {2501 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
2449 const operand = try self.resolveInst(un_op);2502 const operand = try self.resolveInst(un_op);
2450 break :result try self.isNonErr(operand);2503 const ty = self.air.typeOf(un_op);
2504 break :result try self.isNonErr(ty, operand);
2451 };2505 };
2452 return self.finishAir(inst, result, .{ un_op, .none, .none });2506 return self.finishAir(inst, result, .{ un_op, .none, .none });
2453}2507}
...@@ -2456,6 +2510,7 @@ fn airIsNonErrPtr(self: *Self, inst: Air.Inst.Index) !void {...@@ -2456,6 +2510,7 @@ fn airIsNonErrPtr(self: *Self, inst: Air.Inst.Index) !void {
2456 const un_op = self.air.instructions.items(.data)[inst].un_op;2510 const un_op = self.air.instructions.items(.data)[inst].un_op;
2457 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {2511 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
2458 const operand_ptr = try self.resolveInst(un_op);2512 const operand_ptr = try self.resolveInst(un_op);
2513 const ptr_ty = self.air.typeOf(un_op);
2459 const operand: MCValue = blk: {2514 const operand: MCValue = blk: {
2460 if (self.reuseOperand(inst, un_op, 0, operand_ptr)) {2515 if (self.reuseOperand(inst, un_op, 0, operand_ptr)) {
2461 // The MCValue that holds the pointer can be re-used as the value.2516 // The MCValue that holds the pointer can be re-used as the value.
...@@ -2464,8 +2519,8 @@ fn airIsNonErrPtr(self: *Self, inst: Air.Inst.Index) !void {...@@ -2464,8 +2519,8 @@ fn airIsNonErrPtr(self: *Self, inst: Air.Inst.Index) !void {
2464 break :blk try self.allocRegOrMem(inst, true);2519 break :blk try self.allocRegOrMem(inst, true);
2465 }2520 }
2466 };2521 };
2467 try self.load(operand, operand_ptr, self.air.typeOf(un_op));2522 try self.load(operand, operand_ptr, ptr_ty);
2468 break :result try self.isNonErr(operand);2523 break :result try self.isNonErr(ptr_ty.elemType(), operand);
2469 };2524 };
2470 return self.finishAir(inst, result, .{ un_op, .none, .none });2525 return self.finishAir(inst, result, .{ un_op, .none, .none });
2471}2526}
...@@ -3365,31 +3420,32 @@ fn genTypedValue(self: *Self, typed_value: TypedValue) InnerError!MCValue {...@@ -3365,31 +3420,32 @@ fn genTypedValue(self: *Self, typed_value: TypedValue) InnerError!MCValue {
3365 }3420 }
3366 },3421 },
3367 .ErrorSet => {3422 .ErrorSet => {
3368 switch (typed_value.val.tag()) {3423 const err_name = typed_value.val.castTag(.@"error").?.data.name;
3369 .@"error" => {3424 const module = self.bin_file.options.module.?;
3370 const err_name = typed_value.val.castTag(.@"error").?.data.name;3425 const global_error_set = module.global_error_set;
3371 const module = self.bin_file.options.module.?;3426 const error_index = global_error_set.get(err_name).?;
3372 const global_error_set = module.global_error_set;3427 return MCValue{ .immediate = error_index };
3373 const error_index = global_error_set.get(err_name).?;
3374 return MCValue{ .immediate = error_index };
3375 },
3376 else => {
3377 // In this case we are rendering an error union which has a 0 bits payload.
3378 return MCValue{ .immediate = 0 };
3379 },
3380 }
3381 },3428 },
3382 .ErrorUnion => {3429 .ErrorUnion => {
3383 const error_type = typed_value.ty.errorUnionSet();3430 const error_type = typed_value.ty.errorUnionSet();
3384 const payload_type = typed_value.ty.errorUnionPayload();3431 const payload_type = typed_value.ty.errorUnionPayload();
3385 const sub_val = typed_value.val.castTag(.eu_payload).?.data;
33863432
3387 if (!payload_type.hasCodeGenBits()) {3433 if (typed_value.val.castTag(.eu_payload)) |pl| {
3388 // We use the error type directly as the type.3434 if (!payload_type.hasCodeGenBits()) {
3389 return self.genTypedValue(.{ .ty = error_type, .val = sub_val });3435 // We use the error type directly as the type.
3390 }3436 return MCValue{ .immediate = 0 };
3437 }
33913438
3392 return self.fail("TODO implement error union const of type '{}'", .{typed_value.ty});3439 _ = pl;
3440 return self.fail("TODO implement error union const of type '{}' (non-error)", .{typed_value.ty});
3441 } else {
3442 if (!payload_type.hasCodeGenBits()) {
3443 // We use the error type directly as the type.
3444 return self.genTypedValue(.{ .ty = error_type, .val = typed_value.val });
3445 }
3446
3447 return self.fail("TODO implement error union const of type '{}' (error)", .{typed_value.ty});
3448 }
3393 },3449 },
3394 else => return self.fail("TODO implement const of type '{}'", .{typed_value.ty}),3450 else => return self.fail("TODO implement const of type '{}'", .{typed_value.ty}),
3395 }3451 }
test/stage2/arm.zig+69
...@@ -568,4 +568,73 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -568,4 +568,73 @@ pub fn addCases(ctx: *TestContext) !void {
568 "",568 "",
569 );569 );
570 }570 }
571
572 {
573 var case = ctx.exe("optionals", linux_arm);
574 case.addCompareOutput(
575 \\var x: u32 = 42;
576 \\
577 \\pub fn main() void {
578 \\ var p: ?*u32 = null;
579 \\ assert(p == null);
580 \\ p = &x;
581 \\ assert(p != null);
582 \\}
583 \\
584 \\fn assert(ok: bool) void {
585 \\ if (!ok) unreachable;
586 \\}
587 ,
588 "",
589 );
590 }
591
592 {
593 var case = ctx.exe("errors", linux_arm);
594 case.addCompareOutput(
595 \\pub fn main() void {
596 \\ foo() catch print();
597 \\}
598 \\
599 \\fn foo() anyerror!void {}
600 \\
601 \\fn print() void {
602 \\ asm volatile ("svc #0"
603 \\ :
604 \\ : [number] "{r7}" (4),
605 \\ [arg1] "{r0}" (1),
606 \\ [arg2] "{r1}" (@ptrToInt("Hello, World!\n")),
607 \\ [arg3] "{r2}" ("Hello, World!\n".len),
608 \\ : "memory"
609 \\ );
610 \\ return;
611 \\}
612 ,
613 "",
614 );
615
616 case.addCompareOutput(
617 \\pub fn main() void {
618 \\ foo() catch print();
619 \\}
620 \\
621 \\fn foo() anyerror!void {
622 \\ return error.Test;
623 \\}
624 \\
625 \\fn print() void {
626 \\ asm volatile ("svc #0"
627 \\ :
628 \\ : [number] "{r7}" (4),
629 \\ [arg1] "{r0}" (1),
630 \\ [arg2] "{r1}" (@ptrToInt("Hello, World!\n")),
631 \\ [arg3] "{r2}" ("Hello, World!\n".len),
632 \\ : "memory"
633 \\ );
634 \\ return;
635 \\}
636 ,
637 "Hello, World!\n",
638 );
639 }
571}640}