| ... | @@ -1122,6 +1122,26 @@ fn moveStack(self: *Self, offset: u32, local: u32) !void { | ... | @@ -1122,6 +1122,26 @@ fn moveStack(self: *Self, offset: u32, local: u32) !void { |
| 1122 | try self.addLabel(.global_set, 0); | 1122 | try self.addLabel(.global_set, 0); |
| 1123 | } | 1123 | } |
| 1124 | | 1124 | |
| | 1125 | /// From a given type, will create space on the virtual stack to store the value of such type. |
| | 1126 | /// This returns a `WValue` with its active tag set to `local`, containing the index to the local |
| | 1127 | /// that points to the position on the virtual stack. This function should be used instead of |
| | 1128 | /// moveStack unless a local was already created to store the point. |
| | 1129 | /// |
| | 1130 | /// Asserts Type has codegenbits |
| | 1131 | fn allocStack(self: *Self, ty: Type) !WValue { |
| | 1132 | assert(ty.hasCodeGenBits()); |
| | 1133 | |
| | 1134 | // calculate needed stack space |
| | 1135 | const abi_size = std.math.cast(u32, ty.abiSize(self.target)) catch { |
| | 1136 | return self.fail("Given type '{}' too big to fit into stack frame", .{ty}); |
| | 1137 | }; |
| | 1138 | |
| | 1139 | // allocate a local using wasm's pointer size |
| | 1140 | const local = try self.allocLocal(Type.@"usize"); |
| | 1141 | try self.moveStack(abi_size, local.local); |
| | 1142 | return local; |
| | 1143 | } |
| | 1144 | |
| 1125 | /// From given zig bitsize, returns the wasm bitsize | 1145 | /// From given zig bitsize, returns the wasm bitsize |
| 1126 | fn toWasmIntBits(bits: u16) ?u16 { | 1146 | fn toWasmIntBits(bits: u16) ?u16 { |
| 1127 | return for ([_]u16{ 32, 64 }) |wasm_bits| { | 1147 | return for ([_]u16{ 32, 64 }) |wasm_bits| { |
| ... | @@ -1238,21 +1258,24 @@ fn airRetPtr(self: *Self, inst: Air.Inst.Index) InnerError!WValue { | ... | @@ -1238,21 +1258,24 @@ fn airRetPtr(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 1238 | try self.initializeStack(); | 1258 | try self.initializeStack(); |
| 1239 | } | 1259 | } |
| 1240 | | 1260 | |
| 1241 | const abi_size = child_type.abiSize(self.target); | 1261 | if (child_type.abiSize(self.target) == 0) return WValue{ .none = {} }; |
| 1242 | if (abi_size == 0) return WValue{ .none = {} }; | | |
| 1243 | | 1262 | |
| 1244 | // local, containing the offset to the stack position | 1263 | return self.allocStack(child_type); |
| 1245 | const local = try self.allocLocal(Type.initTag(.i32)); // always pointer therefore i32 | | |
| 1246 | try self.moveStack(@intCast(u32, abi_size), local.local); | | |
| 1247 | return local; | | |
| 1248 | } | 1264 | } |
| 1249 | | 1265 | |
| 1250 | fn airRetLoad(self: *Self, inst: Air.Inst.Index) InnerError!WValue { | 1266 | fn airRetLoad(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 1251 | const un_op = self.air.instructions.items(.data)[inst].un_op; | 1267 | const un_op = self.air.instructions.items(.data)[inst].un_op; |
| 1252 | const operand = self.resolveInst(un_op); | 1268 | const operand = self.resolveInst(un_op); |
| | 1269 | const ret_ty = self.air.typeOf(un_op).childType(); |
| | 1270 | if (!ret_ty.hasCodeGenBits()) return WValue.none; |
| | 1271 | |
| | 1272 | if (ret_ty.isSlice() or ret_ty.zigTypeTag() == .ErrorUnion) { |
| | 1273 | try self.emitWValue(operand); |
| | 1274 | } else { |
| | 1275 | const result = try self.load(operand, ret_ty, 0); |
| | 1276 | try self.emitWValue(result); |
| | 1277 | } |
| 1253 | | 1278 | |
| 1254 | const result = try self.load(operand, self.air.typeOf(un_op).childType(), 0); | | |
| 1255 | try self.emitWValue(result); | | |
| 1256 | try self.restoreStackPointer(); | 1279 | try self.restoreStackPointer(); |
| 1257 | try self.addTag(.@"return"); | 1280 | try self.addTag(.@"return"); |
| 1258 | return .none; | 1281 | return .none; |
| ... | @@ -1287,10 +1310,22 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue { | ... | @@ -1287,10 +1310,22 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 1287 | | 1310 | |
| 1288 | const arg_ty = self.air.typeOf(arg_ref); | 1311 | const arg_ty = self.air.typeOf(arg_ref); |
| 1289 | if (!arg_ty.hasCodeGenBits()) continue; | 1312 | if (!arg_ty.hasCodeGenBits()) continue; |
| | 1313 | // Passing constant function pointers must be turned into a stack pointer first. |
| | 1314 | // This is because function pointers are stored as function table indexes, |
| | 1315 | // Which means we would try to attempt to load a function pointer's value by reading |
| | 1316 | // from the table index, rather than an address. |
| | 1317 | var is_fn_ptr = false; |
| | 1318 | if (arg_val == .constant) { |
| | 1319 | if (arg_val.constant.val.castTag(.decl_ref)) |decl| { |
| | 1320 | if (decl.data.ty.zigTypeTag() == .Fn) { |
| | 1321 | is_fn_ptr = true; |
| | 1322 | } |
| | 1323 | } |
| | 1324 | } |
| 1290 | switch (arg_ty.zigTypeTag()) { | 1325 | switch (arg_ty.zigTypeTag()) { |
| 1291 | .Struct, .Pointer, .Optional, .ErrorUnion => { | 1326 | .Struct, .Pointer, .Optional, .ErrorUnion => { |
| 1292 | // single pointer can be passed directly | 1327 | // single pointer can be passed directly |
| 1293 | if (arg_ty.isSinglePointer() or arg_val != .constant) { | 1328 | if ((arg_ty.isSinglePointer() and !is_fn_ptr) or arg_val != .constant) { |
| 1294 | if (arg_val == .none) { | 1329 | if (arg_val == .none) { |
| 1295 | // when the argument is a 0-sized value, but the function | 1330 | // when the argument is a 0-sized value, but the function |
| 1296 | // expects a non-zero typed value (such as a slice), we must emit an argument | 1331 | // expects a non-zero typed value (such as a slice), we must emit an argument |
| ... | @@ -1302,9 +1337,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue { | ... | @@ -1302,9 +1337,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 1302 | try self.emitWValue(arg_val); | 1337 | try self.emitWValue(arg_val); |
| 1303 | continue; | 1338 | continue; |
| 1304 | } | 1339 | } |
| 1305 | const abi_size = arg_ty.abiSize(self.target); | 1340 | const arg_local = try self.allocStack(arg_ty); |
| 1306 | const arg_local = try self.allocLocal(Type.initTag(.i32)); | | |
| 1307 | try self.moveStack(@intCast(u32, abi_size), arg_local.local); | | |
| 1308 | try self.store(arg_local, arg_val, arg_ty, 0); | 1341 | try self.store(arg_local, arg_val, arg_ty, 0); |
| 1309 | try self.emitWValue(arg_local); | 1342 | try self.emitWValue(arg_local); |
| 1310 | }, | 1343 | }, |
| ... | @@ -1336,9 +1369,28 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue { | ... | @@ -1336,9 +1369,28 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 1336 | const ret_ty = fn_ty.fnReturnType(); | 1369 | const ret_ty = fn_ty.fnReturnType(); |
| 1337 | if (!ret_ty.hasCodeGenBits()) return WValue.none; | 1370 | if (!ret_ty.hasCodeGenBits()) return WValue.none; |
| 1338 | | 1371 | |
| | 1372 | // slices are stored on the virtual stack, so we must pull out both ptr and len |
| | 1373 | // to not overwrite the stack |
| | 1374 | if (ret_ty.isSlice()) { |
| | 1375 | // first load the values onto the regular stack, before we move the stack pointer |
| | 1376 | // to prevent overwriting the return value. |
| | 1377 | const tmp = try self.allocLocal(ret_ty); |
| | 1378 | try self.addLabel(.local_set, tmp.local); |
| | 1379 | const field_ty = Type.@"usize"; |
| | 1380 | const offset = @intCast(u32, field_ty.abiSize(self.target)); |
| | 1381 | const ptr_local = try self.load(tmp, field_ty, 0); |
| | 1382 | const len_local = try self.load(tmp, field_ty, offset); |
| | 1383 | |
| | 1384 | // As our values are now safe, we reserve space on the virtual stack and |
| | 1385 | // store the values there. |
| | 1386 | const result = try self.allocStack(ret_ty); |
| | 1387 | try self.store(result, ptr_local, field_ty, 0); |
| | 1388 | try self.store(result, len_local, field_ty, offset); |
| | 1389 | return result; |
| | 1390 | } |
| | 1391 | |
| 1339 | const result_local = try self.allocLocal(ret_ty); | 1392 | const result_local = try self.allocLocal(ret_ty); |
| 1340 | try self.addLabel(.local_set, result_local.local); | 1393 | try self.addLabel(.local_set, result_local.local); |
| 1341 | // if the result was allocated on the virtual stack, we must load | | |
| 1342 | return result_local; | 1394 | return result_local; |
| 1343 | } | 1395 | } |
| 1344 | | 1396 | |
| ... | @@ -1349,14 +1401,8 @@ fn airAlloc(self: *Self, inst: Air.Inst.Index) InnerError!WValue { | ... | @@ -1349,14 +1401,8 @@ fn airAlloc(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 1349 | if (self.initial_stack_value == .none) { | 1401 | if (self.initial_stack_value == .none) { |
| 1350 | try self.initializeStack(); | 1402 | try self.initializeStack(); |
| 1351 | } | 1403 | } |
| 1352 | | 1404 | if (child_type.abiSize(self.target) == 0) return WValue{ .none = {} }; |
| 1353 | const abi_size = child_type.abiSize(self.target); | 1405 | return self.allocStack(child_type); |
| 1354 | if (abi_size == 0) return WValue{ .none = {} }; | | |
| 1355 | | | |
| 1356 | // local, containing the offset to the stack position | | |
| 1357 | const local = try self.allocLocal(Type.initTag(.i32)); // always pointer therefore i32 | | |
| 1358 | try self.moveStack(@intCast(u32, abi_size), local.local); | | |
| 1359 | return local; | | |
| 1360 | } | 1406 | } |
| 1361 | | 1407 | |
| 1362 | fn airStore(self: *Self, inst: Air.Inst.Index) InnerError!WValue { | 1408 | fn airStore(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| ... | @@ -1453,12 +1499,33 @@ fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerErro | ... | @@ -1453,12 +1499,33 @@ fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerErro |
| 1453 | .Pointer => { | 1499 | .Pointer => { |
| 1454 | if (ty.isSlice() and rhs == .constant) { | 1500 | if (ty.isSlice() and rhs == .constant) { |
| 1455 | try self.emitWValue(rhs); | 1501 | try self.emitWValue(rhs); |
| | 1502 | |
| | 1503 | const val = rhs.constant.val; |
| 1456 | const len_local = try self.allocLocal(Type.usize); | 1504 | const len_local = try self.allocLocal(Type.usize); |
| 1457 | const ptr_local = try self.allocLocal(Type.usize); | 1505 | const ptr_local = try self.allocLocal(Type.usize); |
| | 1506 | const len_offset = self.target.cpu.arch.ptrBitWidth() / 8; |
| | 1507 | if (val.castTag(.decl_ref)) |decl| { |
| | 1508 | // for decl references we also need to retrieve the length and the original decl's pointer |
| | 1509 | try self.addMemArg(.i32_load, .{ .offset = 0, .alignment = Type.@"usize".abiAlignment(self.target) }); |
| | 1510 | try self.addLabel(.memory_address, decl.data.link.wasm.sym_index); |
| | 1511 | try self.addMemArg( |
| | 1512 | .i32_load, |
| | 1513 | .{ .offset = len_offset, .alignment = Type.@"usize".abiAlignment(self.target) }, |
| | 1514 | ); |
| | 1515 | } |
| 1458 | try self.addLabel(.local_set, len_local.local); | 1516 | try self.addLabel(.local_set, len_local.local); |
| 1459 | try self.addLabel(.local_set, ptr_local.local); | 1517 | try self.addLabel(.local_set, ptr_local.local); |
| 1460 | try self.store(lhs, ptr_local, Type.usize, 0); | 1518 | try self.store(lhs, ptr_local, Type.usize, 0); |
| 1461 | try self.store(lhs, len_local, Type.usize, self.target.cpu.arch.ptrBitWidth() / 8); | 1519 | try self.store(lhs, len_local, Type.usize, len_offset); |
| | 1520 | return; |
| | 1521 | } else if (ty.isSlice()) { |
| | 1522 | // store pointer first |
| | 1523 | const ptr_local = try self.load(rhs, Type.@"usize", 0); |
| | 1524 | try self.store(lhs, ptr_local, Type.@"usize", 0); |
| | 1525 | |
| | 1526 | // retrieve length from rhs, and store that alongside lhs as well |
| | 1527 | const len_local = try self.load(rhs, Type.@"usize", 4); |
| | 1528 | try self.store(lhs, len_local, Type.@"usize", 4); |
| 1462 | return; | 1529 | return; |
| 1463 | } | 1530 | } |
| 1464 | }, | 1531 | }, |
| ... | @@ -1732,6 +1799,20 @@ fn emitConstant(self: *Self, val: Value, ty: Type) InnerError!void { | ... | @@ -1732,6 +1799,20 @@ fn emitConstant(self: *Self, val: Value, ty: Type) InnerError!void { |
| 1732 | try self.addImm32(0); | 1799 | try self.addImm32(0); |
| 1733 | } | 1800 | } |
| 1734 | }, | 1801 | }, |
| | 1802 | .Struct => { |
| | 1803 | const struct_data = val.castTag(.@"struct").?; |
| | 1804 | // in case of structs, we reserve stack space and store it there. |
| | 1805 | const result = try self.allocStack(ty); |
| | 1806 | |
| | 1807 | const fields = ty.structFields(); |
| | 1808 | for (fields.values()) |field, index| { |
| | 1809 | const tmp = try self.allocLocal(field.ty); |
| | 1810 | try self.emitConstant(struct_data.data[index], field.ty); |
| | 1811 | try self.addLabel(.local_set, tmp.local); |
| | 1812 | try self.store(result, tmp, field.ty, field.offset); |
| | 1813 | } |
| | 1814 | try self.addLabel(.local_get, result.local); |
| | 1815 | }, |
| 1735 | else => |zig_type| return self.fail("Wasm TODO: emitConstant for zigTypeTag {s}", .{zig_type}), | 1816 | else => |zig_type| return self.fail("Wasm TODO: emitConstant for zigTypeTag {s}", .{zig_type}), |
| 1736 | } | 1817 | } |
| 1737 | } | 1818 | } |
| ... | @@ -1748,7 +1829,14 @@ fn emitUndefined(self: *Self, ty: Type) InnerError!void { | ... | @@ -1748,7 +1829,14 @@ fn emitUndefined(self: *Self, ty: Type) InnerError!void { |
| 1748 | 33...64 => try self.addFloat64(@bitCast(f64, @as(u64, 0xaaaaaaaaaaaaaaaa))), | 1829 | 33...64 => try self.addFloat64(@bitCast(f64, @as(u64, 0xaaaaaaaaaaaaaaaa))), |
| 1749 | else => |bits| return self.fail("Wasm TODO: emitUndefined for float bitsize: {d}", .{bits}), | 1830 | else => |bits| return self.fail("Wasm TODO: emitUndefined for float bitsize: {d}", .{bits}), |
| 1750 | }, | 1831 | }, |
| 1751 | .Array => try self.addImm32(@bitCast(i32, @as(u32, 0xaaaaaaaa))), | 1832 | // As arrays point to linear memory, we cannot use 0xaaaaaaaa as the wasm |
| | 1833 | // validator will not accept it due to out-of-bounds memory access); |
| | 1834 | .Array => try self.addImm32(@bitCast(i32, @as(u32, 0xaa))), |
| | 1835 | .Struct => { |
| | 1836 | // TODO: Write 0xaa to each field |
| | 1837 | const result = try self.allocStack(ty); |
| | 1838 | try self.addLabel(.local_get, result.local); |
| | 1839 | }, |
| 1752 | else => return self.fail("Wasm TODO: emitUndefined for type: {}\n", .{ty}), | 1840 | else => return self.fail("Wasm TODO: emitUndefined for type: {}\n", .{ty}), |
| 1753 | } | 1841 | } |
| 1754 | } | 1842 | } |
| ... | @@ -2221,24 +2309,43 @@ fn airWrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue { | ... | @@ -2221,24 +2309,43 @@ fn airWrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 2221 | const operand = self.resolveInst(ty_op.operand); | 2309 | const operand = self.resolveInst(ty_op.operand); |
| 2222 | | 2310 | |
| 2223 | const op_ty = self.air.typeOf(ty_op.operand); | 2311 | const op_ty = self.air.typeOf(ty_op.operand); |
| 2224 | if (!op_ty.hasCodeGenBits()) return WValue.none; | 2312 | if (!op_ty.hasCodeGenBits()) return operand; |
| 2225 | const err_ty = self.air.getRefType(ty_op.ty); | 2313 | const err_ty = self.air.getRefType(ty_op.ty); |
| 2226 | const offset = err_ty.errorUnionSet().abiSize(self.target); | 2314 | const offset = err_ty.errorUnionSet().abiSize(self.target); |
| 2227 | | 2315 | |
| 2228 | return WValue{ .local_with_offset = .{ | 2316 | const err_union = try self.allocStack(err_ty); |
| 2229 | .local = operand.local, | 2317 | const to_store = switch (op_ty.zigTypeTag()) { |
| 2230 | .offset = @intCast(u32, offset), | 2318 | // for those types we must load the pointer and then store |
| 2231 | } }; | 2319 | // its value |
| | 2320 | .Pointer, .Optional => blk: { |
| | 2321 | if (!op_ty.isPtrLikeOptional()) { |
| | 2322 | return self.fail("TODO: airWrapErrUnionPayload for optional type {}", .{op_ty}); |
| | 2323 | } |
| | 2324 | break :blk try self.load(operand, op_ty, 0); |
| | 2325 | }, |
| | 2326 | .Int => operand, |
| | 2327 | else => return self.fail("TODO: airWrapErrUnionPayload for type {}", .{op_ty}), |
| | 2328 | }; |
| | 2329 | |
| | 2330 | try self.store(err_union, to_store, op_ty, @intCast(u32, offset)); |
| | 2331 | |
| | 2332 | // ensure we also write '0' to the error part, so any present stack value gets overwritten by it. |
| | 2333 | const tmp_local = try self.allocLocal(err_ty.errorUnionSet()); // locals are '0' by default. |
| | 2334 | try self.store(err_union, tmp_local, err_ty.errorUnionSet(), 0); |
| | 2335 | |
| | 2336 | return err_union; |
| 2232 | } | 2337 | } |
| 2233 | | 2338 | |
| 2234 | fn airWrapErrUnionErr(self: *Self, inst: Air.Inst.Index) InnerError!WValue { | 2339 | fn airWrapErrUnionErr(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 2235 | if (self.liveness.isUnused(inst)) return WValue.none; | 2340 | if (self.liveness.isUnused(inst)) return WValue.none; |
| 2236 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; | 2341 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; |
| 2237 | const operand = self.resolveInst(ty_op.operand); | 2342 | const operand = self.resolveInst(ty_op.operand); |
| 2238 | return WValue{ .local_with_offset = .{ | 2343 | const err_ty = self.air.getRefType(ty_op.ty); |
| 2239 | .local = operand.local, | 2344 | |
| 2240 | .offset = 0, | 2345 | const err_union = try self.allocStack(err_ty); |
| 2241 | } }; | 2346 | // TODO: Also write 'undefined' to the payload |
| | 2347 | try self.store(err_union, operand, err_ty.errorUnionSet(), 0); |
| | 2348 | return err_union; |
| 2242 | } | 2349 | } |
| 2243 | | 2350 | |
| 2244 | fn airIntcast(self: *Self, inst: Air.Inst.Index) InnerError!WValue { | 2351 | fn airIntcast(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| ... | @@ -2276,9 +2383,11 @@ fn airIsNull(self: *Self, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError! | ... | @@ -2276,9 +2383,11 @@ fn airIsNull(self: *Self, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError! |
| 2276 | const un_op = self.air.instructions.items(.data)[inst].un_op; | 2383 | const un_op = self.air.instructions.items(.data)[inst].un_op; |
| 2277 | const operand = self.resolveInst(un_op); | 2384 | const operand = self.resolveInst(un_op); |
| 2278 | | 2385 | |
| 2279 | // load the null tag value | 2386 | const op_ty = self.air.typeOf(un_op); |
| 2280 | try self.emitWValue(operand); | 2387 | try self.emitWValue(operand); |
| 2281 | try self.addMemArg(.i32_load8_u, .{ .offset = 0, .alignment = 1 }); | 2388 | if (!op_ty.isPtrLikeOptional()) { |
| | 2389 | try self.addMemArg(.i32_load8_u, .{ .offset = 0, .alignment = 1 }); |
| | 2390 | } |
| 2282 | | 2391 | |
| 2283 | // Compare the error value with '0' | 2392 | // Compare the error value with '0' |
| 2284 | try self.addImm32(0); | 2393 | try self.addImm32(0); |
| ... | @@ -2469,5 +2578,26 @@ fn airBoolToInt(self: *Self, inst: Air.Inst.Index) InnerError!WValue { | ... | @@ -2469,5 +2578,26 @@ fn airBoolToInt(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 2469 | | 2578 | |
| 2470 | fn airArrayToSlice(self: *Self, inst: Air.Inst.Index) InnerError!WValue { | 2579 | fn airArrayToSlice(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 2471 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; | 2580 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; |
| 2472 | return self.resolveInst(ty_op.operand); | 2581 | const operand = self.resolveInst(ty_op.operand); |
| | 2582 | const array_ty = self.air.typeOf(ty_op.operand).childType(); |
| | 2583 | const ty = Type.@"usize"; |
| | 2584 | const ptr_width = @intCast(u32, ty.abiSize(self.target)); |
| | 2585 | const slice_ty = self.air.getRefType(ty_op.ty); |
| | 2586 | |
| | 2587 | // create a slice on the stack |
| | 2588 | const slice_local = try self.allocStack(slice_ty); |
| | 2589 | |
| | 2590 | // store the array ptr in the slice |
| | 2591 | if (array_ty.hasCodeGenBits()) { |
| | 2592 | try self.store(slice_local, operand, ty, 0); |
| | 2593 | } |
| | 2594 | |
| | 2595 | // store the length of the array in the slice |
| | 2596 | const len = array_ty.arrayLen(); |
| | 2597 | try self.addImm32(@bitCast(i32, @intCast(u32, len))); |
| | 2598 | const len_local = try self.allocLocal(ty); |
| | 2599 | try self.addLabel(.local_set, len_local.local); |
| | 2600 | try self.store(slice_local, len_local, ty, ptr_width); |
| | 2601 | |
| | 2602 | return slice_local; |
| 2473 | } | 2603 | } |