| ... | @@ -483,7 +483,12 @@ fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode { | ... | @@ -483,7 +483,12 @@ fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode { |
| 483 | .f32 => if (args.signedness.? == .signed) return .i32_trunc_f32_s else return .i32_trunc_f32_u, | 483 | .f32 => if (args.signedness.? == .signed) return .i32_trunc_f32_s else return .i32_trunc_f32_u, |
| 484 | .f64 => if (args.signedness.? == .signed) return .i32_trunc_f64_s else return .i32_trunc_f64_u, | 484 | .f64 => if (args.signedness.? == .signed) return .i32_trunc_f64_s else return .i32_trunc_f64_u, |
| 485 | } else return .f32_trunc, // when no valtype2, it's an f16 instead which is stored in an i32. | 485 | } else return .f32_trunc, // when no valtype2, it's an f16 instead which is stored in an i32. |
| 486 | .i64 => unreachable, | 486 | .i64 => switch (args.valtype2.?) { |
| | 487 | .i32 => unreachable, |
| | 488 | .i64 => unreachable, |
| | 489 | .f32 => if (args.signedness.? == .signed) return .i64_trunc_f32_s else return .i64_trunc_f32_u, |
| | 490 | .f64 => if (args.signedness.? == .signed) return .i64_trunc_f64_s else return .i64_trunc_f64_u, |
| | 491 | }, |
| 487 | .f32 => return .f32_trunc, | 492 | .f32 => return .f32_trunc, |
| 488 | .f64 => return .f64_trunc, | 493 | .f64 => return .f64_trunc, |
| 489 | }, | 494 | }, |
| ... | @@ -687,7 +692,10 @@ const InnerError = error{ | ... | @@ -687,7 +692,10 @@ const InnerError = error{ |
| 687 | }; | 692 | }; |
| 688 | | 693 | |
| 689 | pub fn deinit(func: *CodeGen) void { | 694 | pub fn deinit(func: *CodeGen) void { |
| 690 | assert(func.branches.items.len == 0); // we should end with no branches left. Forgot a call to `branches.pop()`? | 695 | // in case of an error and we still have branches |
| | 696 | for (func.branches.items) |*branch| { |
| | 697 | branch.deinit(func.gpa); |
| | 698 | } |
| 691 | func.branches.deinit(func.gpa); | 699 | func.branches.deinit(func.gpa); |
| 692 | func.blocks.deinit(func.gpa); | 700 | func.blocks.deinit(func.gpa); |
| 693 | func.locals.deinit(func.gpa); | 701 | func.locals.deinit(func.gpa); |
| ... | @@ -909,6 +917,13 @@ fn typeToValtype(ty: Type, target: std.Target) wasm.Valtype { | ... | @@ -909,6 +917,13 @@ fn typeToValtype(ty: Type, target: std.Target) wasm.Valtype { |
| 909 | if (info.bits > 32 and info.bits <= 128) break :blk wasm.Valtype.i64; | 917 | if (info.bits > 32 and info.bits <= 128) break :blk wasm.Valtype.i64; |
| 910 | break :blk wasm.Valtype.i32; // represented as pointer to stack | 918 | break :blk wasm.Valtype.i32; // represented as pointer to stack |
| 911 | }, | 919 | }, |
| | 920 | .Struct => switch (ty.containerLayout()) { |
| | 921 | .Packed => { |
| | 922 | const struct_obj = ty.castTag(.@"struct").?.data; |
| | 923 | return typeToValtype(struct_obj.backing_int_ty, target); |
| | 924 | }, |
| | 925 | else => wasm.Valtype.i32, |
| | 926 | }, |
| 912 | else => wasm.Valtype.i32, // all represented as reference/immediate | 927 | else => wasm.Valtype.i32, // all represented as reference/immediate |
| 913 | }; | 928 | }; |
| 914 | } | 929 | } |
| ... | @@ -1119,13 +1134,14 @@ fn genFunc(func: *CodeGen) InnerError!void { | ... | @@ -1119,13 +1134,14 @@ fn genFunc(func: *CodeGen) InnerError!void { |
| 1119 | try func.addTag(.dbg_prologue_end); | 1134 | try func.addTag(.dbg_prologue_end); |
| 1120 | | 1135 | |
| 1121 | try func.branches.append(func.gpa, .{}); | 1136 | try func.branches.append(func.gpa, .{}); |
| | 1137 | // clean up outer branch |
| | 1138 | defer { |
| | 1139 | var outer_branch = func.branches.pop(); |
| | 1140 | outer_branch.deinit(func.gpa); |
| | 1141 | } |
| 1122 | // Generate MIR for function body | 1142 | // Generate MIR for function body |
| 1123 | try func.genBody(func.air.getMainBody()); | 1143 | try func.genBody(func.air.getMainBody()); |
| 1124 | | 1144 | |
| 1125 | // clean up outer branch | | |
| 1126 | var outer_branch = func.branches.pop(); | | |
| 1127 | outer_branch.deinit(func.gpa); | | |
| 1128 | | | |
| 1129 | // In case we have a return value, but the last instruction is a noreturn (such as a while loop) | 1145 | // In case we have a return value, but the last instruction is a noreturn (such as a while loop) |
| 1130 | // we emit an unreachable instruction to tell the stack validator that part will never be reached. | 1146 | // we emit an unreachable instruction to tell the stack validator that part will never be reached. |
| 1131 | if (func_type.returns.len != 0 and func.air.instructions.len > 0) { | 1147 | if (func_type.returns.len != 0 and func.air.instructions.len > 0) { |
| ... | @@ -1309,17 +1325,21 @@ fn lowerArg(func: *CodeGen, cc: std.builtin.CallingConvention, ty: Type, value: | ... | @@ -1309,17 +1325,21 @@ fn lowerArg(func: *CodeGen, cc: std.builtin.CallingConvention, ty: Type, value: |
| 1309 | assert(ty_classes[0] == .direct); | 1325 | assert(ty_classes[0] == .direct); |
| 1310 | const scalar_type = abi.scalarType(ty, func.target); | 1326 | const scalar_type = abi.scalarType(ty, func.target); |
| 1311 | const abi_size = scalar_type.abiSize(func.target); | 1327 | const abi_size = scalar_type.abiSize(func.target); |
| 1312 | const opcode = buildOpcode(.{ | | |
| 1313 | .op = .load, | | |
| 1314 | .width = @intCast(u8, abi_size), | | |
| 1315 | .signedness = if (scalar_type.isSignedInt()) .signed else .unsigned, | | |
| 1316 | .valtype1 = typeToValtype(scalar_type, func.target), | | |
| 1317 | }); | | |
| 1318 | try func.emitWValue(value); | 1328 | try func.emitWValue(value); |
| 1319 | try func.addMemArg(Mir.Inst.Tag.fromOpcode(opcode), .{ | 1329 | |
| 1320 | .offset = value.offset(), | 1330 | // When the value lives in the virtual stack, we must load it onto the actual stack |
| 1321 | .alignment = scalar_type.abiAlignment(func.target), | 1331 | if (value != .imm32 and value != .imm64) { |
| 1322 | }); | 1332 | const opcode = buildOpcode(.{ |
| | 1333 | .op = .load, |
| | 1334 | .width = @intCast(u8, abi_size), |
| | 1335 | .signedness = if (scalar_type.isSignedInt()) .signed else .unsigned, |
| | 1336 | .valtype1 = typeToValtype(scalar_type, func.target), |
| | 1337 | }); |
| | 1338 | try func.addMemArg(Mir.Inst.Tag.fromOpcode(opcode), .{ |
| | 1339 | .offset = value.offset(), |
| | 1340 | .alignment = scalar_type.abiAlignment(func.target), |
| | 1341 | }); |
| | 1342 | } |
| 1323 | }, | 1343 | }, |
| 1324 | .Int, .Float => { | 1344 | .Int, .Float => { |
| 1325 | if (ty_classes[1] == .none) { | 1345 | if (ty_classes[1] == .none) { |
| ... | @@ -1472,12 +1492,15 @@ fn memcpy(func: *CodeGen, dst: WValue, src: WValue, len: WValue) !void { | ... | @@ -1472,12 +1492,15 @@ fn memcpy(func: *CodeGen, dst: WValue, src: WValue, len: WValue) !void { |
| 1472 | // when the length is comptime-known, rather than a runtime value, we can optimize the generated code by having | 1492 | // when the length is comptime-known, rather than a runtime value, we can optimize the generated code by having |
| 1473 | // the loop during codegen, rather than inserting a runtime loop into the binary. | 1493 | // the loop during codegen, rather than inserting a runtime loop into the binary. |
| 1474 | switch (len) { | 1494 | switch (len) { |
| 1475 | .imm32, .imm64 => { | 1495 | .imm32, .imm64 => blk: { |
| 1476 | const length = switch (len) { | 1496 | const length = switch (len) { |
| 1477 | .imm32 => |val| val, | 1497 | .imm32 => |val| val, |
| 1478 | .imm64 => |val| val, | 1498 | .imm64 => |val| val, |
| 1479 | else => unreachable, | 1499 | else => unreachable, |
| 1480 | }; | 1500 | }; |
| | 1501 | // if the size (length) is more than 1024 bytes, we use a runtime loop instead to prevent |
| | 1502 | // binary size bloat. |
| | 1503 | if (length > 1024) break :blk; |
| 1481 | var offset: u32 = 0; | 1504 | var offset: u32 = 0; |
| 1482 | const lhs_base = dst.offset(); | 1505 | const lhs_base = dst.offset(); |
| 1483 | const rhs_base = src.offset(); | 1506 | const rhs_base = src.offset(); |
| ... | @@ -1498,80 +1521,81 @@ fn memcpy(func: *CodeGen, dst: WValue, src: WValue, len: WValue) !void { | ... | @@ -1498,80 +1521,81 @@ fn memcpy(func: *CodeGen, dst: WValue, src: WValue, len: WValue) !void { |
| 1498 | else => unreachable, | 1521 | else => unreachable, |
| 1499 | } | 1522 | } |
| 1500 | } | 1523 | } |
| | 1524 | return; |
| 1501 | }, | 1525 | }, |
| 1502 | else => { | 1526 | else => {}, |
| 1503 | // TODO: We should probably lower this to a call to compiler_rt | 1527 | } |
| 1504 | // But for now, we implement it manually | | |
| 1505 | var offset = try func.ensureAllocLocal(Type.usize); // local for counter | | |
| 1506 | defer offset.free(func); | | |
| 1507 | | 1528 | |
| 1508 | // outer block to jump to when loop is done | 1529 | // TODO: We should probably lower this to a call to compiler_rt |
| 1509 | try func.startBlock(.block, wasm.block_empty); | 1530 | // But for now, we implement it manually |
| 1510 | try func.startBlock(.loop, wasm.block_empty); | 1531 | var offset = try func.ensureAllocLocal(Type.usize); // local for counter |
| | 1532 | defer offset.free(func); |
| 1511 | | 1533 | |
| 1512 | // loop condition (offset == length -> break) | 1534 | // outer block to jump to when loop is done |
| 1513 | { | 1535 | try func.startBlock(.block, wasm.block_empty); |
| 1514 | try func.emitWValue(offset); | 1536 | try func.startBlock(.loop, wasm.block_empty); |
| 1515 | try func.emitWValue(len); | | |
| 1516 | switch (func.arch()) { | | |
| 1517 | .wasm32 => try func.addTag(.i32_eq), | | |
| 1518 | .wasm64 => try func.addTag(.i64_eq), | | |
| 1519 | else => unreachable, | | |
| 1520 | } | | |
| 1521 | try func.addLabel(.br_if, 1); // jump out of loop into outer block (finished) | | |
| 1522 | } | | |
| 1523 | | 1537 | |
| 1524 | // get dst ptr | 1538 | // loop condition (offset == length -> break) |
| 1525 | { | 1539 | { |
| 1526 | try func.emitWValue(dst); | 1540 | try func.emitWValue(offset); |
| 1527 | try func.emitWValue(offset); | 1541 | try func.emitWValue(len); |
| 1528 | switch (func.arch()) { | 1542 | switch (func.arch()) { |
| 1529 | .wasm32 => try func.addTag(.i32_add), | 1543 | .wasm32 => try func.addTag(.i32_eq), |
| 1530 | .wasm64 => try func.addTag(.i64_add), | 1544 | .wasm64 => try func.addTag(.i64_eq), |
| 1531 | else => unreachable, | 1545 | else => unreachable, |
| 1532 | } | 1546 | } |
| 1533 | } | 1547 | try func.addLabel(.br_if, 1); // jump out of loop into outer block (finished) |
| | 1548 | } |
| 1534 | | 1549 | |
| 1535 | // get src value and also store in dst | 1550 | // get dst ptr |
| 1536 | { | 1551 | { |
| 1537 | try func.emitWValue(src); | 1552 | try func.emitWValue(dst); |
| 1538 | try func.emitWValue(offset); | 1553 | try func.emitWValue(offset); |
| 1539 | switch (func.arch()) { | 1554 | switch (func.arch()) { |
| 1540 | .wasm32 => { | 1555 | .wasm32 => try func.addTag(.i32_add), |
| 1541 | try func.addTag(.i32_add); | 1556 | .wasm64 => try func.addTag(.i64_add), |
| 1542 | try func.addMemArg(.i32_load8_u, .{ .offset = src.offset(), .alignment = 1 }); | 1557 | else => unreachable, |
| 1543 | try func.addMemArg(.i32_store8, .{ .offset = dst.offset(), .alignment = 1 }); | 1558 | } |
| 1544 | }, | 1559 | } |
| 1545 | .wasm64 => { | | |
| 1546 | try func.addTag(.i64_add); | | |
| 1547 | try func.addMemArg(.i64_load8_u, .{ .offset = src.offset(), .alignment = 1 }); | | |
| 1548 | try func.addMemArg(.i64_store8, .{ .offset = dst.offset(), .alignment = 1 }); | | |
| 1549 | }, | | |
| 1550 | else => unreachable, | | |
| 1551 | } | | |
| 1552 | } | | |
| 1553 | | 1560 | |
| 1554 | // increment loop counter | 1561 | // get src value and also store in dst |
| 1555 | { | 1562 | { |
| 1556 | try func.emitWValue(offset); | 1563 | try func.emitWValue(src); |
| 1557 | switch (func.arch()) { | 1564 | try func.emitWValue(offset); |
| 1558 | .wasm32 => { | 1565 | switch (func.arch()) { |
| 1559 | try func.addImm32(1); | 1566 | .wasm32 => { |
| 1560 | try func.addTag(.i32_add); | 1567 | try func.addTag(.i32_add); |
| 1561 | }, | 1568 | try func.addMemArg(.i32_load8_u, .{ .offset = src.offset(), .alignment = 1 }); |
| 1562 | .wasm64 => { | 1569 | try func.addMemArg(.i32_store8, .{ .offset = dst.offset(), .alignment = 1 }); |
| 1563 | try func.addImm64(1); | 1570 | }, |
| 1564 | try func.addTag(.i64_add); | 1571 | .wasm64 => { |
| 1565 | }, | 1572 | try func.addTag(.i64_add); |
| 1566 | else => unreachable, | 1573 | try func.addMemArg(.i64_load8_u, .{ .offset = src.offset(), .alignment = 1 }); |
| 1567 | } | 1574 | try func.addMemArg(.i64_store8, .{ .offset = dst.offset(), .alignment = 1 }); |
| 1568 | try func.addLabel(.local_set, offset.local.value); | 1575 | }, |
| 1569 | try func.addLabel(.br, 0); // jump to start of loop | 1576 | else => unreachable, |
| 1570 | } | 1577 | } |
| 1571 | try func.endBlock(); // close off loop block | | |
| 1572 | try func.endBlock(); // close off outer block | | |
| 1573 | }, | | |
| 1574 | } | 1578 | } |
| | 1579 | |
| | 1580 | // increment loop counter |
| | 1581 | { |
| | 1582 | try func.emitWValue(offset); |
| | 1583 | switch (func.arch()) { |
| | 1584 | .wasm32 => { |
| | 1585 | try func.addImm32(1); |
| | 1586 | try func.addTag(.i32_add); |
| | 1587 | }, |
| | 1588 | .wasm64 => { |
| | 1589 | try func.addImm64(1); |
| | 1590 | try func.addTag(.i64_add); |
| | 1591 | }, |
| | 1592 | else => unreachable, |
| | 1593 | } |
| | 1594 | try func.addLabel(.local_set, offset.local.value); |
| | 1595 | try func.addLabel(.br, 0); // jump to start of loop |
| | 1596 | } |
| | 1597 | try func.endBlock(); // close off loop block |
| | 1598 | try func.endBlock(); // close off outer block |
| 1575 | } | 1599 | } |
| 1576 | | 1600 | |
| 1577 | fn ptrSize(func: *const CodeGen) u16 { | 1601 | fn ptrSize(func: *const CodeGen) u16 { |
| ... | @@ -1607,10 +1631,18 @@ fn isByRef(ty: Type, target: std.Target) bool { | ... | @@ -1607,10 +1631,18 @@ fn isByRef(ty: Type, target: std.Target) bool { |
| 1607 | | 1631 | |
| 1608 | .Array, | 1632 | .Array, |
| 1609 | .Vector, | 1633 | .Vector, |
| 1610 | .Struct, | | |
| 1611 | .Frame, | 1634 | .Frame, |
| 1612 | .Union, | 1635 | .Union, |
| 1613 | => return ty.hasRuntimeBitsIgnoreComptime(), | 1636 | => return ty.hasRuntimeBitsIgnoreComptime(), |
| | 1637 | .Struct => { |
| | 1638 | if (ty.castTag(.@"struct")) |struct_ty| { |
| | 1639 | const struct_obj = struct_ty.data; |
| | 1640 | if (struct_obj.layout == .Packed and struct_obj.haveFieldTypes()) { |
| | 1641 | return isByRef(struct_obj.backing_int_ty, target); |
| | 1642 | } |
| | 1643 | } |
| | 1644 | return ty.hasRuntimeBitsIgnoreComptime(); |
| | 1645 | }, |
| 1614 | .Int => return ty.intInfo(target).bits > 64, | 1646 | .Int => return ty.intInfo(target).bits > 64, |
| 1615 | .Float => return ty.floatBits(target) > 64, | 1647 | .Float => return ty.floatBits(target) > 64, |
| 1616 | .ErrorUnion => { | 1648 | .ErrorUnion => { |
| ... | @@ -2100,9 +2132,48 @@ fn airStore(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { | ... | @@ -2100,9 +2132,48 @@ fn airStore(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 2100 | | 2132 | |
| 2101 | const lhs = try func.resolveInst(bin_op.lhs); | 2133 | const lhs = try func.resolveInst(bin_op.lhs); |
| 2102 | const rhs = try func.resolveInst(bin_op.rhs); | 2134 | const rhs = try func.resolveInst(bin_op.rhs); |
| 2103 | const ty = func.air.typeOf(bin_op.lhs).childType(); | 2135 | const ptr_ty = func.air.typeOf(bin_op.lhs); |
| | 2136 | const ptr_info = ptr_ty.ptrInfo().data; |
| | 2137 | const ty = ptr_ty.childType(); |
| | 2138 | if (ptr_info.host_size == 0) { |
| | 2139 | try func.store(lhs, rhs, ty, 0); |
| | 2140 | } else { |
| | 2141 | // at this point we have a non-natural alignment, we must |
| | 2142 | // load the value, and then shift+or the rhs into the result location. |
| | 2143 | var int_ty_payload: Type.Payload.Bits = .{ |
| | 2144 | .base = .{ .tag = .int_unsigned }, |
| | 2145 | .data = ptr_info.host_size * 8, |
| | 2146 | }; |
| | 2147 | const int_elem_ty = Type.initPayload(&int_ty_payload.base); |
| | 2148 | |
| | 2149 | if (isByRef(int_elem_ty, func.target)) { |
| | 2150 | return func.fail("TODO: airStore for pointers to bitfields with backing type larger than 64bits", .{}); |
| | 2151 | } |
| | 2152 | |
| | 2153 | var mask = @intCast(u64, (@as(u65, 1) << @intCast(u7, ty.bitSize(func.target))) - 1); |
| | 2154 | mask <<= @intCast(u6, ptr_info.bit_offset); |
| | 2155 | mask ^= ~@as(u64, 0); |
| | 2156 | const shift_val = if (ptr_info.host_size <= 4) |
| | 2157 | WValue{ .imm32 = ptr_info.bit_offset } |
| | 2158 | else |
| | 2159 | WValue{ .imm64 = ptr_info.bit_offset }; |
| | 2160 | const mask_val = if (ptr_info.host_size <= 4) |
| | 2161 | WValue{ .imm32 = @truncate(u32, mask) } |
| | 2162 | else |
| | 2163 | WValue{ .imm64 = mask }; |
| | 2164 | |
| | 2165 | try func.emitWValue(lhs); |
| | 2166 | const loaded = try func.load(lhs, int_elem_ty, 0); |
| | 2167 | const anded = try func.binOp(loaded, mask_val, int_elem_ty, .@"and"); |
| | 2168 | const extended_value = try func.intcast(rhs, ty, int_elem_ty); |
| | 2169 | const shifted_value = if (ptr_info.bit_offset > 0) shifted: { |
| | 2170 | break :shifted try func.binOp(extended_value, shift_val, int_elem_ty, .shl); |
| | 2171 | } else extended_value; |
| | 2172 | const result = try func.binOp(anded, shifted_value, int_elem_ty, .@"or"); |
| | 2173 | // lhs is still on the stack |
| | 2174 | try func.store(.stack, result, int_elem_ty, lhs.offset()); |
| | 2175 | } |
| 2104 | | 2176 | |
| 2105 | try func.store(lhs, rhs, ty, 0); | | |
| 2106 | func.finishAir(inst, .none, &.{ bin_op.lhs, bin_op.rhs }); | 2177 | func.finishAir(inst, .none, &.{ bin_op.lhs, bin_op.rhs }); |
| 2107 | } | 2178 | } |
| 2108 | | 2179 | |
| ... | @@ -2134,7 +2205,7 @@ fn store(func: *CodeGen, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerE | ... | @@ -2134,7 +2205,7 @@ fn store(func: *CodeGen, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerE |
| 2134 | const len = @intCast(u32, ty.abiSize(func.target)); | 2205 | const len = @intCast(u32, ty.abiSize(func.target)); |
| 2135 | return func.memcpy(lhs, rhs, .{ .imm32 = len }); | 2206 | return func.memcpy(lhs, rhs, .{ .imm32 = len }); |
| 2136 | }, | 2207 | }, |
| 2137 | .Struct, .Array, .Union, .Vector => { | 2208 | .Struct, .Array, .Union, .Vector => if (isByRef(ty, func.target)) { |
| 2138 | const len = @intCast(u32, ty.abiSize(func.target)); | 2209 | const len = @intCast(u32, ty.abiSize(func.target)); |
| 2139 | return func.memcpy(lhs, rhs, .{ .imm32 = len }); | 2210 | return func.memcpy(lhs, rhs, .{ .imm32 = len }); |
| 2140 | }, | 2211 | }, |
| ... | @@ -2190,6 +2261,8 @@ fn airLoad(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { | ... | @@ -2190,6 +2261,8 @@ fn airLoad(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 2190 | const ty_op = func.air.instructions.items(.data)[inst].ty_op; | 2261 | const ty_op = func.air.instructions.items(.data)[inst].ty_op; |
| 2191 | const operand = try func.resolveInst(ty_op.operand); | 2262 | const operand = try func.resolveInst(ty_op.operand); |
| 2192 | const ty = func.air.getRefType(ty_op.ty); | 2263 | const ty = func.air.getRefType(ty_op.ty); |
| | 2264 | const ptr_ty = func.air.typeOf(ty_op.operand); |
| | 2265 | const ptr_info = ptr_ty.ptrInfo().data; |
| 2193 | | 2266 | |
| 2194 | if (!ty.hasRuntimeBitsIgnoreComptime()) return func.finishAir(inst, .none, &.{ty_op.operand}); | 2267 | if (!ty.hasRuntimeBitsIgnoreComptime()) return func.finishAir(inst, .none, &.{ty_op.operand}); |
| 2195 | | 2268 | |
| ... | @@ -2200,8 +2273,30 @@ fn airLoad(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { | ... | @@ -2200,8 +2273,30 @@ fn airLoad(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 2200 | break :result new_local; | 2273 | break :result new_local; |
| 2201 | } | 2274 | } |
| 2202 | | 2275 | |
| 2203 | const stack_loaded = try func.load(operand, ty, 0); | 2276 | if (ptr_info.host_size == 0) { |
| 2204 | break :result try stack_loaded.toLocal(func, ty); | 2277 | const stack_loaded = try func.load(operand, ty, 0); |
| | 2278 | break :result try stack_loaded.toLocal(func, ty); |
| | 2279 | } |
| | 2280 | |
| | 2281 | // at this point we have a non-natural alignment, we must |
| | 2282 | // shift the value to obtain the correct bit. |
| | 2283 | var int_ty_payload: Type.Payload.Bits = .{ |
| | 2284 | .base = .{ .tag = .int_unsigned }, |
| | 2285 | .data = ptr_info.host_size * 8, |
| | 2286 | }; |
| | 2287 | const int_elem_ty = Type.initPayload(&int_ty_payload.base); |
| | 2288 | const shift_val = if (ptr_info.host_size <= 4) |
| | 2289 | WValue{ .imm32 = ptr_info.bit_offset } |
| | 2290 | else if (ptr_info.host_size <= 8) |
| | 2291 | WValue{ .imm64 = ptr_info.bit_offset } |
| | 2292 | else |
| | 2293 | return func.fail("TODO: airLoad where ptr to bitfield exceeds 64 bits", .{}); |
| | 2294 | |
| | 2295 | const stack_loaded = try func.load(operand, int_elem_ty, 0); |
| | 2296 | const shifted = try func.binOp(stack_loaded, shift_val, int_elem_ty, .shr); |
| | 2297 | const result = try func.trunc(shifted, ty, int_elem_ty); |
| | 2298 | // const wrapped = try func.wrapOperand(shifted, ty); |
| | 2299 | break :result try result.toLocal(func, ty); |
| 2205 | }; | 2300 | }; |
| 2206 | func.finishAir(inst, result, &.{ty_op.operand}); | 2301 | func.finishAir(inst, result, &.{ty_op.operand}); |
| 2207 | } | 2302 | } |
| ... | @@ -2406,7 +2501,7 @@ fn wrapBinOp(func: *CodeGen, lhs: WValue, rhs: WValue, ty: Type, op: Op) InnerEr | ... | @@ -2406,7 +2501,7 @@ fn wrapBinOp(func: *CodeGen, lhs: WValue, rhs: WValue, ty: Type, op: Op) InnerEr |
| 2406 | /// NOTE: When the Type is <= 64 bits, leaves the value on top of the stack. | 2501 | /// NOTE: When the Type is <= 64 bits, leaves the value on top of the stack. |
| 2407 | fn wrapOperand(func: *CodeGen, operand: WValue, ty: Type) InnerError!WValue { | 2502 | fn wrapOperand(func: *CodeGen, operand: WValue, ty: Type) InnerError!WValue { |
| 2408 | assert(ty.abiSize(func.target) <= 16); | 2503 | assert(ty.abiSize(func.target) <= 16); |
| 2409 | const bitsize = ty.intInfo(func.target).bits; | 2504 | const bitsize = @intCast(u16, ty.bitSize(func.target)); |
| 2410 | const wasm_bits = toWasmBits(bitsize) orelse { | 2505 | const wasm_bits = toWasmBits(bitsize) orelse { |
| 2411 | return func.fail("TODO: Implement wrapOperand for bitsize '{d}'", .{bitsize}); | 2506 | return func.fail("TODO: Implement wrapOperand for bitsize '{d}'", .{bitsize}); |
| 2412 | }; | 2507 | }; |
| ... | @@ -2462,18 +2557,21 @@ fn lowerParentPtr(func: *CodeGen, ptr_val: Value, ptr_child_ty: Type) InnerError | ... | @@ -2462,18 +2557,21 @@ fn lowerParentPtr(func: *CodeGen, ptr_val: Value, ptr_child_ty: Type) InnerError |
| 2462 | const parent_ptr = try func.lowerParentPtr(field_ptr.container_ptr, parent_ty); | 2557 | const parent_ptr = try func.lowerParentPtr(field_ptr.container_ptr, parent_ty); |
| 2463 | | 2558 | |
| 2464 | const offset = switch (parent_ty.zigTypeTag()) { | 2559 | const offset = switch (parent_ty.zigTypeTag()) { |
| 2465 | .Struct => blk: { | 2560 | .Struct => switch (parent_ty.containerLayout()) { |
| 2466 | const offset = parent_ty.structFieldOffset(field_ptr.field_index, func.target); | 2561 | .Packed => parent_ty.packedStructFieldByteOffset(field_ptr.field_index, func.target), |
| 2467 | break :blk offset; | 2562 | else => parent_ty.structFieldOffset(field_ptr.field_index, func.target), |
| 2468 | }, | 2563 | }, |
| 2469 | .Union => blk: { | 2564 | .Union => switch (parent_ty.containerLayout()) { |
| 2470 | const layout: Module.Union.Layout = parent_ty.unionGetLayout(func.target); | 2565 | .Packed => 0, |
| 2471 | if (layout.payload_size == 0) break :blk 0; | 2566 | else => blk: { |
| 2472 | if (layout.payload_align > layout.tag_align) break :blk 0; | 2567 | const layout: Module.Union.Layout = parent_ty.unionGetLayout(func.target); |
| 2473 | | 2568 | if (layout.payload_size == 0) break :blk 0; |
| 2474 | // tag is stored first so calculate offset from where payload starts | 2569 | if (layout.payload_align > layout.tag_align) break :blk 0; |
| 2475 | const offset = @intCast(u32, std.mem.alignForwardGeneric(u64, layout.tag_size, layout.tag_align)); | 2570 | |
| 2476 | break :blk offset; | 2571 | // tag is stored first so calculate offset from where payload starts |
| | 2572 | const offset = @intCast(u32, std.mem.alignForwardGeneric(u64, layout.tag_size, layout.tag_align)); |
| | 2573 | break :blk offset; |
| | 2574 | }, |
| 2477 | }, | 2575 | }, |
| 2478 | .Pointer => switch (parent_ty.ptrSize()) { | 2576 | .Pointer => switch (parent_ty.ptrSize()) { |
| 2479 | .Slice => switch (field_ptr.field_index) { | 2577 | .Slice => switch (field_ptr.field_index) { |
| ... | @@ -2689,6 +2787,18 @@ fn lowerConstant(func: *CodeGen, arg_val: Value, ty: Type) InnerError!WValue { | ... | @@ -2689,6 +2787,18 @@ fn lowerConstant(func: *CodeGen, arg_val: Value, ty: Type) InnerError!WValue { |
| 2689 | const is_pl = val.tag() == .opt_payload; | 2787 | const is_pl = val.tag() == .opt_payload; |
| 2690 | return WValue{ .imm32 = if (is_pl) @as(u32, 1) else 0 }; | 2788 | return WValue{ .imm32 = if (is_pl) @as(u32, 1) else 0 }; |
| 2691 | }, | 2789 | }, |
| | 2790 | .Struct => { |
| | 2791 | const struct_obj = ty.castTag(.@"struct").?.data; |
| | 2792 | assert(struct_obj.layout == .Packed); |
| | 2793 | var buf: [8]u8 = .{0} ** 8; // zero the buffer so we do not read 0xaa as integer |
| | 2794 | val.writeToPackedMemory(ty, func.bin_file.base.options.module.?, &buf, 0); |
| | 2795 | var payload: Value.Payload.U64 = .{ |
| | 2796 | .base = .{ .tag = .int_u64 }, |
| | 2797 | .data = std.mem.readIntLittle(u64, &buf), |
| | 2798 | }; |
| | 2799 | const int_val = Value.initPayload(&payload.base); |
| | 2800 | return func.lowerConstant(int_val, struct_obj.backing_int_ty); |
| | 2801 | }, |
| 2692 | else => |zig_type| return func.fail("Wasm TODO: LowerConstant for zigTypeTag {}", .{zig_type}), | 2802 | else => |zig_type| return func.fail("Wasm TODO: LowerConstant for zigTypeTag {}", .{zig_type}), |
| 2693 | } | 2803 | } |
| 2694 | } | 2804 | } |
| ... | @@ -2723,6 +2833,11 @@ fn emitUndefined(func: *CodeGen, ty: Type) InnerError!WValue { | ... | @@ -2723,6 +2833,11 @@ fn emitUndefined(func: *CodeGen, ty: Type) InnerError!WValue { |
| 2723 | .ErrorUnion => { | 2833 | .ErrorUnion => { |
| 2724 | return WValue{ .imm32 = 0xaaaaaaaa }; | 2834 | return WValue{ .imm32 = 0xaaaaaaaa }; |
| 2725 | }, | 2835 | }, |
| | 2836 | .Struct => { |
| | 2837 | const struct_obj = ty.castTag(.@"struct").?.data; |
| | 2838 | assert(struct_obj.layout == .Packed); |
| | 2839 | return func.emitUndefined(struct_obj.backing_int_ty); |
| | 2840 | }, |
| 2726 | else => return func.fail("Wasm TODO: emitUndefined for type: {}\n", .{ty.zigTypeTag()}), | 2841 | else => return func.fail("Wasm TODO: emitUndefined for type: {}\n", .{ty.zigTypeTag()}), |
| 2727 | } | 2842 | } |
| 2728 | } | 2843 | } |
| ... | @@ -3070,11 +3185,32 @@ fn airBitcast(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { | ... | @@ -3070,11 +3185,32 @@ fn airBitcast(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 3070 | const ty_op = func.air.instructions.items(.data)[inst].ty_op; | 3185 | const ty_op = func.air.instructions.items(.data)[inst].ty_op; |
| 3071 | const result = if (!func.liveness.isUnused(inst)) result: { | 3186 | const result = if (!func.liveness.isUnused(inst)) result: { |
| 3072 | const operand = try func.resolveInst(ty_op.operand); | 3187 | const operand = try func.resolveInst(ty_op.operand); |
| | 3188 | const wanted_ty = func.air.typeOfIndex(inst); |
| | 3189 | const given_ty = func.air.typeOf(ty_op.operand); |
| | 3190 | if (given_ty.isAnyFloat() or wanted_ty.isAnyFloat()) { |
| | 3191 | const bitcast_result = try func.bitcast(wanted_ty, given_ty, operand); |
| | 3192 | break :result try bitcast_result.toLocal(func, wanted_ty); |
| | 3193 | } |
| 3073 | break :result func.reuseOperand(ty_op.operand, operand); | 3194 | break :result func.reuseOperand(ty_op.operand, operand); |
| 3074 | } else WValue{ .none = {} }; | 3195 | } else WValue{ .none = {} }; |
| 3075 | func.finishAir(inst, result, &.{}); | 3196 | func.finishAir(inst, result, &.{}); |
| 3076 | } | 3197 | } |
| 3077 | | 3198 | |
| | 3199 | fn bitcast(func: *CodeGen, wanted_ty: Type, given_ty: Type, operand: WValue) InnerError!WValue { |
| | 3200 | // if we bitcast a float to or from an integer we must use the 'reinterpret' instruction |
| | 3201 | if (!(wanted_ty.isAnyFloat() or given_ty.isAnyFloat())) return operand; |
| | 3202 | assert((wanted_ty.isInt() and given_ty.isAnyFloat()) or (wanted_ty.isAnyFloat() and given_ty.isInt())); |
| | 3203 | |
| | 3204 | const opcode = buildOpcode(.{ |
| | 3205 | .op = .reinterpret, |
| | 3206 | .valtype1 = typeToValtype(wanted_ty, func.target), |
| | 3207 | .valtype2 = typeToValtype(given_ty, func.target), |
| | 3208 | }); |
| | 3209 | try func.emitWValue(operand); |
| | 3210 | try func.addTag(Mir.Inst.Tag.fromOpcode(opcode)); |
| | 3211 | return WValue{ .stack = {} }; |
| | 3212 | } |
| | 3213 | |
| 3078 | fn airStructFieldPtr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { | 3214 | fn airStructFieldPtr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 3079 | const ty_pl = func.air.instructions.items(.data)[inst].ty_pl; | 3215 | const ty_pl = func.air.instructions.items(.data)[inst].ty_pl; |
| 3080 | const extra = func.air.extraData(Air.StructField, ty_pl.payload); | 3216 | const extra = func.air.extraData(Air.StructField, ty_pl.payload); |
| ... | @@ -3082,13 +3218,7 @@ fn airStructFieldPtr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { | ... | @@ -3082,13 +3218,7 @@ fn airStructFieldPtr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 3082 | | 3218 | |
| 3083 | const struct_ptr = try func.resolveInst(extra.data.struct_operand); | 3219 | const struct_ptr = try func.resolveInst(extra.data.struct_operand); |
| 3084 | const struct_ty = func.air.typeOf(extra.data.struct_operand).childType(); | 3220 | const struct_ty = func.air.typeOf(extra.data.struct_operand).childType(); |
| 3085 | const offset = std.math.cast(u32, struct_ty.structFieldOffset(extra.data.field_index, func.target)) orelse { | 3221 | const result = try func.structFieldPtr(inst, extra.data.struct_operand, struct_ptr, struct_ty, extra.data.field_index); |
| 3086 | const module = func.bin_file.base.options.module.?; | | |
| 3087 | return func.fail("Field type '{}' too big to fit into stack frame", .{ | | |
| 3088 | struct_ty.structFieldType(extra.data.field_index).fmt(module), | | |
| 3089 | }); | | |
| 3090 | }; | | |
| 3091 | const result = try func.structFieldPtr(struct_ptr, offset); | | |
| 3092 | func.finishAir(inst, result, &.{extra.data.struct_operand}); | 3222 | func.finishAir(inst, result, &.{extra.data.struct_operand}); |
| 3093 | } | 3223 | } |
| 3094 | | 3224 | |
| ... | @@ -3097,21 +3227,40 @@ fn airStructFieldPtrIndex(func: *CodeGen, inst: Air.Inst.Index, index: u32) Inne | ... | @@ -3097,21 +3227,40 @@ fn airStructFieldPtrIndex(func: *CodeGen, inst: Air.Inst.Index, index: u32) Inne |
| 3097 | if (func.liveness.isUnused(inst)) return func.finishAir(inst, .none, &.{ty_op.operand}); | 3227 | if (func.liveness.isUnused(inst)) return func.finishAir(inst, .none, &.{ty_op.operand}); |
| 3098 | const struct_ptr = try func.resolveInst(ty_op.operand); | 3228 | const struct_ptr = try func.resolveInst(ty_op.operand); |
| 3099 | const struct_ty = func.air.typeOf(ty_op.operand).childType(); | 3229 | const struct_ty = func.air.typeOf(ty_op.operand).childType(); |
| 3100 | const field_ty = struct_ty.structFieldType(index); | 3230 | |
| 3101 | const offset = std.math.cast(u32, struct_ty.structFieldOffset(index, func.target)) orelse { | 3231 | const result = try func.structFieldPtr(inst, ty_op.operand, struct_ptr, struct_ty, index); |
| 3102 | const module = func.bin_file.base.options.module.?; | | |
| 3103 | return func.fail("Field type '{}' too big to fit into stack frame", .{ | | |
| 3104 | field_ty.fmt(module), | | |
| 3105 | }); | | |
| 3106 | }; | | |
| 3107 | const result = try func.structFieldPtr(struct_ptr, offset); | | |
| 3108 | func.finishAir(inst, result, &.{ty_op.operand}); | 3232 | func.finishAir(inst, result, &.{ty_op.operand}); |
| 3109 | } | 3233 | } |
| 3110 | | 3234 | |
| 3111 | fn structFieldPtr(func: *CodeGen, struct_ptr: WValue, offset: u32) InnerError!WValue { | 3235 | fn structFieldPtr( |
| | 3236 | func: *CodeGen, |
| | 3237 | inst: Air.Inst.Index, |
| | 3238 | ref: Air.Inst.Ref, |
| | 3239 | struct_ptr: WValue, |
| | 3240 | struct_ty: Type, |
| | 3241 | index: u32, |
| | 3242 | ) InnerError!WValue { |
| | 3243 | const result_ty = func.air.typeOfIndex(inst); |
| | 3244 | const offset = switch (struct_ty.containerLayout()) { |
| | 3245 | .Packed => switch (struct_ty.zigTypeTag()) { |
| | 3246 | .Struct => offset: { |
| | 3247 | if (result_ty.ptrInfo().data.host_size != 0) { |
| | 3248 | break :offset @as(u32, 0); |
| | 3249 | } |
| | 3250 | break :offset struct_ty.packedStructFieldByteOffset(index, func.target); |
| | 3251 | }, |
| | 3252 | .Union => 0, |
| | 3253 | else => unreachable, |
| | 3254 | }, |
| | 3255 | else => struct_ty.structFieldOffset(index, func.target), |
| | 3256 | }; |
| | 3257 | // save a load and store when we can simply reuse the operand |
| | 3258 | if (offset == 0) { |
| | 3259 | return func.reuseOperand(ref, struct_ptr); |
| | 3260 | } |
| 3112 | switch (struct_ptr) { | 3261 | switch (struct_ptr) { |
| 3113 | .stack_offset => |stack_offset| { | 3262 | .stack_offset => |stack_offset| { |
| 3114 | return WValue{ .stack_offset = .{ .value = stack_offset.value + offset, .references = 1 } }; | 3263 | return WValue{ .stack_offset = .{ .value = stack_offset.value + @intCast(u32, offset), .references = 1 } }; |
| 3115 | }, | 3264 | }, |
| 3116 | else => return func.buildPointerOffset(struct_ptr, offset, .new), | 3265 | else => return func.buildPointerOffset(struct_ptr, offset, .new), |
| 3117 | } | 3266 | } |
| ... | @@ -3128,24 +3277,75 @@ fn airStructFieldVal(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { | ... | @@ -3128,24 +3277,75 @@ fn airStructFieldVal(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 3128 | const field_ty = struct_ty.structFieldType(field_index); | 3277 | const field_ty = struct_ty.structFieldType(field_index); |
| 3129 | if (!field_ty.hasRuntimeBitsIgnoreComptime()) return func.finishAir(inst, .none, &.{struct_field.struct_operand}); | 3278 | if (!field_ty.hasRuntimeBitsIgnoreComptime()) return func.finishAir(inst, .none, &.{struct_field.struct_operand}); |
| 3130 | | 3279 | |
| 3131 | const offset = std.math.cast(u32, struct_ty.structFieldOffset(field_index, func.target)) orelse { | 3280 | const result = switch (struct_ty.containerLayout()) { |
| 3132 | const module = func.bin_file.base.options.module.?; | 3281 | .Packed => switch (struct_ty.zigTypeTag()) { |
| 3133 | return func.fail("Field type '{}' too big to fit into stack frame", .{field_ty.fmt(module)}); | 3282 | .Struct => result: { |
| 3134 | }; | 3283 | const struct_obj = struct_ty.castTag(.@"struct").?.data; |
| 3135 | | 3284 | assert(struct_obj.layout == .Packed); |
| 3136 | const result = result: { | 3285 | const offset = struct_obj.packedFieldBitOffset(func.target, field_index); |
| 3137 | if (isByRef(field_ty, func.target)) { | 3286 | const backing_ty = struct_obj.backing_int_ty; |
| 3138 | switch (operand) { | 3287 | const wasm_bits = toWasmBits(backing_ty.intInfo(func.target).bits) orelse { |
| 3139 | .stack_offset => |stack_offset| { | 3288 | return func.fail("TODO: airStructFieldVal for packed structs larger than 128 bits", .{}); |
| 3140 | break :result WValue{ .stack_offset = .{ .value = stack_offset.value + offset, .references = 1 } }; | 3289 | }; |
| 3141 | }, | 3290 | const const_wvalue = if (wasm_bits == 32) |
| 3142 | else => break :result try func.buildPointerOffset(operand, offset, .new), | 3291 | WValue{ .imm32 = offset } |
| | 3292 | else if (wasm_bits == 64) |
| | 3293 | WValue{ .imm64 = offset } |
| | 3294 | else |
| | 3295 | return func.fail("TODO: airStructFieldVal for packed structs larger than 64 bits", .{}); |
| | 3296 | |
| | 3297 | // for first field we don't require any shifting |
| | 3298 | const shifted_value = if (offset == 0) |
| | 3299 | operand |
| | 3300 | else |
| | 3301 | try func.binOp(operand, const_wvalue, backing_ty, .shr); |
| | 3302 | |
| | 3303 | if (field_ty.zigTypeTag() == .Float) { |
| | 3304 | var payload: Type.Payload.Bits = .{ |
| | 3305 | .base = .{ .tag = .int_unsigned }, |
| | 3306 | .data = @intCast(u16, field_ty.bitSize(func.target)), |
| | 3307 | }; |
| | 3308 | const int_type = Type.initPayload(&payload.base); |
| | 3309 | const truncated = try func.trunc(shifted_value, int_type, backing_ty); |
| | 3310 | const bitcasted = try func.bitcast(field_ty, int_type, truncated); |
| | 3311 | break :result try bitcasted.toLocal(func, field_ty); |
| | 3312 | } else if (field_ty.isPtrAtRuntime() and struct_obj.fields.count() == 1) { |
| | 3313 | // In this case we do not have to perform any transformations, |
| | 3314 | // we can simply reuse the operand. |
| | 3315 | break :result func.reuseOperand(struct_field.struct_operand, operand); |
| | 3316 | } else if (field_ty.isPtrAtRuntime()) { |
| | 3317 | var payload: Type.Payload.Bits = .{ |
| | 3318 | .base = .{ .tag = .int_unsigned }, |
| | 3319 | .data = @intCast(u16, field_ty.bitSize(func.target)), |
| | 3320 | }; |
| | 3321 | const int_type = Type.initPayload(&payload.base); |
| | 3322 | const truncated = try func.trunc(shifted_value, int_type, backing_ty); |
| | 3323 | break :result try truncated.toLocal(func, field_ty); |
| | 3324 | } |
| | 3325 | const truncated = try func.trunc(shifted_value, field_ty, backing_ty); |
| | 3326 | break :result try truncated.toLocal(func, field_ty); |
| | 3327 | }, |
| | 3328 | .Union => return func.fail("TODO: airStructFieldVal for packed unions", .{}), |
| | 3329 | else => unreachable, |
| | 3330 | }, |
| | 3331 | else => result: { |
| | 3332 | const offset = std.math.cast(u32, struct_ty.structFieldOffset(field_index, func.target)) orelse { |
| | 3333 | const module = func.bin_file.base.options.module.?; |
| | 3334 | return func.fail("Field type '{}' too big to fit into stack frame", .{field_ty.fmt(module)}); |
| | 3335 | }; |
| | 3336 | if (isByRef(field_ty, func.target)) { |
| | 3337 | switch (operand) { |
| | 3338 | .stack_offset => |stack_offset| { |
| | 3339 | break :result WValue{ .stack_offset = .{ .value = stack_offset.value + offset, .references = 1 } }; |
| | 3340 | }, |
| | 3341 | else => break :result try func.buildPointerOffset(operand, offset, .new), |
| | 3342 | } |
| 3143 | } | 3343 | } |
| 3144 | } | 3344 | const field = try func.load(operand, field_ty, offset); |
| 3145 | | 3345 | break :result try field.toLocal(func, field_ty); |
| 3146 | const field = try func.load(operand, field_ty, offset); | 3346 | }, |
| 3147 | break :result try field.toLocal(func, field_ty); | | |
| 3148 | }; | 3347 | }; |
| | 3348 | |
| 3149 | func.finishAir(inst, result, &.{struct_field.struct_operand}); | 3349 | func.finishAir(inst, result, &.{struct_field.struct_operand}); |
| 3150 | } | 3350 | } |
| 3151 | | 3351 | |
| ... | @@ -3492,13 +3692,13 @@ fn airIntcast(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { | ... | @@ -3492,13 +3692,13 @@ fn airIntcast(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 3492 | /// Asserts type's bitsize <= 128 | 3692 | /// Asserts type's bitsize <= 128 |
| 3493 | /// NOTE: May leave the result on the top of the stack. | 3693 | /// NOTE: May leave the result on the top of the stack. |
| 3494 | fn intcast(func: *CodeGen, operand: WValue, given: Type, wanted: Type) InnerError!WValue { | 3694 | fn intcast(func: *CodeGen, operand: WValue, given: Type, wanted: Type) InnerError!WValue { |
| 3495 | const given_info = given.intInfo(func.target); | 3695 | const given_bitsize = @intCast(u16, given.bitSize(func.target)); |
| 3496 | const wanted_info = wanted.intInfo(func.target); | 3696 | const wanted_bitsize = @intCast(u16, wanted.bitSize(func.target)); |
| 3497 | assert(given_info.bits <= 128); | 3697 | assert(given_bitsize <= 128); |
| 3498 | assert(wanted_info.bits <= 128); | 3698 | assert(wanted_bitsize <= 128); |
| 3499 | | 3699 | |
| 3500 | const op_bits = toWasmBits(given_info.bits).?; | 3700 | const op_bits = toWasmBits(given_bitsize).?; |
| 3501 | const wanted_bits = toWasmBits(wanted_info.bits).?; | 3701 | const wanted_bits = toWasmBits(wanted_bitsize).?; |
| 3502 | if (op_bits == wanted_bits) return operand; | 3702 | if (op_bits == wanted_bits) return operand; |
| 3503 | | 3703 | |
| 3504 | if (op_bits > 32 and op_bits <= 64 and wanted_bits == 32) { | 3704 | if (op_bits > 32 and op_bits <= 64 and wanted_bits == 32) { |
| ... | @@ -3506,10 +3706,7 @@ fn intcast(func: *CodeGen, operand: WValue, given: Type, wanted: Type) InnerErro | ... | @@ -3506,10 +3706,7 @@ fn intcast(func: *CodeGen, operand: WValue, given: Type, wanted: Type) InnerErro |
| 3506 | try func.addTag(.i32_wrap_i64); | 3706 | try func.addTag(.i32_wrap_i64); |
| 3507 | } else if (op_bits == 32 and wanted_bits > 32 and wanted_bits <= 64) { | 3707 | } else if (op_bits == 32 and wanted_bits > 32 and wanted_bits <= 64) { |
| 3508 | try func.emitWValue(operand); | 3708 | try func.emitWValue(operand); |
| 3509 | try func.addTag(switch (wanted_info.signedness) { | 3709 | try func.addTag(if (wanted.isSignedInt()) .i64_extend_i32_s else .i64_extend_i32_u); |
| 3510 | .signed => .i64_extend_i32_s, | | |
| 3511 | .unsigned => .i64_extend_i32_u, | | |
| 3512 | }); | | |
| 3513 | } else if (wanted_bits == 128) { | 3710 | } else if (wanted_bits == 128) { |
| 3514 | // for 128bit integers we store the integer in the virtual stack, rather than a local | 3711 | // for 128bit integers we store the integer in the virtual stack, rather than a local |
| 3515 | const stack_ptr = try func.allocStack(wanted); | 3712 | const stack_ptr = try func.allocStack(wanted); |
| ... | @@ -3725,7 +3922,7 @@ fn airSliceElemVal(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { | ... | @@ -3725,7 +3922,7 @@ fn airSliceElemVal(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 3725 | try func.addTag(.i32_mul); | 3922 | try func.addTag(.i32_mul); |
| 3726 | try func.addTag(.i32_add); | 3923 | try func.addTag(.i32_add); |
| 3727 | | 3924 | |
| 3728 | const result_ptr = try func.allocLocal(elem_ty); | 3925 | const result_ptr = try func.allocLocal(Type.usize); |
| 3729 | try func.addLabel(.local_set, result_ptr.local.value); | 3926 | try func.addLabel(.local_set, result_ptr.local.value); |
| 3730 | | 3927 | |
| 3731 | const result = if (!isByRef(elem_ty, func.target)) result: { | 3928 | const result = if (!isByRef(elem_ty, func.target)) result: { |
| ... | @@ -3777,19 +3974,25 @@ fn airTrunc(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { | ... | @@ -3777,19 +3974,25 @@ fn airTrunc(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 3777 | const wanted_ty = func.air.getRefType(ty_op.ty); | 3974 | const wanted_ty = func.air.getRefType(ty_op.ty); |
| 3778 | const op_ty = func.air.typeOf(ty_op.operand); | 3975 | const op_ty = func.air.typeOf(ty_op.operand); |
| 3779 | | 3976 | |
| 3780 | const int_info = op_ty.intInfo(func.target); | 3977 | const result = try func.trunc(operand, wanted_ty, op_ty); |
| 3781 | if (toWasmBits(int_info.bits) == null) { | 3978 | func.finishAir(inst, try result.toLocal(func, wanted_ty), &.{ty_op.operand}); |
| 3782 | return func.fail("TODO: Implement wasm integer truncation for integer bitsize: {d}", .{int_info.bits}); | 3979 | } |
| | 3980 | |
| | 3981 | /// Truncates a given operand to a given type, discarding any overflown bits. |
| | 3982 | /// NOTE: Resulting value is left on the stack. |
| | 3983 | fn trunc(func: *CodeGen, operand: WValue, wanted_ty: Type, given_ty: Type) InnerError!WValue { |
| | 3984 | const given_bits = @intCast(u16, given_ty.bitSize(func.target)); |
| | 3985 | if (toWasmBits(given_bits) == null) { |
| | 3986 | return func.fail("TODO: Implement wasm integer truncation for integer bitsize: {d}", .{given_bits}); |
| 3783 | } | 3987 | } |
| 3784 | | 3988 | |
| 3785 | var result = try func.intcast(operand, op_ty, wanted_ty); | 3989 | var result = try func.intcast(operand, given_ty, wanted_ty); |
| 3786 | const wanted_bits = wanted_ty.intInfo(func.target).bits; | 3990 | const wanted_bits = @intCast(u16, wanted_ty.bitSize(func.target)); |
| 3787 | const wasm_bits = toWasmBits(wanted_bits).?; | 3991 | const wasm_bits = toWasmBits(wanted_bits).?; |
| 3788 | if (wasm_bits != wanted_bits) { | 3992 | if (wasm_bits != wanted_bits) { |
| 3789 | result = try func.wrapOperand(result, wanted_ty); | 3993 | result = try func.wrapOperand(result, wanted_ty); |
| 3790 | } | 3994 | } |
| 3791 | | 3995 | return result; |
| 3792 | func.finishAir(inst, try result.toLocal(func, wanted_ty), &.{ty_op.operand}); | | |
| 3793 | } | 3996 | } |
| 3794 | | 3997 | |
| 3795 | fn airBoolToInt(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { | 3998 | fn airBoolToInt(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| ... | @@ -4186,23 +4389,68 @@ fn airAggregateInit(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { | ... | @@ -4186,23 +4389,68 @@ fn airAggregateInit(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 4186 | } | 4389 | } |
| 4187 | break :result_value result; | 4390 | break :result_value result; |
| 4188 | }, | 4391 | }, |
| 4189 | .Struct => { | 4392 | .Struct => switch (result_ty.containerLayout()) { |
| 4190 | const result = try func.allocStack(result_ty); | 4393 | .Packed => { |
| 4191 | const offset = try func.buildPointerOffset(result, 0, .new); // pointer to offset | 4394 | if (isByRef(result_ty, func.target)) { |
| 4192 | for (elements) |elem, elem_index| { | 4395 | return func.fail("TODO: airAggregateInit for packed structs larger than 64 bits", .{}); |
| 4193 | if (result_ty.structFieldValueComptime(elem_index) != null) continue; | 4396 | } |
| | 4397 | const struct_obj = result_ty.castTag(.@"struct").?.data; |
| | 4398 | const fields = struct_obj.fields.values(); |
| | 4399 | const backing_type = struct_obj.backing_int_ty; |
| | 4400 | // we ensure a new local is created so it's zero-initialized |
| | 4401 | const result = try func.ensureAllocLocal(backing_type); |
| | 4402 | var current_bit: u16 = 0; |
| | 4403 | for (elements) |elem, elem_index| { |
| | 4404 | const field = fields[elem_index]; |
| | 4405 | if (!field.ty.hasRuntimeBitsIgnoreComptime()) continue; |
| | 4406 | |
| | 4407 | const shift_val = if (struct_obj.backing_int_ty.bitSize(func.target) <= 32) |
| | 4408 | WValue{ .imm32 = current_bit } |
| | 4409 | else |
| | 4410 | WValue{ .imm64 = current_bit }; |
| | 4411 | |
| | 4412 | const value = try func.resolveInst(elem); |
| | 4413 | const value_bit_size = @intCast(u16, field.ty.bitSize(func.target)); |
| | 4414 | var int_ty_payload: Type.Payload.Bits = .{ |
| | 4415 | .base = .{ .tag = .int_unsigned }, |
| | 4416 | .data = value_bit_size, |
| | 4417 | }; |
| | 4418 | const int_ty = Type.initPayload(&int_ty_payload.base); |
| | 4419 | |
| | 4420 | // load our current result on stack so we can perform all transformations |
| | 4421 | // using only stack values. Saving the cost of loads and stores. |
| | 4422 | try func.emitWValue(result); |
| | 4423 | const bitcasted = try func.bitcast(int_ty, field.ty, value); |
| | 4424 | const extended_val = try func.intcast(bitcasted, int_ty, backing_type); |
| | 4425 | // no need to shift any values when the current offset is 0 |
| | 4426 | const shifted = if (current_bit != 0) shifted: { |
| | 4427 | break :shifted try func.binOp(extended_val, shift_val, backing_type, .shl); |
| | 4428 | } else extended_val; |
| | 4429 | // we ignore the result as we keep it on the stack to assign it directly to `result` |
| | 4430 | _ = try func.binOp(.stack, shifted, backing_type, .@"or"); |
| | 4431 | try func.addLabel(.local_set, result.local.value); |
| | 4432 | current_bit += value_bit_size; |
| | 4433 | } |
| | 4434 | break :result_value result; |
| | 4435 | }, |
| | 4436 | else => { |
| | 4437 | const result = try func.allocStack(result_ty); |
| | 4438 | const offset = try func.buildPointerOffset(result, 0, .new); // pointer to offset |
| | 4439 | for (elements) |elem, elem_index| { |
| | 4440 | if (result_ty.structFieldValueComptime(elem_index) != null) continue; |
| 4194 | | 4441 | |
| 4195 | const elem_ty = result_ty.structFieldType(elem_index); | 4442 | const elem_ty = result_ty.structFieldType(elem_index); |
| 4196 | const elem_size = @intCast(u32, elem_ty.abiSize(func.target)); | 4443 | const elem_size = @intCast(u32, elem_ty.abiSize(func.target)); |
| 4197 | const value = try func.resolveInst(elem); | 4444 | const value = try func.resolveInst(elem); |
| 4198 | try func.store(offset, value, elem_ty, 0); | 4445 | try func.store(offset, value, elem_ty, 0); |
| 4199 | | 4446 | |
| 4200 | if (elem_index < elements.len - 1) { | 4447 | if (elem_index < elements.len - 1) { |
| 4201 | _ = try func.buildPointerOffset(offset, elem_size, .modify); | 4448 | _ = try func.buildPointerOffset(offset, elem_size, .modify); |
| | 4449 | } |
| 4202 | } | 4450 | } |
| 4203 | } | | |
| 4204 | | 4451 | |
| 4205 | break :result_value result; | 4452 | break :result_value result; |
| | 4453 | }, |
| 4206 | }, | 4454 | }, |
| 4207 | .Vector => return func.fail("TODO: Wasm backend: implement airAggregateInit for vectors", .{}), | 4455 | .Vector => return func.fail("TODO: Wasm backend: implement airAggregateInit for vectors", .{}), |
| 4208 | else => unreachable, | 4456 | else => unreachable, |
| ... | @@ -4444,8 +4692,8 @@ fn airFptrunc(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { | ... | @@ -4444,8 +4692,8 @@ fn airFptrunc(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 4444 | | 4692 | |
| 4445 | const dest_ty = func.air.typeOfIndex(inst); | 4693 | const dest_ty = func.air.typeOfIndex(inst); |
| 4446 | const operand = try func.resolveInst(ty_op.operand); | 4694 | const operand = try func.resolveInst(ty_op.operand); |
| 4447 | const trunc = try func.fptrunc(operand, func.air.typeOf(ty_op.operand), dest_ty); | 4695 | const truncated = try func.fptrunc(operand, func.air.typeOf(ty_op.operand), dest_ty); |
| 4448 | const result = try trunc.toLocal(func, dest_ty); | 4696 | const result = try truncated.toLocal(func, dest_ty); |
| 4449 | func.finishAir(inst, result, &.{ty_op.operand}); | 4697 | func.finishAir(inst, result, &.{ty_op.operand}); |
| 4450 | } | 4698 | } |
| 4451 | | 4699 | |