| ... | ... | @@ -1088,10 +1088,8 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) InnerError!CallWValu |
| 1088 | 1088 | } |
| 1089 | 1089 | |
| 1090 | 1090 | const ret_ty = fn_ty.fnReturnType(); |
| 1091 | | switch (ret_ty.zigTypeTag()) { |
| 1092 | | .ErrorUnion, .Optional, .Pointer => result.return_value = try self.allocLocal(Type.initTag(.i32)), |
| 1093 | | .Int, .Float, .Bool, .Void, .NoReturn => {}, |
| 1094 | | else => return self.fail("TODO: Implement function return type {}", .{ret_ty}), |
| 1091 | if (isByRef(ret_ty)) { |
| 1092 | result.return_value = try self.allocLocal(Type.initTag(.i32)); |
| 1095 | 1093 | } |
| 1096 | 1094 | |
| 1097 | 1095 | // Check if we store the result as a pointer to the stack rather than |
| ... | ... | @@ -1204,6 +1202,60 @@ fn memCopy(self: *Self, ty: Type, lhs: WValue, rhs: WValue) !void { |
| 1204 | 1202 | } |
| 1205 | 1203 | } |
| 1206 | 1204 | |
| 1205 | fn ptrSize(self: *const Self) u16 { |
| 1206 | return @divExact(self.target.cpu.arch.ptrBitWidth(), 8); |
| 1207 | } |
| 1208 | |
| 1209 | /// For a given `Type`, will return true when the type will be passed |
| 1210 | /// by reference, rather than by value. |
| 1211 | fn isByRef(ty: Type) bool { |
| 1212 | switch (ty.zigTypeTag()) { |
| 1213 | .Type, |
| 1214 | .ComptimeInt, |
| 1215 | .ComptimeFloat, |
| 1216 | .EnumLiteral, |
| 1217 | .Undefined, |
| 1218 | .Null, |
| 1219 | .BoundFn, |
| 1220 | .Opaque, |
| 1221 | => unreachable, |
| 1222 | |
| 1223 | .NoReturn, |
| 1224 | .Void, |
| 1225 | .Bool, |
| 1226 | .Int, |
| 1227 | .Float, |
| 1228 | .ErrorSet, |
| 1229 | .Fn, |
| 1230 | .Enum, |
| 1231 | .Vector, |
| 1232 | .AnyFrame, |
| 1233 | => return false, |
| 1234 | |
| 1235 | .Array, |
| 1236 | .Struct, |
| 1237 | .Frame, |
| 1238 | .Union, |
| 1239 | => return ty.hasCodeGenBits(), |
| 1240 | .ErrorUnion => { |
| 1241 | const has_tag = ty.errorUnionSet().hasCodeGenBits(); |
| 1242 | const has_pl = ty.errorUnionPayload().hasCodeGenBits(); |
| 1243 | if (!has_tag or !has_pl) return false; |
| 1244 | return ty.hasCodeGenBits(); |
| 1245 | }, |
| 1246 | .Optional => { |
| 1247 | if (ty.isPtrLikeOptional()) return false; |
| 1248 | var buf: Type.Payload.ElemType = undefined; |
| 1249 | return ty.optionalChild(&buf).hasCodeGenBits(); |
| 1250 | }, |
| 1251 | .Pointer => { |
| 1252 | // Slices act like struct and will be passed by reference |
| 1253 | if (ty.isSlice()) return ty.hasCodeGenBits(); |
| 1254 | return false; |
| 1255 | }, |
| 1256 | } |
| 1257 | } |
| 1258 | |
| 1207 | 1259 | fn genInst(self: *Self, inst: Air.Inst.Index) !WValue { |
| 1208 | 1260 | const air_tags = self.air.instructions.items(.tag); |
| 1209 | 1261 | return switch (air_tags[inst]) { |
| ... | ... | @@ -1255,6 +1307,10 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue { |
| 1255 | 1307 | .optional_payload => self.airOptionalPayload(inst), |
| 1256 | 1308 | .optional_payload_ptr => self.airOptionalPayload(inst), |
| 1257 | 1309 | .optional_payload_ptr_set => self.airOptionalPayloadPtrSet(inst), |
| 1310 | .ptr_add => self.airPtrBinOp(inst, .add), |
| 1311 | .ptr_sub => self.airPtrBinOp(inst, .sub), |
| 1312 | .ptr_elem_ptr => self.airPtrElemPtr(inst), |
| 1313 | .ptr_elem_val => self.airPtrElemVal(inst), |
| 1258 | 1314 | .ptrtoint => self.airPtrToInt(inst), |
| 1259 | 1315 | .ret => self.airRet(inst), |
| 1260 | 1316 | .ret_ptr => self.airRetPtr(inst), |
| ... | ... | @@ -1366,38 +1422,19 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 1366 | 1422 | |
| 1367 | 1423 | const arg_ty = self.air.typeOf(arg_ref); |
| 1368 | 1424 | if (!arg_ty.hasCodeGenBits()) continue; |
| 1369 | | // Passing constant function pointers must be turned into a stack pointer first. |
| 1370 | | // This is because function pointers are stored as function table indexes, |
| 1371 | | // Which means we would try to attempt to load a function pointer's value by reading |
| 1372 | | // from the table index, rather than an address. |
| 1373 | | var is_fn_ptr = false; |
| 1374 | | if (arg_val == .constant) { |
| 1375 | | if (arg_val.constant.val.castTag(.decl_ref)) |decl| { |
| 1376 | | if (decl.data.ty.zigTypeTag() == .Fn) { |
| 1377 | | is_fn_ptr = true; |
| 1378 | | } |
| 1379 | | } |
| 1380 | | } |
| 1381 | | switch (arg_ty.zigTypeTag()) { |
| 1382 | | .Struct, .Pointer, .Optional, .ErrorUnion => { |
| 1383 | | // single pointer can be passed directly |
| 1384 | | if ((arg_ty.isSinglePointer() and !is_fn_ptr) or arg_val != .constant) { |
| 1385 | | if (arg_val == .none) { |
| 1386 | | // when the argument is a 0-sized value, but the function |
| 1387 | | // expects a non-zero typed value (such as a slice), we must emit an argument |
| 1388 | | // as function calls are verified with the function signature in wasm. |
| 1389 | | // In those cases we will emit a '0xaa' as address, meaning invalid memory. |
| 1390 | | try self.addImm32(@bitCast(i32, @as(u32, 0xaaaaaaaa))); |
| 1391 | | continue; |
| 1392 | | } |
| 1393 | | try self.emitWValue(arg_val); |
| 1394 | | continue; |
| 1395 | | } |
| 1396 | | const arg_local = try self.allocStack(arg_ty); |
| 1397 | | try self.store(arg_local, arg_val, arg_ty, 0); |
| 1398 | | try self.emitWValue(arg_local); |
| 1399 | | }, |
| 1400 | | else => try self.emitWValue(arg_val), |
| 1425 | |
| 1426 | // If we need to pass by reference, but the argument is a constant, |
| 1427 | // we must first lower it before passing it. |
| 1428 | if (isByRef(arg_ty) and arg_val == .constant) { |
| 1429 | const arg_local = try self.allocStack(arg_ty); |
| 1430 | try self.store(arg_local, arg_val, arg_ty, 0); |
| 1431 | try self.emitWValue(arg_local); |
| 1432 | } else if (arg_val == .none) { |
| 1433 | // TODO: Remove this branch when zero-sized pointers do not generate |
| 1434 | // an argument. |
| 1435 | try self.addImm32(0); |
| 1436 | } else { |
| 1437 | try self.emitWValue(arg_val); |
| 1401 | 1438 | } |
| 1402 | 1439 | } |
| 1403 | 1440 | |
| ... | ... | @@ -1408,12 +1445,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 1408 | 1445 | // so load its value onto the stack |
| 1409 | 1446 | std.debug.assert(ty.zigTypeTag() == .Pointer); |
| 1410 | 1447 | const operand = self.resolveInst(pl_op.operand); |
| 1411 | | const offset = switch (operand) { |
| 1412 | | .local_with_offset => |with_offset| with_offset.offset, |
| 1413 | | else => @as(u32, 0), |
| 1414 | | }; |
| 1415 | | const result = try self.load(operand, fn_ty, offset); |
| 1416 | | try self.addLabel(.local_get, result.local); |
| 1448 | try self.emitWValue(operand); |
| 1417 | 1449 | |
| 1418 | 1450 | var fn_type = try self.genFunctype(fn_ty); |
| 1419 | 1451 | defer fn_type.deinit(self.gpa); |
| ... | ... | @@ -1425,8 +1457,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 1425 | 1457 | const ret_ty = fn_ty.fnReturnType(); |
| 1426 | 1458 | if (!ret_ty.hasCodeGenBits()) return WValue.none; |
| 1427 | 1459 | |
| 1428 | | // slices are stored on the virtual stack, so we must pull out both ptr and len |
| 1429 | | // to not overwrite the stack |
| 1460 | // TODO: Implement this for all aggregate types |
| 1430 | 1461 | if (ret_ty.isSlice()) { |
| 1431 | 1462 | // first load the values onto the regular stack, before we move the stack pointer |
| 1432 | 1463 | // to prevent overwriting the return value. |
| ... | ... | @@ -1551,7 +1582,7 @@ fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerErro |
| 1551 | 1582 | const val = rhs.constant.val; |
| 1552 | 1583 | const len_local = try self.allocLocal(Type.usize); |
| 1553 | 1584 | const ptr_local = try self.allocLocal(Type.usize); |
| 1554 | | const len_offset = self.target.cpu.arch.ptrBitWidth() / 8; |
| 1585 | const len_offset = self.ptrSize(); |
| 1555 | 1586 | if (val.castTag(.decl_ref)) |decl| { |
| 1556 | 1587 | // for decl references we also need to retrieve the length and the original decl's pointer |
| 1557 | 1588 | try self.addMemArg(.i32_load, .{ .offset = 0, .alignment = Type.@"usize".abiAlignment(self.target) }); |
| ... | ... | @@ -1611,12 +1642,13 @@ fn airLoad(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 1611 | 1642 | |
| 1612 | 1643 | if (!ty.hasCodeGenBits()) return WValue{ .none = {} }; |
| 1613 | 1644 | |
| 1614 | | return switch (ty.zigTypeTag()) { |
| 1615 | | .Struct, .ErrorUnion, .Optional, .Pointer => operand, // pass as pointer |
| 1616 | | else => switch (operand) { |
| 1617 | | .local_with_offset => |with_offset| try self.load(operand, ty, with_offset.offset), |
| 1618 | | else => try self.load(operand, ty, 0), |
| 1619 | | }, |
| 1645 | if (isByRef(ty)) { |
| 1646 | return operand; |
| 1647 | } |
| 1648 | |
| 1649 | return switch (operand) { |
| 1650 | .local_with_offset => |with_offset| try self.load(operand, ty, with_offset.offset), |
| 1651 | else => try self.load(operand, ty, 0), |
| 1620 | 1652 | }; |
| 1621 | 1653 | } |
| 1622 | 1654 | |
| ... | ... | @@ -1628,7 +1660,6 @@ fn load(self: *Self, operand: WValue, ty: Type, offset: u32) InnerError!WValue { |
| 1628 | 1660 | .unsigned |
| 1629 | 1661 | else |
| 1630 | 1662 | .signed; |
| 1631 | | // check if we should pass by pointer or value based on ABI size |
| 1632 | 1663 | // TODO: Implement a way to get ABI values from a given type, |
| 1633 | 1664 | // that is portable across the backend, rather than copying logic. |
| 1634 | 1665 | const abi_size = if ((ty.isInt() or ty.isAnyFloat()) and ty.abiSize(self.target) <= 8) |
| ... | ... | @@ -1779,6 +1810,10 @@ fn emitConstant(self: *Self, val: Value, ty: Type) InnerError!void { |
| 1779 | 1810 | } |
| 1780 | 1811 | } else if (val.castTag(.int_u64)) |int_ptr| { |
| 1781 | 1812 | try self.addImm32(@bitCast(i32, @intCast(u32, int_ptr.data))); |
| 1813 | } else if (val.tag() == .zero) { |
| 1814 | try self.addImm32(0); |
| 1815 | } else if (val.tag() == .one) { |
| 1816 | try self.addImm32(1); |
| 1782 | 1817 | } else return self.fail("Wasm TODO: emitConstant for other const pointer tag {s}", .{val.tag()}); |
| 1783 | 1818 | }, |
| 1784 | 1819 | .Void => {}, |
| ... | ... | @@ -2163,12 +2198,8 @@ fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 2163 | 2198 | return self.fail("Field type '{}' too big to fit into stack frame", .{field_ty}); |
| 2164 | 2199 | }; |
| 2165 | 2200 | |
| 2166 | | // TODO: Replace this check with some 'isByRef' function to de-duplicate logic |
| 2167 | | if (field_ty.zigTypeTag() == .Struct) { |
| 2168 | | return WValue{ .local_with_offset = .{ |
| 2169 | | .local = operand.local, |
| 2170 | | .offset = offset, |
| 2171 | | } }; |
| 2201 | if (isByRef(field_ty)) { |
| 2202 | return WValue{ .local_with_offset = .{ .local = operand.local, .offset = offset } }; |
| 2172 | 2203 | } |
| 2173 | 2204 | |
| 2174 | 2205 | switch (operand) { |
| ... | ... | @@ -2328,20 +2359,23 @@ fn airSwitchBr(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 2328 | 2359 | fn airIsErr(self: *Self, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!WValue { |
| 2329 | 2360 | const un_op = self.air.instructions.items(.data)[inst].un_op; |
| 2330 | 2361 | const operand = self.resolveInst(un_op); |
| 2331 | | const err_ty = self.air.typeOf(un_op).errorUnionSet(); |
| 2362 | const err_ty = self.air.typeOf(un_op); |
| 2363 | const pl_ty = err_ty.errorUnionPayload(); |
| 2332 | 2364 | |
| 2333 | 2365 | // load the error tag value |
| 2334 | 2366 | try self.emitWValue(operand); |
| 2335 | | try self.addMemArg( |
| 2336 | | .i32_load16_u, |
| 2337 | | .{ .offset = 0, .alignment = err_ty.abiAlignment(self.target) }, |
| 2338 | | ); |
| 2367 | if (pl_ty.hasCodeGenBits()) { |
| 2368 | try self.addMemArg(.i32_load16_u, .{ |
| 2369 | .offset = 0, |
| 2370 | .alignment = err_ty.errorUnionSet().abiAlignment(self.target), |
| 2371 | }); |
| 2372 | } |
| 2339 | 2373 | |
| 2340 | 2374 | // Compare the error value with '0' |
| 2341 | 2375 | try self.addImm32(0); |
| 2342 | 2376 | try self.addTag(Mir.Inst.Tag.fromOpcode(opcode)); |
| 2343 | 2377 | |
| 2344 | | const is_err_tmp = try self.allocLocal(err_ty); |
| 2378 | const is_err_tmp = try self.allocLocal(Type.initTag(.i32)); // result is always an i32 |
| 2345 | 2379 | try self.addLabel(.local_set, is_err_tmp.local); |
| 2346 | 2380 | return is_err_tmp; |
| 2347 | 2381 | } |
| ... | ... | @@ -2363,6 +2397,11 @@ fn airUnwrapErrUnionError(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 2363 | 2397 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; |
| 2364 | 2398 | const operand = self.resolveInst(ty_op.operand); |
| 2365 | 2399 | const err_ty = self.air.typeOf(ty_op.operand); |
| 2400 | const payload_ty = err_ty.errorUnionPayload(); |
| 2401 | if (!payload_ty.hasCodeGenBits()) { |
| 2402 | return operand; |
| 2403 | } |
| 2404 | |
| 2366 | 2405 | return try self.load(operand, err_ty.errorUnionSet(), 0); |
| 2367 | 2406 | } |
| 2368 | 2407 | |
| ... | ... | @@ -2507,9 +2546,8 @@ fn airSliceLen(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 2507 | 2546 | |
| 2508 | 2547 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; |
| 2509 | 2548 | const operand = self.resolveInst(ty_op.operand); |
| 2510 | | const pointer_width = self.target.cpu.arch.ptrBitWidth() / 8; |
| 2511 | 2549 | |
| 2512 | | return try self.load(operand, Type.usize, pointer_width); |
| 2550 | return try self.load(operand, Type.usize, self.ptrSize()); |
| 2513 | 2551 | } |
| 2514 | 2552 | |
| 2515 | 2553 | fn airSliceElemVal(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| ... | ... | @@ -2534,12 +2572,11 @@ fn airSliceElemVal(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 2534 | 2572 | |
| 2535 | 2573 | const result = try self.allocLocal(elem_ty); |
| 2536 | 2574 | try self.addLabel(.local_set, result.local); |
| 2537 | | return switch (elem_ty.zigTypeTag()) { |
| 2538 | | // pass as pointer |
| 2539 | | .Pointer, .Struct, .Optional => result, |
| 2540 | | // pass by value |
| 2541 | | else => try self.load(result, elem_ty, 0), |
| 2542 | | }; |
| 2575 | |
| 2576 | if (isByRef(elem_ty)) { |
| 2577 | return result; |
| 2578 | } |
| 2579 | return try self.load(result, elem_ty, 0); |
| 2543 | 2580 | } |
| 2544 | 2581 | |
| 2545 | 2582 | fn airSliceElemPtr(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| ... | ... | @@ -2670,3 +2707,87 @@ fn airPtrToInt(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 2670 | 2707 | const un_op = self.air.instructions.items(.data)[inst].un_op; |
| 2671 | 2708 | return self.resolveInst(un_op); |
| 2672 | 2709 | } |
| 2710 | |
| 2711 | fn airPtrElemVal(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 2712 | if (self.liveness.isUnused(inst)) return WValue{ .none = {} }; |
| 2713 | |
| 2714 | const bin_op = self.air.instructions.items(.data)[inst].bin_op; |
| 2715 | const ptr_ty = self.air.typeOf(bin_op.lhs); |
| 2716 | const pointer = self.resolveInst(bin_op.lhs); |
| 2717 | const index = self.resolveInst(bin_op.rhs); |
| 2718 | const elem_ty = ptr_ty.childType(); |
| 2719 | const elem_size = elem_ty.abiSize(self.target); |
| 2720 | |
| 2721 | // load pointer onto the stack |
| 2722 | if (ptr_ty.isSlice()) { |
| 2723 | const ptr_local = try self.load(pointer, ptr_ty, 0); |
| 2724 | try self.addLabel(.local_get, ptr_local.local); |
| 2725 | } else { |
| 2726 | try self.emitWValue(pointer); |
| 2727 | } |
| 2728 | |
| 2729 | // calculate index into slice |
| 2730 | try self.emitWValue(index); |
| 2731 | try self.addImm32(@bitCast(i32, @intCast(u32, elem_size))); |
| 2732 | try self.addTag(.i32_mul); |
| 2733 | try self.addTag(.i32_add); |
| 2734 | |
| 2735 | const result = try self.allocLocal(elem_ty); |
| 2736 | try self.addLabel(.local_set, result.local); |
| 2737 | if (isByRef(elem_ty)) { |
| 2738 | return result; |
| 2739 | } |
| 2740 | return try self.load(result, elem_ty, 0); |
| 2741 | } |
| 2742 | |
| 2743 | fn airPtrElemPtr(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 2744 | if (self.liveness.isUnused(inst)) return WValue{ .none = {} }; |
| 2745 | const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; |
| 2746 | const bin_op = self.air.extraData(Air.Bin, ty_pl.payload).data; |
| 2747 | const ptr_ty = self.air.typeOf(bin_op.lhs); |
| 2748 | const elem_ty = self.air.getRefType(ty_pl.ty).childType(); |
| 2749 | const elem_size = elem_ty.abiSize(self.target); |
| 2750 | |
| 2751 | const ptr = self.resolveInst(bin_op.lhs); |
| 2752 | const index = self.resolveInst(bin_op.rhs); |
| 2753 | |
| 2754 | // load pointer onto the stack |
| 2755 | if (ptr_ty.isSlice()) { |
| 2756 | const ptr_local = try self.load(ptr, ptr_ty, 0); |
| 2757 | try self.addLabel(.local_get, ptr_local.local); |
| 2758 | } else { |
| 2759 | try self.emitWValue(ptr); |
| 2760 | } |
| 2761 | |
| 2762 | // calculate index into ptr |
| 2763 | try self.emitWValue(index); |
| 2764 | try self.addImm32(@bitCast(i32, @intCast(u32, elem_size))); |
| 2765 | try self.addTag(.i32_mul); |
| 2766 | try self.addTag(.i32_add); |
| 2767 | |
| 2768 | const result = try self.allocLocal(Type.initTag(.i32)); |
| 2769 | try self.addLabel(.local_set, result.local); |
| 2770 | return result; |
| 2771 | } |
| 2772 | |
| 2773 | fn airPtrBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue { |
| 2774 | if (self.liveness.isUnused(inst)) return WValue{ .none = {} }; |
| 2775 | const bin_op = self.air.instructions.items(.data)[inst].bin_op; |
| 2776 | const ptr = self.resolveInst(bin_op.lhs); |
| 2777 | const offset = self.resolveInst(bin_op.rhs); |
| 2778 | const pointee_ty = self.air.typeOf(bin_op.lhs).childType(); |
| 2779 | |
| 2780 | const valtype = try self.typeToValtype(Type.usize); |
| 2781 | const mul_opcode = buildOpcode(.{ .valtype1 = valtype, .op = .mul }); |
| 2782 | const bin_opcode = buildOpcode(.{ .valtype1 = valtype, .op = op }); |
| 2783 | |
| 2784 | try self.emitWValue(ptr); |
| 2785 | try self.emitWValue(offset); |
| 2786 | try self.addImm32(@bitCast(i32, @intCast(u32, pointee_ty.abiSize(self.target)))); |
| 2787 | try self.addTag(Mir.Inst.Tag.fromOpcode(mul_opcode)); |
| 2788 | try self.addTag(Mir.Inst.Tag.fromOpcode(bin_opcode)); |
| 2789 | |
| 2790 | const result = try self.allocLocal(Type.usize); |
| 2791 | try self.addLabel(.local_set, result.local); |
| 2792 | return result; |
| 2793 | } |