| author | |
| committer | |
| log | cb785b9c6ba705fcb507fdfa470e27b5e82ce1c5 |
| tree | 59bd1aa803f9669ba434aa7e7890c8c5cd283055 |
| parent | 008b0ec5e58fc7e31f3b989868a7d1ea4df3f41d |
New AIR instruction: `optional_payload_ptr_set`
It's like `optional_payload_ptr` except it sets the non-null bit.
When storing to the payload via a result location that is an optional,
`optional_payload_ptr_set` is now emitted. There is a new algorithm in
`zirCoerceResultPtr` which stores a dummy value through the result
pointer into a temporary block, and then pops off the AIR instructions
from the temporary block in order to determine how to transform the
result location pointer in case any in-between coercions need to happen.
Fixes a couple of behavior tests regarding optionals.13 files changed, 231 insertions(+), 39 deletions(-)
src/Air.zig+4| ... | @@ -336,6 +336,9 @@ pub const Inst = struct { | ... | @@ -336,6 +336,9 @@ pub const Inst = struct { |
| 336 | /// *?T => *T. If the value is null, undefined behavior. | 336 | /// *?T => *T. If the value is null, undefined behavior. |
| 337 | /// Uses the `ty_op` field. | 337 | /// Uses the `ty_op` field. |
| 338 | optional_payload_ptr, | 338 | optional_payload_ptr, |
| 339 | /// *?T => *T. Sets the value to non-null with an undefined payload value. | ||
| 340 | /// Uses the `ty_op` field. | ||
| 341 | optional_payload_ptr_set, | ||
| 339 | /// Given a payload value, wraps it in an optional type. | 342 | /// Given a payload value, wraps it in an optional type. |
| 340 | /// Uses the `ty_op` field. | 343 | /// Uses the `ty_op` field. |
| 341 | wrap_optional, | 344 | wrap_optional, |
| ... | @@ -728,6 +731,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { | ... | @@ -728,6 +731,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { |
| 728 | .trunc, | 731 | .trunc, |
| 729 | .optional_payload, | 732 | .optional_payload, |
| 730 | .optional_payload_ptr, | 733 | .optional_payload_ptr, |
| 734 | .optional_payload_ptr_set, | ||
| 731 | .wrap_optional, | 735 | .wrap_optional, |
| 732 | .unwrap_errunion_payload, | 736 | .unwrap_errunion_payload, |
| 733 | .unwrap_errunion_err, | 737 | .unwrap_errunion_err, |
src/Liveness.zig+1| ... | @@ -292,6 +292,7 @@ fn analyzeInst( | ... | @@ -292,6 +292,7 @@ fn analyzeInst( |
| 292 | .trunc, | 292 | .trunc, |
| 293 | .optional_payload, | 293 | .optional_payload, |
| 294 | .optional_payload_ptr, | 294 | .optional_payload_ptr, |
| 295 | .optional_payload_ptr_set, | ||
| 295 | .wrap_optional, | 296 | .wrap_optional, |
| 296 | .unwrap_errunion_payload, | 297 | .unwrap_errunion_payload, |
| 297 | .unwrap_errunion_err, | 298 | .unwrap_errunion_err, |
src/Sema.zig+88-5| ... | @@ -1414,9 +1414,10 @@ fn zirCoerceResultPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE | ... | @@ -1414,9 +1414,10 @@ fn zirCoerceResultPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE |
| 1414 | const pointee_ty = try sema.resolveType(block, src, bin_inst.lhs); | 1414 | const pointee_ty = try sema.resolveType(block, src, bin_inst.lhs); |
| 1415 | const ptr = sema.resolveInst(bin_inst.rhs); | 1415 | const ptr = sema.resolveInst(bin_inst.rhs); |
| 1416 | 1416 | ||
| 1417 | const addr_space = target_util.defaultAddressSpace(sema.mod.getTarget(), .local); | ||
| 1417 | const ptr_ty = try Type.ptr(sema.arena, .{ | 1418 | const ptr_ty = try Type.ptr(sema.arena, .{ |
| 1418 | .pointee_type = pointee_ty, | 1419 | .pointee_type = pointee_ty, |
| 1419 | .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .local), | 1420 | .@"addrspace" = addr_space, |
| 1420 | }); | 1421 | }); |
| 1421 | 1422 | ||
| 1422 | if (Air.refToIndex(ptr)) |ptr_inst| { | 1423 | if (Air.refToIndex(ptr)) |ptr_inst| { |
| ... | @@ -1430,8 +1431,15 @@ fn zirCoerceResultPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE | ... | @@ -1430,8 +1431,15 @@ fn zirCoerceResultPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE |
| 1430 | // for the inferred allocation. | 1431 | // for the inferred allocation. |
| 1431 | // This instruction will not make it to codegen; it is only to participate | 1432 | // This instruction will not make it to codegen; it is only to participate |
| 1432 | // in the `stored_inst_list` of the `inferred_alloc`. | 1433 | // in the `stored_inst_list` of the `inferred_alloc`. |
| 1433 | const operand = try block.addBitCast(pointee_ty, .void_value); | 1434 | var trash_block = block.makeSubBlock(); |
| 1435 | defer trash_block.instructions.deinit(sema.gpa); | ||
| 1436 | const operand = try trash_block.addBitCast(pointee_ty, .void_value); | ||
| 1437 | |||
| 1434 | try inferred_alloc.stored_inst_list.append(sema.arena, operand); | 1438 | try inferred_alloc.stored_inst_list.append(sema.arena, operand); |
| 1439 | |||
| 1440 | try sema.requireRuntimeBlock(block, src); | ||
| 1441 | const bitcasted_ptr = try block.addBitCast(ptr_ty, ptr); | ||
| 1442 | return bitcasted_ptr; | ||
| 1435 | }, | 1443 | }, |
| 1436 | .inferred_alloc_comptime => { | 1444 | .inferred_alloc_comptime => { |
| 1437 | const iac = ptr_val.castTag(.inferred_alloc_comptime).?; | 1445 | const iac = ptr_val.castTag(.inferred_alloc_comptime).?; |
| ... | @@ -1456,9 +1464,78 @@ fn zirCoerceResultPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE | ... | @@ -1456,9 +1464,78 @@ fn zirCoerceResultPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE |
| 1456 | } | 1464 | } |
| 1457 | } | 1465 | } |
| 1458 | } | 1466 | } |
| 1467 | |||
| 1459 | try sema.requireRuntimeBlock(block, src); | 1468 | try sema.requireRuntimeBlock(block, src); |
| 1460 | const bitcasted_ptr = try block.addBitCast(ptr_ty, ptr); | 1469 | |
| 1461 | return bitcasted_ptr; | 1470 | // Make a dummy store through the pointer to test the coercion. |
| 1471 | // We will then use the generated instructions to decide what | ||
| 1472 | // kind of transformations to make on the result pointer. | ||
| 1473 | var trash_block = block.makeSubBlock(); | ||
| 1474 | defer trash_block.instructions.deinit(sema.gpa); | ||
| 1475 | |||
| 1476 | const dummy_operand = try trash_block.addBitCast(pointee_ty, .void_value); | ||
| 1477 | try sema.storePtr(&trash_block, src, ptr, dummy_operand); | ||
| 1478 | |||
| 1479 | { | ||
| 1480 | const air_tags = sema.air_instructions.items(.tag); | ||
| 1481 | |||
| 1482 | //std.debug.print("dummy storePtr instructions:\n", .{}); | ||
| 1483 | //for (trash_block.instructions.items) |item| { | ||
| 1484 | // std.debug.print(" {s}\n", .{@tagName(air_tags[item])}); | ||
| 1485 | //} | ||
| 1486 | |||
| 1487 | // The last one is always `store`. | ||
| 1488 | const trash_inst = trash_block.instructions.pop(); | ||
| 1489 | assert(air_tags[trash_inst] == .store); | ||
| 1490 | assert(trash_inst == sema.air_instructions.len - 1); | ||
| 1491 | sema.air_instructions.len -= 1; | ||
| 1492 | } | ||
| 1493 | |||
| 1494 | var new_ptr = ptr; | ||
| 1495 | |||
| 1496 | while (true) { | ||
| 1497 | const air_tags = sema.air_instructions.items(.tag); | ||
| 1498 | const air_datas = sema.air_instructions.items(.data); | ||
| 1499 | const trash_inst = trash_block.instructions.pop(); | ||
| 1500 | switch (air_tags[trash_inst]) { | ||
| 1501 | .bitcast => { | ||
| 1502 | if (Air.indexToRef(trash_inst) == dummy_operand) { | ||
| 1503 | return block.addBitCast(ptr_ty, new_ptr); | ||
| 1504 | } | ||
| 1505 | const ty_op = air_datas[trash_inst].ty_op; | ||
| 1506 | const operand_ty = sema.getTmpAir().typeOf(ty_op.operand); | ||
| 1507 | const ptr_operand_ty = try Type.ptr(sema.arena, .{ | ||
| 1508 | .pointee_type = operand_ty, | ||
| 1509 | .@"addrspace" = addr_space, | ||
| 1510 | }); | ||
| 1511 | new_ptr = try block.addBitCast(ptr_operand_ty, new_ptr); | ||
| 1512 | }, | ||
| 1513 | .wrap_optional => { | ||
| 1514 | const ty_op = air_datas[trash_inst].ty_op; | ||
| 1515 | const payload_ty = sema.getTmpAir().typeOf(ty_op.operand); | ||
| 1516 | const ptr_payload_ty = try Type.ptr(sema.arena, .{ | ||
| 1517 | .pointee_type = payload_ty, | ||
| 1518 | .@"addrspace" = addr_space, | ||
| 1519 | }); | ||
| 1520 | new_ptr = try block.addTyOp(.optional_payload_ptr_set, ptr_payload_ty, new_ptr); | ||
| 1521 | }, | ||
| 1522 | .wrap_errunion_err => { | ||
| 1523 | return sema.fail(block, src, "TODO coerce_result_ptr wrap_errunion_err", .{}); | ||
| 1524 | }, | ||
| 1525 | .wrap_errunion_payload => { | ||
| 1526 | return sema.fail(block, src, "TODO coerce_result_ptr wrap_errunion_payload", .{}); | ||
| 1527 | }, | ||
| 1528 | else => { | ||
| 1529 | if (std.debug.runtime_safety) { | ||
| 1530 | std.debug.panic("unexpected AIR tag for coerce_result_ptr: {s}", .{ | ||
| 1531 | air_tags[trash_inst], | ||
| 1532 | }); | ||
| 1533 | } else { | ||
| 1534 | unreachable; | ||
| 1535 | } | ||
| 1536 | }, | ||
| 1537 | } | ||
| 1538 | } else unreachable; // TODO should not need else unreachable | ||
| 1462 | } | 1539 | } |
| 1463 | 1540 | ||
| 1464 | pub fn analyzeStructDecl( | 1541 | pub fn analyzeStructDecl( |
| ... | @@ -2365,7 +2442,13 @@ fn validateUnionInit( | ... | @@ -2365,7 +2442,13 @@ fn validateUnionInit( |
| 2365 | // Otherwise, the bitcast should be preserved and a store instruction should be | 2442 | // Otherwise, the bitcast should be preserved and a store instruction should be |
| 2366 | // emitted to store the constant union value through the bitcast. | 2443 | // emitted to store the constant union value through the bitcast. |
| 2367 | }, | 2444 | }, |
| 2368 | else => unreachable, | 2445 | else => |t| { |
| 2446 | if (std.debug.runtime_safety) { | ||
| 2447 | std.debug.panic("unexpected AIR tag for union pointer: {s}", .{@tagName(t)}); | ||
| 2448 | } else { | ||
| 2449 | unreachable; | ||
| 2450 | } | ||
| 2451 | }, | ||
| 2369 | } | 2452 | } |
| 2370 | 2453 | ||
| 2371 | // Otherwise, we set the new union tag now. | 2454 | // Otherwise, we set the new union tag now. |
src/arch/aarch64/CodeGen.zig+7| ... | @@ -592,6 +592,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { | ... | @@ -592,6 +592,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { |
| 592 | 592 | ||
| 593 | .optional_payload => try self.airOptionalPayload(inst), | 593 | .optional_payload => try self.airOptionalPayload(inst), |
| 594 | .optional_payload_ptr => try self.airOptionalPayloadPtr(inst), | 594 | .optional_payload_ptr => try self.airOptionalPayloadPtr(inst), |
| 595 | .optional_payload_ptr_set => try self.airOptionalPayloadPtrSet(inst), | ||
| 595 | .unwrap_errunion_err => try self.airUnwrapErrErr(inst), | 596 | .unwrap_errunion_err => try self.airUnwrapErrErr(inst), |
| 596 | .unwrap_errunion_payload => try self.airUnwrapErrPayload(inst), | 597 | .unwrap_errunion_payload => try self.airUnwrapErrPayload(inst), |
| 597 | .unwrap_errunion_err_ptr => try self.airUnwrapErrErrPtr(inst), | 598 | .unwrap_errunion_err_ptr => try self.airUnwrapErrErrPtr(inst), |
| ... | @@ -1010,6 +1011,12 @@ fn airOptionalPayloadPtr(self: *Self, inst: Air.Inst.Index) !void { | ... | @@ -1010,6 +1011,12 @@ fn airOptionalPayloadPtr(self: *Self, inst: Air.Inst.Index) !void { |
| 1010 | return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); | 1011 | return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); |
| 1011 | } | 1012 | } |
| 1012 | 1013 | ||
| 1014 | fn airOptionalPayloadPtrSet(self: *Self, inst: Air.Inst.Index) !void { | ||
| 1015 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; | ||
| 1016 | const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement .optional_payload_ptr_set for {}", .{self.target.cpu.arch}); | ||
| 1017 | return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); | ||
| 1018 | } | ||
| 1019 | |||
| 1013 | fn airUnwrapErrErr(self: *Self, inst: Air.Inst.Index) !void { | 1020 | fn airUnwrapErrErr(self: *Self, inst: Air.Inst.Index) !void { |
| 1014 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; | 1021 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; |
| 1015 | const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement unwrap error union error for {}", .{self.target.cpu.arch}); | 1022 | const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement unwrap error union error for {}", .{self.target.cpu.arch}); |
src/arch/arm/CodeGen.zig+7| ... | @@ -510,6 +510,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { | ... | @@ -510,6 +510,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { |
| 510 | 510 | ||
| 511 | .optional_payload => try self.airOptionalPayload(inst), | 511 | .optional_payload => try self.airOptionalPayload(inst), |
| 512 | .optional_payload_ptr => try self.airOptionalPayloadPtr(inst), | 512 | .optional_payload_ptr => try self.airOptionalPayloadPtr(inst), |
| 513 | .optional_payload_ptr_set => try self.airOptionalPayloadPtrSet(inst), | ||
| 513 | .unwrap_errunion_err => try self.airUnwrapErrErr(inst), | 514 | .unwrap_errunion_err => try self.airUnwrapErrErr(inst), |
| 514 | .unwrap_errunion_payload => try self.airUnwrapErrPayload(inst), | 515 | .unwrap_errunion_payload => try self.airUnwrapErrPayload(inst), |
| 515 | .unwrap_errunion_err_ptr => try self.airUnwrapErrErrPtr(inst), | 516 | .unwrap_errunion_err_ptr => try self.airUnwrapErrErrPtr(inst), |
| ... | @@ -1008,6 +1009,12 @@ fn airOptionalPayloadPtr(self: *Self, inst: Air.Inst.Index) !void { | ... | @@ -1008,6 +1009,12 @@ fn airOptionalPayloadPtr(self: *Self, inst: Air.Inst.Index) !void { |
| 1008 | return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); | 1009 | return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); |
| 1009 | } | 1010 | } |
| 1010 | 1011 | ||
| 1012 | fn airOptionalPayloadPtrSet(self: *Self, inst: Air.Inst.Index) !void { | ||
| 1013 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; | ||
| 1014 | const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement .optional_payload_ptr_set for {}", .{self.target.cpu.arch}); | ||
| 1015 | return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); | ||
| 1016 | } | ||
| 1017 | |||
| 1011 | fn airUnwrapErrErr(self: *Self, inst: Air.Inst.Index) !void { | 1018 | fn airUnwrapErrErr(self: *Self, inst: Air.Inst.Index) !void { |
| 1012 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; | 1019 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; |
| 1013 | const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement unwrap error union error for {}", .{self.target.cpu.arch}); | 1020 | const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement unwrap error union error for {}", .{self.target.cpu.arch}); |
src/arch/riscv64/CodeGen.zig+7| ... | @@ -505,6 +505,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { | ... | @@ -505,6 +505,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { |
| 505 | 505 | ||
| 506 | .optional_payload => try self.airOptionalPayload(inst), | 506 | .optional_payload => try self.airOptionalPayload(inst), |
| 507 | .optional_payload_ptr => try self.airOptionalPayloadPtr(inst), | 507 | .optional_payload_ptr => try self.airOptionalPayloadPtr(inst), |
| 508 | .optional_payload_ptr_set => try self.airOptionalPayloadPtrSet(inst), | ||
| 508 | .unwrap_errunion_err => try self.airUnwrapErrErr(inst), | 509 | .unwrap_errunion_err => try self.airUnwrapErrErr(inst), |
| 509 | .unwrap_errunion_payload => try self.airUnwrapErrPayload(inst), | 510 | .unwrap_errunion_payload => try self.airUnwrapErrPayload(inst), |
| 510 | .unwrap_errunion_err_ptr => try self.airUnwrapErrErrPtr(inst), | 511 | .unwrap_errunion_err_ptr => try self.airUnwrapErrErrPtr(inst), |
| ... | @@ -926,6 +927,12 @@ fn airOptionalPayloadPtr(self: *Self, inst: Air.Inst.Index) !void { | ... | @@ -926,6 +927,12 @@ fn airOptionalPayloadPtr(self: *Self, inst: Air.Inst.Index) !void { |
| 926 | return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); | 927 | return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); |
| 927 | } | 928 | } |
| 928 | 929 | ||
| 930 | fn airOptionalPayloadPtrSet(self: *Self, inst: Air.Inst.Index) !void { | ||
| 931 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; | ||
| 932 | const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement .optional_payload_ptr_set for {}", .{self.target.cpu.arch}); | ||
| 933 | return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); | ||
| 934 | } | ||
| 935 | |||
| 929 | fn airUnwrapErrErr(self: *Self, inst: Air.Inst.Index) !void { | 936 | fn airUnwrapErrErr(self: *Self, inst: Air.Inst.Index) !void { |
| 930 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; | 937 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; |
| 931 | const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement unwrap error union error for {}", .{self.target.cpu.arch}); | 938 | const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement unwrap error union error for {}", .{self.target.cpu.arch}); |
src/arch/x86_64/CodeGen.zig+10| ... | @@ -578,6 +578,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { | ... | @@ -578,6 +578,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { |
| 578 | 578 | ||
| 579 | .optional_payload => try self.airOptionalPayload(inst), | 579 | .optional_payload => try self.airOptionalPayload(inst), |
| 580 | .optional_payload_ptr => try self.airOptionalPayloadPtr(inst), | 580 | .optional_payload_ptr => try self.airOptionalPayloadPtr(inst), |
| 581 | .optional_payload_ptr_set => try self.airOptionalPayloadPtrSet(inst), | ||
| 581 | .unwrap_errunion_err => try self.airUnwrapErrErr(inst), | 582 | .unwrap_errunion_err => try self.airUnwrapErrErr(inst), |
| 582 | .unwrap_errunion_payload => try self.airUnwrapErrPayload(inst), | 583 | .unwrap_errunion_payload => try self.airUnwrapErrPayload(inst), |
| 583 | .unwrap_errunion_err_ptr => try self.airUnwrapErrErrPtr(inst), | 584 | .unwrap_errunion_err_ptr => try self.airUnwrapErrErrPtr(inst), |
| ... | @@ -1043,6 +1044,15 @@ fn airOptionalPayloadPtr(self: *Self, inst: Air.Inst.Index) !void { | ... | @@ -1043,6 +1044,15 @@ fn airOptionalPayloadPtr(self: *Self, inst: Air.Inst.Index) !void { |
| 1043 | return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); | 1044 | return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); |
| 1044 | } | 1045 | } |
| 1045 | 1046 | ||
| 1047 | fn airOptionalPayloadPtrSet(self: *Self, inst: Air.Inst.Index) !void { | ||
| 1048 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; | ||
| 1049 | const result: MCValue = if (self.liveness.isUnused(inst)) | ||
| 1050 | .dead | ||
| 1051 | else | ||
| 1052 | return self.fail("TODO implement .optional_payload_ptr_set for {}", .{self.target.cpu.arch}); | ||
| 1053 | return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); | ||
| 1054 | } | ||
| 1055 | |||
| 1046 | fn airUnwrapErrErr(self: *Self, inst: Air.Inst.Index) !void { | 1056 | fn airUnwrapErrErr(self: *Self, inst: Air.Inst.Index) !void { |
| 1047 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; | 1057 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; |
| 1048 | const result: MCValue = if (self.liveness.isUnused(inst)) | 1058 | const result: MCValue = if (self.liveness.isUnused(inst)) |
src/codegen/c.zig+30-2| ... | @@ -1125,8 +1125,9 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO | ... | @@ -1125,8 +1125,9 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO |
| 1125 | .shl, .shl_exact => try airBinOp(f, inst, " << "), | 1125 | .shl, .shl_exact => try airBinOp(f, inst, " << "), |
| 1126 | .not => try airNot (f, inst), | 1126 | .not => try airNot (f, inst), |
| 1127 | 1127 | ||
| 1128 | .optional_payload => try airOptionalPayload(f, inst), | 1128 | .optional_payload => try airOptionalPayload(f, inst), |
| 1129 | .optional_payload_ptr => try airOptionalPayload(f, inst), | 1129 | .optional_payload_ptr => try airOptionalPayload(f, inst), |
| 1130 | .optional_payload_ptr_set => try airOptionalPayloadPtrSet(f, inst), | ||
| 1130 | 1131 | ||
| 1131 | .is_err => try airIsErr(f, inst, "", ".", "!="), | 1132 | .is_err => try airIsErr(f, inst, "", ".", "!="), |
| 1132 | .is_non_err => try airIsErr(f, inst, "", ".", "=="), | 1133 | .is_non_err => try airIsErr(f, inst, "", ".", "=="), |
| ... | @@ -2218,6 +2219,33 @@ fn airOptionalPayload(f: *Function, inst: Air.Inst.Index) !CValue { | ... | @@ -2218,6 +2219,33 @@ fn airOptionalPayload(f: *Function, inst: Air.Inst.Index) !CValue { |
| 2218 | return local; | 2219 | return local; |
| 2219 | } | 2220 | } |
| 2220 | 2221 | ||
| 2222 | fn airOptionalPayloadPtrSet(f: *Function, inst: Air.Inst.Index) !CValue { | ||
| 2223 | const ty_op = f.air.instructions.items(.data)[inst].ty_op; | ||
| 2224 | const writer = f.object.writer(); | ||
| 2225 | const operand = try f.resolveInst(ty_op.operand); | ||
| 2226 | const operand_ty = f.air.typeOf(ty_op.operand); | ||
| 2227 | |||
| 2228 | const opt_ty = operand_ty.elemType(); | ||
| 2229 | |||
| 2230 | if (opt_ty.isPtrLikeOptional()) { | ||
| 2231 | // The payload and the optional are the same value. | ||
| 2232 | // Setting to non-null will be done when the payload is set. | ||
| 2233 | return operand; | ||
| 2234 | } | ||
| 2235 | |||
| 2236 | try writer.writeAll("("); | ||
| 2237 | try f.writeCValue(writer, operand); | ||
| 2238 | try writer.writeAll(")->is_null = false;\n"); | ||
| 2239 | |||
| 2240 | const inst_ty = f.air.typeOfIndex(inst); | ||
| 2241 | const local = try f.allocLocal(inst_ty, .Const); | ||
| 2242 | try writer.writeAll(" = &("); | ||
| 2243 | try f.writeCValue(writer, operand); | ||
| 2244 | |||
| 2245 | try writer.writeAll(")->payload;\n"); | ||
| 2246 | return local; | ||
| 2247 | } | ||
| 2248 | |||
| 2221 | fn airStructFieldPtr(f: *Function, inst: Air.Inst.Index) !CValue { | 2249 | fn airStructFieldPtr(f: *Function, inst: Air.Inst.Index) !CValue { |
| 2222 | if (f.liveness.isUnused(inst)) | 2250 | if (f.liveness.isUnused(inst)) |
| 2223 | // TODO this @as is needed because of a stage1 bug | 2251 | // TODO this @as is needed because of a stage1 bug |
src/codegen/llvm.zig+38-2| ... | @@ -1796,8 +1796,9 @@ pub const FuncGen = struct { | ... | @@ -1796,8 +1796,9 @@ pub const FuncGen = struct { |
| 1796 | .ptr_elem_val => try self.airPtrElemVal(inst), | 1796 | .ptr_elem_val => try self.airPtrElemVal(inst), |
| 1797 | .ptr_elem_ptr => try self.airPtrElemPtr(inst), | 1797 | .ptr_elem_ptr => try self.airPtrElemPtr(inst), |
| 1798 | 1798 | ||
| 1799 | .optional_payload => try self.airOptionalPayload(inst), | 1799 | .optional_payload => try self.airOptionalPayload(inst), |
| 1800 | .optional_payload_ptr => try self.airOptionalPayloadPtr(inst), | 1800 | .optional_payload_ptr => try self.airOptionalPayloadPtr(inst), |
| 1801 | .optional_payload_ptr_set => try self.airOptionalPayloadPtrSet(inst), | ||
| 1801 | 1802 | ||
| 1802 | .unwrap_errunion_payload => try self.airErrUnionPayload(inst, false), | 1803 | .unwrap_errunion_payload => try self.airErrUnionPayload(inst, false), |
| 1803 | .unwrap_errunion_payload_ptr => try self.airErrUnionPayload(inst, true), | 1804 | .unwrap_errunion_payload_ptr => try self.airErrUnionPayload(inst, true), |
| ... | @@ -2572,6 +2573,41 @@ pub const FuncGen = struct { | ... | @@ -2572,6 +2573,41 @@ pub const FuncGen = struct { |
| 2572 | return self.builder.buildInBoundsGEP(operand, &indices, indices.len, ""); | 2573 | return self.builder.buildInBoundsGEP(operand, &indices, indices.len, ""); |
| 2573 | } | 2574 | } |
| 2574 | 2575 | ||
| 2576 | fn airOptionalPayloadPtrSet(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { | ||
| 2577 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; | ||
| 2578 | const operand = try self.resolveInst(ty_op.operand); | ||
| 2579 | const optional_ty = self.air.typeOf(ty_op.operand).childType(); | ||
| 2580 | var buf: Type.Payload.ElemType = undefined; | ||
| 2581 | const payload_ty = optional_ty.optionalChild(&buf); | ||
| 2582 | const non_null_bit = self.context.intType(1).constAllOnes(); | ||
| 2583 | if (!payload_ty.hasCodeGenBits()) { | ||
| 2584 | // We have a pointer to a i1. We need to set it to 1 and then return the same pointer. | ||
| 2585 | _ = self.builder.buildStore(non_null_bit, operand); | ||
| 2586 | return operand; | ||
| 2587 | } | ||
| 2588 | if (optional_ty.isPtrLikeOptional()) { | ||
| 2589 | // The payload and the optional are the same value. | ||
| 2590 | // Setting to non-null will be done when the payload is set. | ||
| 2591 | return operand; | ||
| 2592 | } | ||
| 2593 | const index_type = self.context.intType(32); | ||
| 2594 | { | ||
| 2595 | // First set the non-null bit. | ||
| 2596 | const indices: [2]*const llvm.Value = .{ | ||
| 2597 | index_type.constNull(), // dereference the pointer | ||
| 2598 | index_type.constInt(1, .False), // second field is the payload | ||
| 2599 | }; | ||
| 2600 | const non_null_ptr = self.builder.buildInBoundsGEP(operand, &indices, indices.len, ""); | ||
| 2601 | _ = self.builder.buildStore(non_null_bit, non_null_ptr); | ||
| 2602 | } | ||
| 2603 | // Then return the payload pointer. | ||
| 2604 | const indices: [2]*const llvm.Value = .{ | ||
| 2605 | index_type.constNull(), // dereference the pointer | ||
| 2606 | index_type.constNull(), // first field is the payload | ||
| 2607 | }; | ||
| 2608 | return self.builder.buildInBoundsGEP(operand, &indices, indices.len, ""); | ||
| 2609 | } | ||
| 2610 | |||
| 2575 | fn airOptionalPayload(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { | 2611 | fn airOptionalPayload(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { |
| 2576 | if (self.liveness.isUnused(inst)) return null; | 2612 | if (self.liveness.isUnused(inst)) return null; |
| 2577 | 2613 |
src/codegen/wasm.zig+8| ... | @@ -881,6 +881,7 @@ pub const Context = struct { | ... | @@ -881,6 +881,7 @@ pub const Context = struct { |
| 881 | 881 | ||
| 882 | .optional_payload => self.airOptionalPayload(inst), | 882 | .optional_payload => self.airOptionalPayload(inst), |
| 883 | .optional_payload_ptr => self.airOptionalPayload(inst), | 883 | .optional_payload_ptr => self.airOptionalPayload(inst), |
| 884 | .optional_payload_ptr_set => self.airOptionalPayloadPtrSet(inst), | ||
| 884 | else => |tag| self.fail("TODO: Implement wasm inst: {s}", .{@tagName(tag)}), | 885 | else => |tag| self.fail("TODO: Implement wasm inst: {s}", .{@tagName(tag)}), |
| 885 | }; | 886 | }; |
| 886 | } | 887 | } |
| ... | @@ -1702,6 +1703,13 @@ pub const Context = struct { | ... | @@ -1702,6 +1703,13 @@ pub const Context = struct { |
| 1702 | return WValue{ .local = operand.multi_value.index + 1 }; | 1703 | return WValue{ .local = operand.multi_value.index + 1 }; |
| 1703 | } | 1704 | } |
| 1704 | 1705 | ||
| 1706 | fn airOptionalPayloadPtrSet(self: *Context, inst: Air.Inst.Index) InnerError!WValue { | ||
| 1707 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; | ||
| 1708 | const operand = self.resolveInst(ty_op.operand); | ||
| 1709 | _ = operand; | ||
| 1710 | return self.fail("TODO - wasm codegen for optional_payload_ptr_set", .{}); | ||
| 1711 | } | ||
| 1712 | |||
| 1705 | fn airWrapOptional(self: *Context, inst: Air.Inst.Index) InnerError!WValue { | 1713 | fn airWrapOptional(self: *Context, inst: Air.Inst.Index) InnerError!WValue { |
| 1706 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; | 1714 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; |
| 1707 | return self.resolveInst(ty_op.operand); | 1715 | return self.resolveInst(ty_op.operand); |
src/print_air.zig+1| ... | @@ -175,6 +175,7 @@ const Writer = struct { | ... | @@ -175,6 +175,7 @@ const Writer = struct { |
| 175 | .trunc, | 175 | .trunc, |
| 176 | .optional_payload, | 176 | .optional_payload, |
| 177 | .optional_payload_ptr, | 177 | .optional_payload_ptr, |
| 178 | .optional_payload_ptr_set, | ||
| 178 | .wrap_optional, | 179 | .wrap_optional, |
| 179 | .unwrap_errunion_payload, | 180 | .unwrap_errunion_payload, |
| 180 | .unwrap_errunion_err, | 181 | .unwrap_errunion_err, |
test/behavior/optional.zig+30| ... | @@ -73,3 +73,33 @@ test "optional with void type" { | ... | @@ -73,3 +73,33 @@ test "optional with void type" { |
| 73 | var x = Foo{ .x = null }; | 73 | var x = Foo{ .x = null }; |
| 74 | try expect(x.x == null); | 74 | try expect(x.x == null); |
| 75 | } | 75 | } |
| 76 | |||
| 77 | test "address of unwrap optional" { | ||
| 78 | const S = struct { | ||
| 79 | const Foo = struct { | ||
| 80 | a: i32, | ||
| 81 | }; | ||
| 82 | |||
| 83 | var global: ?Foo = null; | ||
| 84 | |||
| 85 | pub fn getFoo() anyerror!*Foo { | ||
| 86 | return &global.?; | ||
| 87 | } | ||
| 88 | }; | ||
| 89 | S.global = S.Foo{ .a = 1234 }; | ||
| 90 | const foo = S.getFoo() catch unreachable; | ||
| 91 | try expect(foo.a == 1234); | ||
| 92 | } | ||
| 93 | |||
| 94 | test "nested optional field in struct" { | ||
| 95 | const S2 = struct { | ||
| 96 | y: u8, | ||
| 97 | }; | ||
| 98 | const S1 = struct { | ||
| 99 | x: ?S2, | ||
| 100 | }; | ||
| 101 | var s = S1{ | ||
| 102 | .x = S2{ .y = 127 }, | ||
| 103 | }; | ||
| 104 | try expect(s.x.?.y == 127); | ||
| 105 | } |
test/behavior/optional_stage1.zig-30| ... | @@ -3,23 +3,6 @@ const testing = std.testing; | ... | @@ -3,23 +3,6 @@ const testing = std.testing; |
| 3 | const expect = testing.expect; | 3 | const expect = testing.expect; |
| 4 | const expectEqual = testing.expectEqual; | 4 | const expectEqual = testing.expectEqual; |
| 5 | 5 | ||
| 6 | test "address of unwrap optional" { | ||
| 7 | const S = struct { | ||
| 8 | const Foo = struct { | ||
| 9 | a: i32, | ||
| 10 | }; | ||
| 11 | |||
| 12 | var global: ?Foo = null; | ||
| 13 | |||
| 14 | pub fn getFoo() anyerror!*Foo { | ||
| 15 | return &global.?; | ||
| 16 | } | ||
| 17 | }; | ||
| 18 | S.global = S.Foo{ .a = 1234 }; | ||
| 19 | const foo = S.getFoo() catch unreachable; | ||
| 20 | try expect(foo.a == 1234); | ||
| 21 | } | ||
| 22 | |||
| 23 | test "equality compare optional with non-optional" { | 6 | test "equality compare optional with non-optional" { |
| 24 | try test_cmp_optional_non_optional(); | 7 | try test_cmp_optional_non_optional(); |
| 25 | comptime try test_cmp_optional_non_optional(); | 8 | comptime try test_cmp_optional_non_optional(); |
| ... | @@ -198,16 +181,3 @@ test "array of optional unaligned types" { | ... | @@ -198,16 +181,3 @@ test "array of optional unaligned types" { |
| 198 | i += 1; | 181 | i += 1; |
| 199 | try expectEqual(Enum.three, values[i].?.Num); | 182 | try expectEqual(Enum.three, values[i].?.Num); |
| 200 | } | 183 | } |
| 201 | |||
| 202 | test "nested optional field in struct" { | ||
| 203 | const S2 = struct { | ||
| 204 | y: u8, | ||
| 205 | }; | ||
| 206 | const S1 = struct { | ||
| 207 | x: ?S2, | ||
| 208 | }; | ||
| 209 | var s = S1{ | ||
| 210 | .x = S2{ .y = 127 }, | ||
| 211 | }; | ||
| 212 | try expect(s.x.?.y == 127); | ||
| 213 | } |