authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2023-05-18 17:39:43+02:00
committergravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2023-05-20 17:30:21+02:00
log0ba0d8fecb255cf19a225e4cb160921d73f83652
tree101032b9899da6cbc261aa5397c14cdf8e865613
parent7d519b3383431df48a41d6b32520e5c5e3d77612
signaturelock-open Commit is signed but in an unrecognized format.

spirv: dont use OpIAddCarry

This instruction is not really working well in the LLVM SPIRV translator, as it is not implemented. This commit also intruces the constructStruct helper function to initialize structs at runtime. This is ALSO buggy in the translator, and we must work around OpCompositeConstruct not working when some of the constituents are runtime-known only. Some other improvements are made: - improved variable() so that it is more useful and no longer requires the address space. It always puts values in the Function address space, and returns a pointer to the Generic address space - adds a boolToInt utility function

3 files changed, 153 insertions(+), 145 deletions(-)

src/codegen/spirv.zig+153-136
...@@ -442,6 +442,43 @@ pub const DeclGen = struct {...@@ -442,6 +442,43 @@ pub const DeclGen = struct {
442 }442 }
443 }443 }
444444
445 /// Construct a struct at runtime.
446 /// result_ty_ref must be a struct type.
447 fn constructStruct(self: *DeclGen, result_ty_ref: SpvType.Ref, constituents: []const IdRef) !IdRef {
448 // The Khronos LLVM-SPIRV translator crashes because it cannot construct structs which'
449 // operands are not constant.
450 // See https://github.com/KhronosGroup/SPIRV-LLVM-Translator/issues/1349
451 // For now, just initialize the struct by setting the fields manually...
452 // TODO: Make this OpCompositeConstruct when we can
453 const ptr_composite_id = try self.alloc(result_ty_ref, null);
454 // Note: using 32-bit ints here because usize crashes the translator as well
455 const index_ty_ref = try self.intType(.unsigned, 32);
456 const spv_composite_ty = self.spv.typeRefType(result_ty_ref);
457 const members = spv_composite_ty.payload(.@"struct").members;
458 for (constituents, members, 0..) |constitent_id, member, index| {
459 const index_id = try self.constInt(index_ty_ref, index);
460 const ptr_id = self.spv.allocId();
461 const ptr_member_ty_ref = try self.spv.ptrType(member.ty, .Generic, 0);
462 try self.func.body.emit(self.spv.gpa, .OpInBoundsAccessChain, .{
463 .id_result_type = self.typeId(ptr_member_ty_ref),
464 .id_result = ptr_id,
465 .base = ptr_composite_id,
466 .indexes = &.{index_id},
467 });
468 try self.func.body.emit(self.spv.gpa, .OpStore, .{
469 .pointer = ptr_id,
470 .object = constitent_id,
471 });
472 }
473 const result_id = self.spv.allocId();
474 try self.func.body.emit(self.spv.gpa, .OpLoad, .{
475 .id_result_type = self.typeId(result_ty_ref),
476 .id_result = result_id,
477 .pointer = ptr_composite_id,
478 });
479 return result_id;
480 }
481
445 const IndirectConstantLowering = struct {482 const IndirectConstantLowering = struct {
446 const undef = 0xAA;483 const undef = 0xAA;
447484
...@@ -1582,6 +1619,20 @@ pub const DeclGen = struct {...@@ -1582,6 +1619,20 @@ pub const DeclGen = struct {
1582 }1619 }
1583 }1620 }
15841621
1622 fn boolToInt(self: *DeclGen, result_ty_ref: SpvType.Ref, condition_id: IdRef) !IdRef {
1623 const zero_id = try self.constInt(result_ty_ref, 0);
1624 const one_id = try self.constInt(result_ty_ref, 1);
1625 const result_id = self.spv.allocId();
1626 try self.func.body.emit(self.spv.gpa, .OpSelect, .{
1627 .id_result_type = self.typeId(result_ty_ref),
1628 .id_result = result_id,
1629 .condition = condition_id,
1630 .object_1 = one_id,
1631 .object_2 = zero_id,
1632 });
1633 return result_id;
1634 }
1635
1585 /// Convert representation from indirect (in memory) to direct (in 'register')1636 /// Convert representation from indirect (in memory) to direct (in 'register')
1586 /// This converts the argument type from resolveType(ty, .indirect) to resolveType(ty, .direct).1637 /// This converts the argument type from resolveType(ty, .indirect) to resolveType(ty, .direct).
1587 fn convertToDirect(self: *DeclGen, ty: Type, operand_id: IdRef) !IdRef {1638 fn convertToDirect(self: *DeclGen, ty: Type, operand_id: IdRef) !IdRef {
...@@ -1610,17 +1661,7 @@ pub const DeclGen = struct {...@@ -1610,17 +1661,7 @@ pub const DeclGen = struct {
1610 return switch (ty.zigTypeTag()) {1661 return switch (ty.zigTypeTag()) {
1611 .Bool => blk: {1662 .Bool => blk: {
1612 const indirect_bool_ty_ref = try self.resolveType(ty, .indirect);1663 const indirect_bool_ty_ref = try self.resolveType(ty, .indirect);
1613 const zero_id = try self.constInt(indirect_bool_ty_ref, 0);1664 break :blk self.boolToInt(indirect_bool_ty_ref, operand_id);
1614 const one_id = try self.constInt(indirect_bool_ty_ref, 1);
1615 const result_id = self.spv.allocId();
1616 try self.func.body.emit(self.spv.gpa, .OpSelect, .{
1617 .id_result_type = self.typeId(indirect_bool_ty_ref),
1618 .id_result = result_id,
1619 .condition = operand_id,
1620 .object_1 = one_id,
1621 .object_2 = zero_id,
1622 });
1623 break :blk result_id;
1624 },1665 },
1625 else => operand_id,1666 else => operand_id,
1626 };1667 };
...@@ -1933,64 +1974,83 @@ pub const DeclGen = struct {...@@ -1933,64 +1974,83 @@ pub const DeclGen = struct {
1933 .float, .bool => unreachable,1974 .float, .bool => unreachable,
1934 }1975 }
19351976
1936 // The operand type must be the same as the result type in SPIR-V.1977 // The operand type must be the same as the result type in SPIR-V, which
1978 // is the same as in Zig.
1937 const operand_ty_ref = try self.resolveType(operand_ty, .direct);1979 const operand_ty_ref = try self.resolveType(operand_ty, .direct);
1938 const operand_ty_id = self.typeId(operand_ty_ref);1980 const operand_ty_id = self.typeId(operand_ty_ref);
19391981
1940 const op_result_id = blk: {1982 const bool_ty_ref = try self.resolveType(Type.bool, .direct);
1941 // Construct the SPIR-V result type.
1942 // It is almost the same as the zig one, except that the fields must be the same type
1943 // and they must be unsigned.
1944 const overflow_result_ty_ref = try self.spv.simpleStructType(&.{
1945 .{ .ty = operand_ty_ref, .name = "res" },
1946 .{ .ty = operand_ty_ref, .name = "ov" },
1947 });
1948 const result_id = self.spv.allocId();
1949 try self.func.body.emit(self.spv.gpa, .OpIAddCarry, .{
1950 .id_result_type = self.typeId(overflow_result_ty_ref),
1951 .id_result = result_id,
1952 .operand_1 = lhs,
1953 .operand_2 = rhs,
1954 });
1955 break :blk result_id;
1956 };
1957
1958 // Now convert the SPIR-V flavor result into a Zig-flavor result.
1959 // First, extract the two fields.
1960 const unsigned_result = try self.extractField(operand_ty, op_result_id, 0);
1961 const overflow = try self.extractField(operand_ty, op_result_id, 1);
1962
1963 // We need to convert the results to the types that Zig expects here.
1964 // The `result` is the same type except unsigned, so we can just bitcast that.
1965 // TODO: This can be removed in Kernels as there are only unsigned ints. Maybe for
1966 // shaders as well?
1967 const result = try self.bitcast(operand_ty_id, unsigned_result);
1968
1969 // The overflow needs to be converted into whatever is used to represent it in Zig.
1970 const casted_overflow = blk: {
1971 const ov_ty = result_ty.tupleFields().types[1];
1972 const ov_ty_id = try self.resolveTypeId(ov_ty);
1973 const result_id = self.spv.allocId();
1974 try self.func.body.emit(self.spv.gpa, .OpUConvert, .{
1975 .id_result_type = ov_ty_id,
1976 .id_result = result_id,
1977 .unsigned_value = overflow,
1978 });
1979 break :blk result_id;
1980 };
19811983
1982 // TODO: If copying this function for borrow, make sure to convert -1 to 1 as appropriate.1984 const ov_ty = result_ty.tupleFields().types[1];
1985 // Note: result is stored in a struct, so indirect representation.
1986 const ov_ty_ref = try self.resolveType(ov_ty, .indirect);
19831987
1984 // Finally, construct the Zig type.1988 // TODO: Operations other than addition.
1985 // Layout is result, overflow.1989 const value_id = self.spv.allocId();
1986 const result_id = self.spv.allocId();1990 try self.func.body.emit(self.spv.gpa, .OpIAdd, .{
1987 const constituents = [_]IdRef{ result, casted_overflow };
1988 try self.func.body.emit(self.spv.gpa, .OpCompositeConstruct, .{
1989 .id_result_type = operand_ty_id,1991 .id_result_type = operand_ty_id,
1990 .id_result = result_id,1992 .id_result = value_id,
1991 .constituents = &constituents,1993 .operand_1 = lhs,
1994 .operand_2 = rhs,
1995 });
1996
1997 const overflowed_id = switch (info.signedness) {
1998 .unsigned => blk: {
1999 // Overflow happened if the result is smaller than either of the operands. It doesn't matter which.
2000 const overflowed_id = self.spv.allocId();
2001 try self.func.body.emit(self.spv.gpa, .OpULessThan, .{
2002 .id_result_type = self.typeId(bool_ty_ref),
2003 .id_result = overflowed_id,
2004 .operand_1 = value_id,
2005 .operand_2 = lhs,
2006 });
2007 break :blk overflowed_id;
2008 },
2009 .signed => blk: {
2010 // Overflow happened if:
2011 // - rhs is negative and value > lhs
2012 // - rhs is positive and value < lhs
2013 // This can be shortened to:
2014 // (rhs < 0 && value > lhs) || (rhs >= 0 && value <= lhs)
2015 // = (rhs < 0) == (value > lhs)
2016 // Note that signed overflow is also wrapping in spir-v.
2017
2018 const rhs_lt_zero_id = self.spv.allocId();
2019 const zero_id = try self.constInt(operand_ty_ref, 0);
2020 try self.func.body.emit(self.spv.gpa, .OpSLessThan, .{
2021 .id_result_type = self.typeId(bool_ty_ref),
2022 .id_result = rhs_lt_zero_id,
2023 .operand_1 = rhs,
2024 .operand_2 = zero_id,
2025 });
2026
2027 const value_gt_lhs_id = self.spv.allocId();
2028 try self.func.body.emit(self.spv.gpa, .OpSGreaterThan, .{
2029 .id_result_type = self.typeId(bool_ty_ref),
2030 .id_result = value_gt_lhs_id,
2031 .operand_1 = value_id,
2032 .operand_2 = lhs,
2033 });
2034
2035 const overflowed_id = self.spv.allocId();
2036 try self.func.body.emit(self.spv.gpa, .OpLogicalEqual, .{
2037 .id_result_type = self.typeId(bool_ty_ref),
2038 .id_result = overflowed_id,
2039 .operand_1 = rhs_lt_zero_id,
2040 .operand_2 = value_gt_lhs_id,
2041 });
2042 break :blk overflowed_id;
2043 },
2044 };
2045
2046 // Construct the struct that Zig wants as result.
2047 // The value should already be the correct type.
2048 const ov_id = try self.boolToInt(ov_ty_ref, overflowed_id);
2049 const result_ty_ref = try self.resolveType(result_ty, .direct);
2050 return try self.constructStruct(result_ty_ref, &.{
2051 value_id,
2052 ov_id,
1992 });2053 });
1993 return result_id;
1994 }2054 }
19952055
1996 fn airShuffle(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {2056 fn airShuffle(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
...@@ -2463,76 +2523,45 @@ pub const DeclGen = struct {...@@ -2463,76 +2523,45 @@ pub const DeclGen = struct {
2463 return result_id;2523 return result_id;
2464 }2524 }
24652525
2466 fn variable(2526 // Allocate a function-local variable, with possible initializer.
2527 // This function returns a pointer to a variable of type `ty_ref`,
2528 // which is in the Generic address space. The variable is actually
2529 // placed in the Function address space.
2530 fn alloc(
2467 self: *DeclGen,2531 self: *DeclGen,
2468 comptime context: enum { function, global },2532 ty_ref: SpvType.Ref,
2469 result_id: IdRef,
2470 ptr_ty_ref: SpvType.Ref,
2471 initializer: ?IdRef,2533 initializer: ?IdRef,
2472 ) !void {2534 ) !IdRef {
2473 const storage_class = self.spv.typeRefType(ptr_ty_ref).payload(.pointer).storage_class;2535 const fn_ptr_ty_ref = try self.spv.ptrType(ty_ref, .Function, 0);
2474 const actual_storage_class = switch (storage_class) {2536 const general_ptr_ty_ref = try self.spv.ptrType(ty_ref, .Generic, 0);
2475 .Generic => switch (context) {
2476 .function => .Function,
2477 .global => .CrossWorkgroup,
2478 },
2479 else => storage_class,
2480 };
2481 const actual_ptr_ty_ref = switch (storage_class) {
2482 .Generic => try self.spv.changePtrStorageClass(ptr_ty_ref, actual_storage_class),
2483 else => ptr_ty_ref,
2484 };
2485 const alloc_result_id = switch (storage_class) {
2486 .Generic => self.spv.allocId(),
2487 else => result_id,
2488 };
24892537
2490 const section = switch (actual_storage_class) {2538 // SPIR-V requires that OpVariable declarations for locals go into the first block, so we are just going to
2491 .Generic => unreachable,2539 // directly generate them into func.prologue instead of the body.
2492 // SPIR-V requires that OpVariable declarations for locals go into the first block, so we are just going to2540 const var_id = self.spv.allocId();
2493 // directly generate them into func.prologue instead of the body.2541 try self.func.prologue.emit(self.spv.gpa, .OpVariable, .{
2494 .Function => &self.func.prologue,2542 .id_result_type = self.typeId(fn_ptr_ty_ref),
2495 else => &self.spv.sections.types_globals_constants,2543 .id_result = var_id,
2496 };2544 .storage_class = .Function,
2497 try section.emit(self.spv.gpa, .OpVariable, .{
2498 .id_result_type = self.typeId(actual_ptr_ty_ref),
2499 .id_result = alloc_result_id,
2500 .storage_class = actual_storage_class,
2501 .initializer = initializer,2545 .initializer = initializer,
2502 });2546 });
25032547
2504 if (storage_class != .Generic) {2548 // Convert to a generic pointer
2505 return;2549 const result_id = self.spv.allocId();
2506 }2550 try self.func.body.emit(self.spv.gpa, .OpPtrCastToGeneric, .{
25072551 .id_result_type = self.typeId(general_ptr_ty_ref),
2508 // Now we need to convert the pointer.2552 .id_result = result_id,
2509 // If this is a function local, we need to perform the conversion at runtime. Otherwise, we can do2553 .pointer = var_id,
2510 // it ahead of time using OpSpecConstantOp.2554 });
2511 switch (actual_storage_class) {2555 return result_id;
2512 .Function => try self.func.body.emit(self.spv.gpa, .OpPtrCastToGeneric, .{
2513 .id_result_type = self.typeId(ptr_ty_ref),
2514 .id_result = result_id,
2515 .pointer = alloc_result_id,
2516 }),
2517 // TODO: Can we do without this cast or move it to runtime?
2518 else => {
2519 const const_ptr_id = try self.makePointerConstant(section, actual_ptr_ty_ref, alloc_result_id);
2520 try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{
2521 .id_result_type = self.typeId(ptr_ty_ref),
2522 .id_result = result_id,
2523 .pointer = const_ptr_id,
2524 });
2525 },
2526 }
2527 }2556 }
25282557
2529 fn airAlloc(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {2558 fn airAlloc(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
2530 if (self.liveness.isUnused(inst)) return null;2559 if (self.liveness.isUnused(inst)) return null;
2531 const ty = self.air.typeOfIndex(inst);2560 const ptr_ty = self.air.typeOfIndex(inst);
2532 const result_ty_ref = try self.resolveType(ty, .direct);2561 assert(ptr_ty.ptrAddressSpace() == .generic);
2533 const result_id = self.spv.allocId();2562 const child_ty = ptr_ty.childType();
2534 try self.variable(.function, result_id, result_ty_ref, null);2563 const child_ty_ref = try self.resolveType(child_ty, .indirect);
2535 return result_id;2564 return try self.alloc(child_ty_ref, null);
2536 }2565 }
25372566
2538 fn airArg(self: *DeclGen) IdRef {2567 fn airArg(self: *DeclGen) IdRef {
...@@ -2819,13 +2848,7 @@ pub const DeclGen = struct {...@@ -2819,13 +2848,7 @@ pub const DeclGen = struct {
2819 }2848 }
28202849
2821 const err_union_ty_ref = try self.resolveType(err_union_ty, .direct);2850 const err_union_ty_ref = try self.resolveType(err_union_ty, .direct);
2822 const result_id = self.spv.allocId();2851 return try self.constructStruct(err_union_ty_ref, members.slice());
2823 try self.func.body.emit(self.spv.gpa, .OpCompositeConstruct, .{
2824 .id_result_type = self.typeId(err_union_ty_ref),
2825 .id_result = result_id,
2826 .constituents = members.slice(),
2827 });
2828 return result_id;
2829 }2852 }
28302853
2831 fn airIsNull(self: *DeclGen, inst: Air.Inst.Index, pred: enum { is_null, is_non_null }) !?IdRef {2854 fn airIsNull(self: *DeclGen, inst: Air.Inst.Index, pred: enum { is_null, is_non_null }) !?IdRef {
...@@ -2925,14 +2948,8 @@ pub const DeclGen = struct {...@@ -2925,14 +2948,8 @@ pub const DeclGen = struct {
2925 }2948 }
29262949
2927 const optional_ty_ref = try self.resolveType(optional_ty, .direct);2950 const optional_ty_ref = try self.resolveType(optional_ty, .direct);
2928 const result_id = self.spv.allocId();
2929 const members = [_]IdRef{ operand_id, try self.constBool(true, .indirect) };2951 const members = [_]IdRef{ operand_id, try self.constBool(true, .indirect) };
2930 try self.func.body.emit(self.spv.gpa, .OpCompositeConstruct, .{2952 return try self.constructStruct(optional_ty_ref, &members);
2931 .id_result_type = self.typeId(optional_ty_ref),
2932 .id_result = result_id,
2933 .constituents = &members,
2934 });
2935 return result_id;
2936 }2953 }
29372954
2938 fn airSwitchBr(self: *DeclGen, inst: Air.Inst.Index) !void {2955 fn airSwitchBr(self: *DeclGen, inst: Air.Inst.Index) !void {
test/behavior/basic.zig-7
...@@ -82,8 +82,6 @@ test "type equality" {...@@ -82,8 +82,6 @@ test "type equality" {
82}82}
8383
84test "pointer dereferencing" {84test "pointer dereferencing" {
85 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
86
87 var x = @as(i32, 3);85 var x = @as(i32, 3);
88 const y = &x;86 const y = &x;
8987
...@@ -293,8 +291,6 @@ test "function closes over local const" {...@@ -293,8 +291,6 @@ test "function closes over local const" {
293}291}
294292
295test "volatile load and store" {293test "volatile load and store" {
296 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
297
298 var number: i32 = 1234;294 var number: i32 = 1234;
299 const ptr = @as(*volatile i32, &number);295 const ptr = @as(*volatile i32, &number);
300 ptr.* += 1;296 ptr.* += 1;
...@@ -466,7 +462,6 @@ fn nine() u8 {...@@ -466,7 +462,6 @@ fn nine() u8 {
466test "struct inside function" {462test "struct inside function" {
467 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;463 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
468 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO464 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
469 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
470465
471 try testStructInFn();466 try testStructInFn();
472 comptime try testStructInFn();467 comptime try testStructInFn();
...@@ -691,7 +686,6 @@ test "string concatenation" {...@@ -691,7 +686,6 @@ test "string concatenation" {
691 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;686 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
692 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;687 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
693 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO688 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
694 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
695689
696 const a = "OK" ++ " IT " ++ "WORKED";690 const a = "OK" ++ " IT " ++ "WORKED";
697 const b = "OK IT WORKED";691 const b = "OK IT WORKED";
...@@ -1130,7 +1124,6 @@ test "returning an opaque type from a function" {...@@ -1130,7 +1124,6 @@ test "returning an opaque type from a function" {
1130test "orelse coercion as function argument" {1124test "orelse coercion as function argument" {
1131 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO1125 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
1132 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO1126 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
1133 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
11341127
1135 const Loc = struct { start: i32 = -1 };1128 const Loc = struct { start: i32 = -1 };
1136 const Container = struct {1129 const Container = struct {
test/behavior/math.zig-2
...@@ -208,7 +208,6 @@ test "float equality" {...@@ -208,7 +208,6 @@ test "float equality" {
208 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO208 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
209 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO209 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
210 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO210 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
211 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
212211
213 const x: f64 = 0.012;212 const x: f64 = 0.012;
214 const y: f64 = x + 1.0;213 const y: f64 = x + 1.0;
...@@ -685,7 +684,6 @@ test "@addWithOverflow" {...@@ -685,7 +684,6 @@ test "@addWithOverflow" {
685 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO684 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
686 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO685 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
687 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO686 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
688 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
689687
690 {688 {
691 var a: u8 = 250;689 var a: u8 = 250;