| ... | @@ -1492,12 +1492,15 @@ fn memcpy(func: *CodeGen, dst: WValue, src: WValue, len: WValue) !void { | ... | @@ -1492,12 +1492,15 @@ fn memcpy(func: *CodeGen, dst: WValue, src: WValue, len: WValue) !void { |
| 1492 | // 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 |
| 1493 | // 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. |
| 1494 | switch (len) { | 1494 | switch (len) { |
| 1495 | .imm32, .imm64 => { | 1495 | .imm32, .imm64 => blk: { |
| 1496 | const length = switch (len) { | 1496 | const length = switch (len) { |
| 1497 | .imm32 => |val| val, | 1497 | .imm32 => |val| val, |
| 1498 | .imm64 => |val| val, | 1498 | .imm64 => |val| val, |
| 1499 | else => unreachable, | 1499 | else => unreachable, |
| 1500 | }; | 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; |
| 1501 | var offset: u32 = 0; | 1504 | var offset: u32 = 0; |
| 1502 | const lhs_base = dst.offset(); | 1505 | const lhs_base = dst.offset(); |
| 1503 | const rhs_base = src.offset(); | 1506 | const rhs_base = src.offset(); |
| ... | @@ -1518,80 +1521,81 @@ fn memcpy(func: *CodeGen, dst: WValue, src: WValue, len: WValue) !void { | ... | @@ -1518,80 +1521,81 @@ fn memcpy(func: *CodeGen, dst: WValue, src: WValue, len: WValue) !void { |
| 1518 | else => unreachable, | 1521 | else => unreachable, |
| 1519 | } | 1522 | } |
| 1520 | } | 1523 | } |
| | 1524 | return; |
| 1521 | }, | 1525 | }, |
| 1522 | else => { | 1526 | else => {}, |
| 1523 | // TODO: We should probably lower this to a call to compiler_rt | 1527 | } |
| 1524 | // But for now, we implement it manually | | |
| 1525 | var offset = try func.ensureAllocLocal(Type.usize); // local for counter | | |
| 1526 | defer offset.free(func); | | |
| 1527 | | 1528 | |
| 1528 | // outer block to jump to when loop is done | 1529 | // TODO: We should probably lower this to a call to compiler_rt |
| 1529 | try func.startBlock(.block, wasm.block_empty); | 1530 | // But for now, we implement it manually |
| 1530 | try func.startBlock(.loop, wasm.block_empty); | 1531 | var offset = try func.ensureAllocLocal(Type.usize); // local for counter |
| | 1532 | defer offset.free(func); |
| 1531 | | 1533 | |
| 1532 | // loop condition (offset == length -> break) | 1534 | // outer block to jump to when loop is done |
| 1533 | { | 1535 | try func.startBlock(.block, wasm.block_empty); |
| 1534 | try func.emitWValue(offset); | 1536 | try func.startBlock(.loop, wasm.block_empty); |
| 1535 | try func.emitWValue(len); | | |
| 1536 | switch (func.arch()) { | | |
| 1537 | .wasm32 => try func.addTag(.i32_eq), | | |
| 1538 | .wasm64 => try func.addTag(.i64_eq), | | |
| 1539 | else => unreachable, | | |
| 1540 | } | | |
| 1541 | try func.addLabel(.br_if, 1); // jump out of loop into outer block (finished) | | |
| 1542 | } | | |
| 1543 | | 1537 | |
| 1544 | // get dst ptr | 1538 | // loop condition (offset == length -> break) |
| 1545 | { | 1539 | { |
| 1546 | try func.emitWValue(dst); | 1540 | try func.emitWValue(offset); |
| 1547 | try func.emitWValue(offset); | 1541 | try func.emitWValue(len); |
| 1548 | switch (func.arch()) { | 1542 | switch (func.arch()) { |
| 1549 | .wasm32 => try func.addTag(.i32_add), | 1543 | .wasm32 => try func.addTag(.i32_eq), |
| 1550 | .wasm64 => try func.addTag(.i64_add), | 1544 | .wasm64 => try func.addTag(.i64_eq), |
| 1551 | else => unreachable, | 1545 | else => unreachable, |
| 1552 | } | 1546 | } |
| 1553 | } | 1547 | try func.addLabel(.br_if, 1); // jump out of loop into outer block (finished) |
| | 1548 | } |
| 1554 | | 1549 | |
| 1555 | // get src value and also store in dst | 1550 | // get dst ptr |
| 1556 | { | 1551 | { |
| 1557 | try func.emitWValue(src); | 1552 | try func.emitWValue(dst); |
| 1558 | try func.emitWValue(offset); | 1553 | try func.emitWValue(offset); |
| 1559 | switch (func.arch()) { | 1554 | switch (func.arch()) { |
| 1560 | .wasm32 => { | 1555 | .wasm32 => try func.addTag(.i32_add), |
| 1561 | try func.addTag(.i32_add); | 1556 | .wasm64 => try func.addTag(.i64_add), |
| 1562 | try func.addMemArg(.i32_load8_u, .{ .offset = src.offset(), .alignment = 1 }); | 1557 | else => unreachable, |
| 1563 | try func.addMemArg(.i32_store8, .{ .offset = dst.offset(), .alignment = 1 }); | 1558 | } |
| 1564 | }, | 1559 | } |
| 1565 | .wasm64 => { | | |
| 1566 | try func.addTag(.i64_add); | | |
| 1567 | try func.addMemArg(.i64_load8_u, .{ .offset = src.offset(), .alignment = 1 }); | | |
| 1568 | try func.addMemArg(.i64_store8, .{ .offset = dst.offset(), .alignment = 1 }); | | |
| 1569 | }, | | |
| 1570 | else => unreachable, | | |
| 1571 | } | | |
| 1572 | } | | |
| 1573 | | 1560 | |
| 1574 | // increment loop counter | 1561 | // get src value and also store in dst |
| 1575 | { | 1562 | { |
| 1576 | try func.emitWValue(offset); | 1563 | try func.emitWValue(src); |
| 1577 | switch (func.arch()) { | 1564 | try func.emitWValue(offset); |
| 1578 | .wasm32 => { | 1565 | switch (func.arch()) { |
| 1579 | try func.addImm32(1); | 1566 | .wasm32 => { |
| 1580 | try func.addTag(.i32_add); | 1567 | try func.addTag(.i32_add); |
| 1581 | }, | 1568 | try func.addMemArg(.i32_load8_u, .{ .offset = src.offset(), .alignment = 1 }); |
| 1582 | .wasm64 => { | 1569 | try func.addMemArg(.i32_store8, .{ .offset = dst.offset(), .alignment = 1 }); |
| 1583 | try func.addImm64(1); | 1570 | }, |
| 1584 | try func.addTag(.i64_add); | 1571 | .wasm64 => { |
| 1585 | }, | 1572 | try func.addTag(.i64_add); |
| 1586 | else => unreachable, | 1573 | try func.addMemArg(.i64_load8_u, .{ .offset = src.offset(), .alignment = 1 }); |
| 1587 | } | 1574 | try func.addMemArg(.i64_store8, .{ .offset = dst.offset(), .alignment = 1 }); |
| 1588 | try func.addLabel(.local_set, offset.local.value); | 1575 | }, |
| 1589 | try func.addLabel(.br, 0); // jump to start of loop | 1576 | else => unreachable, |
| 1590 | } | 1577 | } |
| 1591 | try func.endBlock(); // close off loop block | | |
| 1592 | try func.endBlock(); // close off outer block | | |
| 1593 | }, | | |
| 1594 | } | 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 |
| 1595 | } | 1599 | } |
| 1596 | | 1600 | |
| 1597 | fn ptrSize(func: *const CodeGen) u16 { | 1601 | fn ptrSize(func: *const CodeGen) u16 { |
| ... | @@ -2128,9 +2132,45 @@ fn airStore(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { | ... | @@ -2128,9 +2132,45 @@ fn airStore(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 2128 | | 2132 | |
| 2129 | const lhs = try func.resolveInst(bin_op.lhs); | 2133 | const lhs = try func.resolveInst(bin_op.lhs); |
| 2130 | const rhs = try func.resolveInst(bin_op.rhs); | 2134 | const rhs = try func.resolveInst(bin_op.rhs); |
| 2131 | 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 | var mask = @intCast(u64, (@as(u65, 1) << @intCast(u7, ty.bitSize(func.target))) - 1); |
| | 2150 | mask <<= @intCast(u6, ptr_info.bit_offset); |
| | 2151 | mask ^= ~@as(u64, 0); |
| | 2152 | const shift_val = if (ptr_info.host_size <= 4) |
| | 2153 | WValue{ .imm32 = ptr_info.bit_offset } |
| | 2154 | else |
| | 2155 | WValue{ .imm64 = ptr_info.bit_offset }; |
| | 2156 | const mask_val = if (ptr_info.host_size <= 4) |
| | 2157 | WValue{ .imm32 = @truncate(u32, mask) } |
| | 2158 | else |
| | 2159 | WValue{ .imm64 = mask }; |
| | 2160 | |
| | 2161 | try func.emitWValue(lhs); |
| | 2162 | const loaded = try func.load(lhs, int_elem_ty, 0); |
| | 2163 | const anded = try func.binOp(loaded, mask_val, int_elem_ty, .@"and"); |
| | 2164 | const extended_value = try func.intcast(rhs, ty, int_elem_ty); |
| | 2165 | const shifted_value = if (ptr_info.bit_offset > 0) shifted: { |
| | 2166 | break :shifted try func.binOp(extended_value, shift_val, int_elem_ty, .shl); |
| | 2167 | } else extended_value; |
| | 2168 | const result = try func.binOp(anded, shifted_value, int_elem_ty, .@"or"); |
| | 2169 | std.debug.print("Host: {} ty {} ty {}\n", .{ ptr_info.host_size, int_elem_ty.fmtDebug(), ty.fmtDebug() }); |
| | 2170 | // lhs is still on the stack |
| | 2171 | try func.store(.stack, result, int_elem_ty, 0); |
| | 2172 | } |
| 2132 | | 2173 | |
| 2133 | try func.store(lhs, rhs, ty, 0); | | |
| 2134 | func.finishAir(inst, .none, &.{ bin_op.lhs, bin_op.rhs }); | 2174 | func.finishAir(inst, .none, &.{ bin_op.lhs, bin_op.rhs }); |
| 2135 | } | 2175 | } |
| 2136 | | 2176 | |
| ... | @@ -2218,6 +2258,8 @@ fn airLoad(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { | ... | @@ -2218,6 +2258,8 @@ fn airLoad(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 2218 | const ty_op = func.air.instructions.items(.data)[inst].ty_op; | 2258 | const ty_op = func.air.instructions.items(.data)[inst].ty_op; |
| 2219 | const operand = try func.resolveInst(ty_op.operand); | 2259 | const operand = try func.resolveInst(ty_op.operand); |
| 2220 | const ty = func.air.getRefType(ty_op.ty); | 2260 | const ty = func.air.getRefType(ty_op.ty); |
| | 2261 | const ptr_ty = func.air.typeOf(ty_op.operand); |
| | 2262 | const ptr_info = ptr_ty.ptrInfo().data; |
| 2221 | | 2263 | |
| 2222 | if (!ty.hasRuntimeBitsIgnoreComptime()) return func.finishAir(inst, .none, &.{ty_op.operand}); | 2264 | if (!ty.hasRuntimeBitsIgnoreComptime()) return func.finishAir(inst, .none, &.{ty_op.operand}); |
| 2223 | | 2265 | |
| ... | @@ -2228,8 +2270,28 @@ fn airLoad(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { | ... | @@ -2228,8 +2270,28 @@ fn airLoad(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 2228 | break :result new_local; | 2270 | break :result new_local; |
| 2229 | } | 2271 | } |
| 2230 | | 2272 | |
| 2231 | const stack_loaded = try func.load(operand, ty, 0); | 2273 | if (ptr_info.host_size == 0) { |
| 2232 | break :result try stack_loaded.toLocal(func, ty); | 2274 | const stack_loaded = try func.load(operand, ty, 0); |
| | 2275 | break :result try stack_loaded.toLocal(func, ty); |
| | 2276 | } |
| | 2277 | |
| | 2278 | // at this point we have a non-natural alignment, we must |
| | 2279 | // shift the value to obtain the correct bit. |
| | 2280 | var int_ty_payload: Type.Payload.Bits = .{ |
| | 2281 | .base = .{ .tag = .int_unsigned }, |
| | 2282 | .data = ptr_info.host_size * 8, |
| | 2283 | }; |
| | 2284 | const int_elem_ty = Type.initPayload(&int_ty_payload.base); |
| | 2285 | const shift_val = if (ptr_info.host_size <= 4) |
| | 2286 | WValue{ .imm32 = ptr_info.bit_offset } |
| | 2287 | else |
| | 2288 | WValue{ .imm64 = ptr_info.bit_offset }; |
| | 2289 | |
| | 2290 | const stack_loaded = try func.load(operand, int_elem_ty, 0); |
| | 2291 | const shifted = try func.binOp(stack_loaded, shift_val, int_elem_ty, .shr); |
| | 2292 | const result = try func.trunc(shifted, ty, int_elem_ty); |
| | 2293 | // const wrapped = try func.wrapOperand(shifted, ty); |
| | 2294 | break :result try result.toLocal(func, ty); |
| 2233 | }; | 2295 | }; |
| 2234 | func.finishAir(inst, result, &.{ty_op.operand}); | 2296 | func.finishAir(inst, result, &.{ty_op.operand}); |
| 2235 | } | 2297 | } |
| ... | @@ -3151,7 +3213,7 @@ fn airStructFieldPtr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { | ... | @@ -3151,7 +3213,7 @@ fn airStructFieldPtr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 3151 | | 3213 | |
| 3152 | const struct_ptr = try func.resolveInst(extra.data.struct_operand); | 3214 | const struct_ptr = try func.resolveInst(extra.data.struct_operand); |
| 3153 | const struct_ty = func.air.typeOf(extra.data.struct_operand).childType(); | 3215 | const struct_ty = func.air.typeOf(extra.data.struct_operand).childType(); |
| 3154 | const result = try func.structFieldPtr(struct_ptr, struct_ty, extra.data.field_index); | 3216 | const result = try func.structFieldPtr(extra.data.struct_operand, struct_ptr, struct_ty, extra.data.field_index); |
| 3155 | func.finishAir(inst, result, &.{extra.data.struct_operand}); | 3217 | func.finishAir(inst, result, &.{extra.data.struct_operand}); |
| 3156 | } | 3218 | } |
| 3157 | | 3219 | |
| ... | @@ -3161,11 +3223,11 @@ fn airStructFieldPtrIndex(func: *CodeGen, inst: Air.Inst.Index, index: u32) Inne | ... | @@ -3161,11 +3223,11 @@ fn airStructFieldPtrIndex(func: *CodeGen, inst: Air.Inst.Index, index: u32) Inne |
| 3161 | const struct_ptr = try func.resolveInst(ty_op.operand); | 3223 | const struct_ptr = try func.resolveInst(ty_op.operand); |
| 3162 | const struct_ty = func.air.typeOf(ty_op.operand).childType(); | 3224 | const struct_ty = func.air.typeOf(ty_op.operand).childType(); |
| 3163 | | 3225 | |
| 3164 | const result = try func.structFieldPtr(struct_ptr, struct_ty, index); | 3226 | const result = try func.structFieldPtr(ty_op.operand, struct_ptr, struct_ty, index); |
| 3165 | func.finishAir(inst, result, &.{ty_op.operand}); | 3227 | func.finishAir(inst, result, &.{ty_op.operand}); |
| 3166 | } | 3228 | } |
| 3167 | | 3229 | |
| 3168 | fn structFieldPtr(func: *CodeGen, struct_ptr: WValue, struct_ty: Type, index: u32) InnerError!WValue { | 3230 | fn structFieldPtr(func: *CodeGen, ref: Air.Inst.Ref, struct_ptr: WValue, struct_ty: Type, index: u32) InnerError!WValue { |
| 3169 | const offset = switch (struct_ty.containerLayout()) { | 3231 | const offset = switch (struct_ty.containerLayout()) { |
| 3170 | .Packed => switch (struct_ty.zigTypeTag()) { | 3232 | .Packed => switch (struct_ty.zigTypeTag()) { |
| 3171 | .Struct => struct_ty.packedStructFieldByteOffset(index, func.target), | 3233 | .Struct => struct_ty.packedStructFieldByteOffset(index, func.target), |
| ... | @@ -3174,6 +3236,10 @@ fn structFieldPtr(func: *CodeGen, struct_ptr: WValue, struct_ty: Type, index: u3 | ... | @@ -3174,6 +3236,10 @@ fn structFieldPtr(func: *CodeGen, struct_ptr: WValue, struct_ty: Type, index: u3 |
| 3174 | }, | 3236 | }, |
| 3175 | else => struct_ty.structFieldOffset(index, func.target), | 3237 | else => struct_ty.structFieldOffset(index, func.target), |
| 3176 | }; | 3238 | }; |
| | 3239 | // save a load and store when we can simply reuse the operand |
| | 3240 | if (offset == 0) { |
| | 3241 | return func.reuseOperand(ref, struct_ptr); |
| | 3242 | } |
| 3177 | switch (struct_ptr) { | 3243 | switch (struct_ptr) { |
| 3178 | .stack_offset => |stack_offset| { | 3244 | .stack_offset => |stack_offset| { |
| 3179 | return WValue{ .stack_offset = .{ .value = stack_offset.value + @intCast(u32, offset), .references = 1 } }; | 3245 | return WValue{ .stack_offset = .{ .value = stack_offset.value + @intCast(u32, offset), .references = 1 } }; |
| ... | @@ -3893,9 +3959,9 @@ fn airTrunc(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { | ... | @@ -3893,9 +3959,9 @@ fn airTrunc(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 3893 | /// Truncates a given operand to a given type, discarding any overflown bits. | 3959 | /// Truncates a given operand to a given type, discarding any overflown bits. |
| 3894 | /// NOTE: Resulting value is left on the stack. | 3960 | /// NOTE: Resulting value is left on the stack. |
| 3895 | fn trunc(func: *CodeGen, operand: WValue, wanted_ty: Type, given_ty: Type) InnerError!WValue { | 3961 | fn trunc(func: *CodeGen, operand: WValue, wanted_ty: Type, given_ty: Type) InnerError!WValue { |
| 3896 | const int_info = given_ty.intInfo(func.target); | 3962 | const given_bits = @intCast(u16, given_ty.bitSize(func.target)); |
| 3897 | if (toWasmBits(int_info.bits) == null) { | 3963 | if (toWasmBits(given_bits) == null) { |
| 3898 | return func.fail("TODO: Implement wasm integer truncation for integer bitsize: {d}", .{int_info.bits}); | 3964 | return func.fail("TODO: Implement wasm integer truncation for integer bitsize: {d}", .{given_bits}); |
| 3899 | } | 3965 | } |
| 3900 | | 3966 | |
| 3901 | var result = try func.intcast(operand, given_ty, wanted_ty); | 3967 | var result = try func.intcast(operand, given_ty, wanted_ty); |