| author | |
| committer | |
| log | 331bd83f1115bf5e33b6b2d7651d67dcfe16ac2a |
| tree | cfb5b8ae47de4f0276c5c5f7ec4bf13f487da078 |
| parent | 0209c68fcca22ffe1ab359096f6f3aeb477043e2 |
| signature |
I changed to `wasm/abi.zig`, this design is certainly better than the previous one. Still there is some conflict of interest between llvm and self-hosted backend, better design will appear when abi tests will be tested with self-hosted.
Resolves: #23304
Resolves: #233056 files changed, 329 insertions(+), 230 deletions(-)
src/arch/wasm/CodeGen.zig+69-72| ... | ... | @@ -1398,11 +1398,22 @@ fn resolveCallingConventionValues( |
| 1398 | 1398 | }, |
| 1399 | 1399 | .wasm_mvp => { |
| 1400 | 1400 | for (fn_info.param_types.get(ip)) |ty| { |
| 1401 | const ty_classes = abi.classifyType(Type.fromInterned(ty), zcu); | |
| 1402 | for (ty_classes) |class| { | |
| 1403 | if (class == .none) continue; | |
| 1404 | try args.append(.{ .local = .{ .value = result.local_index, .references = 1 } }); | |
| 1405 | result.local_index += 1; | |
| 1401 | if (!Type.fromInterned(ty).hasRuntimeBitsIgnoreComptime(zcu)) { | |
| 1402 | continue; | |
| 1403 | } | |
| 1404 | switch (abi.classifyType(.fromInterned(ty), zcu)) { | |
| 1405 | .direct => |scalar_ty| if (!abi.lowerAsDoubleI64(scalar_ty, zcu)) { | |
| 1406 | try args.append(.{ .local = .{ .value = result.local_index, .references = 1 } }); | |
| 1407 | result.local_index += 1; | |
| 1408 | } else { | |
| 1409 | try args.append(.{ .local = .{ .value = result.local_index, .references = 1 } }); | |
| 1410 | try args.append(.{ .local = .{ .value = result.local_index + 1, .references = 1 } }); | |
| 1411 | result.local_index += 2; | |
| 1412 | }, | |
| 1413 | .indirect => { | |
| 1414 | try args.append(.{ .local = .{ .value = result.local_index, .references = 1 } }); | |
| 1415 | result.local_index += 1; | |
| 1416 | }, | |
| 1406 | 1417 | } |
| 1407 | 1418 | } |
| 1408 | 1419 | }, |
| ... | ... | @@ -1418,14 +1429,13 @@ pub fn firstParamSRet( |
| 1418 | 1429 | zcu: *const Zcu, |
| 1419 | 1430 | target: *const std.Target, |
| 1420 | 1431 | ) bool { |
| 1432 | if (!return_type.hasRuntimeBitsIgnoreComptime(zcu)) return false; | |
| 1421 | 1433 | switch (cc) { |
| 1422 | 1434 | .@"inline" => unreachable, |
| 1423 | 1435 | .auto => return isByRef(return_type, zcu, target), |
| 1424 | .wasm_mvp => { | |
| 1425 | const ty_classes = abi.classifyType(return_type, zcu); | |
| 1426 | if (ty_classes[0] == .indirect) return true; | |
| 1427 | if (ty_classes[0] == .direct and ty_classes[1] == .direct) return true; | |
| 1428 | return false; | |
| 1436 | .wasm_mvp => switch (abi.classifyType(return_type, zcu)) { | |
| 1437 | .direct => |scalar_ty| return abi.lowerAsDoubleI64(scalar_ty, zcu), | |
| 1438 | .indirect => return true, | |
| 1429 | 1439 | }, |
| 1430 | 1440 | else => return false, |
| 1431 | 1441 | } |
| ... | ... | @@ -1439,26 +1449,19 @@ fn lowerArg(cg: *CodeGen, cc: std.builtin.CallingConvention, ty: Type, value: WV |
| 1439 | 1449 | } |
| 1440 | 1450 | |
| 1441 | 1451 | const zcu = cg.pt.zcu; |
| 1442 | const ty_classes = abi.classifyType(ty, zcu); | |
| 1443 | assert(ty_classes[0] != .none); | |
| 1444 | switch (ty.zigTypeTag(zcu)) { | |
| 1445 | .@"struct", .@"union" => { | |
| 1446 | if (ty_classes[0] == .indirect) { | |
| 1447 | return cg.lowerToStack(value); | |
| 1448 | } | |
| 1449 | assert(ty_classes[0] == .direct); | |
| 1450 | const scalar_type = abi.scalarType(ty, zcu); | |
| 1451 | switch (value) { | |
| 1452 | .nav_ref, .stack_offset => _ = try cg.load(value, scalar_type, 0), | |
| 1453 | .dead => unreachable, | |
| 1454 | else => try cg.emitWValue(value), | |
| 1455 | } | |
| 1456 | }, | |
| 1457 | .int, .float => { | |
| 1458 | if (ty_classes[1] == .none) { | |
| 1452 | ||
| 1453 | switch (abi.classifyType(ty, zcu)) { | |
| 1454 | .direct => |scalar_type| if (!abi.lowerAsDoubleI64(scalar_type, zcu)) { | |
| 1455 | if (!isByRef(ty, zcu, cg.target)) { | |
| 1459 | 1456 | return cg.lowerToStack(value); |
| 1457 | } else { | |
| 1458 | switch (value) { | |
| 1459 | .nav_ref, .stack_offset => _ = try cg.load(value, scalar_type, 0), | |
| 1460 | .dead => unreachable, | |
| 1461 | else => try cg.emitWValue(value), | |
| 1462 | } | |
| 1460 | 1463 | } |
| 1461 | assert(ty_classes[0] == .direct and ty_classes[1] == .direct); | |
| 1464 | } else { | |
| 1462 | 1465 | assert(ty.abiSize(zcu) == 16); |
| 1463 | 1466 | // in this case we have an integer or float that must be lowered as 2 i64's. |
| 1464 | 1467 | try cg.emitWValue(value); |
| ... | ... | @@ -1466,7 +1469,7 @@ fn lowerArg(cg: *CodeGen, cc: std.builtin.CallingConvention, ty: Type, value: WV |
| 1466 | 1469 | try cg.emitWValue(value); |
| 1467 | 1470 | try cg.addMemArg(.i64_load, .{ .offset = value.offset() + 8, .alignment = 8 }); |
| 1468 | 1471 | }, |
| 1469 | else => return cg.lowerToStack(value), | |
| 1472 | .indirect => return cg.lowerToStack(value), | |
| 1470 | 1473 | } |
| 1471 | 1474 | } |
| 1472 | 1475 | |
| ... | ... | @@ -2125,23 +2128,16 @@ fn airRet(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 2125 | 2128 | if (cg.return_value != .none) { |
| 2126 | 2129 | try cg.store(cg.return_value, operand, ret_ty, 0); |
| 2127 | 2130 | } else if (fn_info.cc == .wasm_mvp and ret_ty.hasRuntimeBitsIgnoreComptime(zcu)) { |
| 2128 | switch (ret_ty.zigTypeTag(zcu)) { | |
| 2129 | // Aggregate types can be lowered as a singular value | |
| 2130 | .@"struct", .@"union" => { | |
| 2131 | const scalar_type = abi.scalarType(ret_ty, zcu); | |
| 2132 | try cg.emitWValue(operand); | |
| 2133 | const opcode = buildOpcode(.{ | |
| 2134 | .op = .load, | |
| 2135 | .width = @as(u8, @intCast(scalar_type.abiSize(zcu) * 8)), | |
| 2136 | .signedness = if (scalar_type.isSignedInt(zcu)) .signed else .unsigned, | |
| 2137 | .valtype1 = typeToValtype(scalar_type, zcu, cg.target), | |
| 2138 | }); | |
| 2139 | try cg.addMemArg(Mir.Inst.Tag.fromOpcode(opcode), .{ | |
| 2140 | .offset = operand.offset(), | |
| 2141 | .alignment = @intCast(scalar_type.abiAlignment(zcu).toByteUnits().?), | |
| 2142 | }); | |
| 2131 | switch (abi.classifyType(ret_ty, zcu)) { | |
| 2132 | .direct => |scalar_type| { | |
| 2133 | assert(!abi.lowerAsDoubleI64(scalar_type, zcu)); | |
| 2134 | if (!isByRef(ret_ty, zcu, cg.target)) { | |
| 2135 | try cg.emitWValue(operand); | |
| 2136 | } else { | |
| 2137 | _ = try cg.load(operand, scalar_type, 0); | |
| 2138 | } | |
| 2143 | 2139 | }, |
| 2144 | else => try cg.emitWValue(operand), | |
| 2140 | .indirect => unreachable, | |
| 2145 | 2141 | } |
| 2146 | 2142 | } else { |
| 2147 | 2143 | if (!ret_ty.hasRuntimeBitsIgnoreComptime(zcu) and ret_ty.isError(zcu)) { |
| ... | ... | @@ -2267,14 +2263,24 @@ fn airCall(cg: *CodeGen, inst: Air.Inst.Index, modifier: std.builtin.CallModifie |
| 2267 | 2263 | break :result_value .none; |
| 2268 | 2264 | } else if (first_param_sret) { |
| 2269 | 2265 | break :result_value sret; |
| 2270 | // TODO: Make this less fragile and optimize | |
| 2271 | } else if (zcu.typeToFunc(fn_ty).?.cc == .wasm_mvp and ret_ty.zigTypeTag(zcu) == .@"struct" or ret_ty.zigTypeTag(zcu) == .@"union") { | |
| 2272 | const result_local = try cg.allocLocal(ret_ty); | |
| 2273 | try cg.addLocal(.local_set, result_local.local.value); | |
| 2274 | const scalar_type = abi.scalarType(ret_ty, zcu); | |
| 2275 | const result = try cg.allocStack(scalar_type); | |
| 2276 | try cg.store(result, result_local, scalar_type, 0); | |
| 2277 | break :result_value result; | |
| 2266 | } else if (zcu.typeToFunc(fn_ty).?.cc == .wasm_mvp) { | |
| 2267 | switch (abi.classifyType(ret_ty, zcu)) { | |
| 2268 | .direct => |scalar_type| { | |
| 2269 | assert(!abi.lowerAsDoubleI64(scalar_type, zcu)); | |
| 2270 | if (!isByRef(ret_ty, zcu, cg.target)) { | |
| 2271 | const result_local = try cg.allocLocal(ret_ty); | |
| 2272 | try cg.addLocal(.local_set, result_local.local.value); | |
| 2273 | break :result_value result_local; | |
| 2274 | } else { | |
| 2275 | const result_local = try cg.allocLocal(ret_ty); | |
| 2276 | try cg.addLocal(.local_set, result_local.local.value); | |
| 2277 | const result = try cg.allocStack(ret_ty); | |
| 2278 | try cg.store(result, result_local, scalar_type, 0); | |
| 2279 | break :result_value result; | |
| 2280 | } | |
| 2281 | }, | |
| 2282 | .indirect => unreachable, | |
| 2283 | } | |
| 2278 | 2284 | } else { |
| 2279 | 2285 | const result_local = try cg.allocLocal(ret_ty); |
| 2280 | 2286 | try cg.addLocal(.local_set, result_local.local.value); |
| ... | ... | @@ -2547,26 +2553,17 @@ fn airArg(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 2547 | 2553 | const cc = zcu.typeToFunc(zcu.navValue(cg.owner_nav).typeOf(zcu)).?.cc; |
| 2548 | 2554 | const arg_ty = cg.typeOfIndex(inst); |
| 2549 | 2555 | if (cc == .wasm_mvp) { |
| 2550 | const arg_classes = abi.classifyType(arg_ty, zcu); | |
| 2551 | for (arg_classes) |class| { | |
| 2552 | if (class != .none) { | |
| 2556 | switch (abi.classifyType(arg_ty, zcu)) { | |
| 2557 | .direct => |scalar_ty| if (!abi.lowerAsDoubleI64(scalar_ty, zcu)) { | |
| 2553 | 2558 | cg.arg_index += 1; |
| 2554 | } | |
| 2555 | } | |
| 2556 | ||
| 2557 | // When we have an argument that's passed using more than a single parameter, | |
| 2558 | // we combine them into a single stack value | |
| 2559 | if (arg_classes[0] == .direct and arg_classes[1] == .direct) { | |
| 2560 | if (arg_ty.zigTypeTag(zcu) != .int and arg_ty.zigTypeTag(zcu) != .float) { | |
| 2561 | return cg.fail( | |
| 2562 | "TODO: Implement C-ABI argument for type '{}'", | |
| 2563 | .{arg_ty.fmt(pt)}, | |
| 2564 | ); | |
| 2565 | } | |
| 2566 | const result = try cg.allocStack(arg_ty); | |
| 2567 | try cg.store(result, arg, Type.u64, 0); | |
| 2568 | try cg.store(result, cg.args[arg_index + 1], Type.u64, 8); | |
| 2569 | return cg.finishAir(inst, result, &.{}); | |
| 2559 | } else { | |
| 2560 | cg.arg_index += 2; | |
| 2561 | const result = try cg.allocStack(arg_ty); | |
| 2562 | try cg.store(result, arg, Type.u64, 0); | |
| 2563 | try cg.store(result, cg.args[arg_index + 1], Type.u64, 8); | |
| 2564 | return cg.finishAir(inst, result, &.{}); | |
| 2565 | }, | |
| 2566 | .indirect => cg.arg_index += 1, | |
| 2570 | 2567 | } |
| 2571 | 2568 | } else { |
| 2572 | 2569 | cg.arg_index += 1; |
src/arch/wasm/abi.zig+26-67| ... | ... | @@ -13,70 +13,55 @@ const Zcu = @import("../../Zcu.zig"); |
| 13 | 13 | |
| 14 | 14 | /// Defines how to pass a type as part of a function signature, |
| 15 | 15 | /// both for parameters as well as return values. |
| 16 | pub const Class = enum { direct, indirect, none }; | |
| 17 | ||
| 18 | const none: [2]Class = .{ .none, .none }; | |
| 19 | const memory: [2]Class = .{ .indirect, .none }; | |
| 20 | const direct: [2]Class = .{ .direct, .none }; | |
| 16 | pub const Class = union(enum) { | |
| 17 | direct: Type, | |
| 18 | indirect, | |
| 19 | }; | |
| 21 | 20 | |
| 22 | 21 | /// Classifies a given Zig type to determine how they must be passed |
| 23 | 22 | /// or returned as value within a wasm function. |
| 24 | /// When all elements result in `.none`, no value must be passed in or returned. | |
| 25 | pub fn classifyType(ty: Type, zcu: *const Zcu) [2]Class { | |
| 23 | pub fn classifyType(ty: Type, zcu: *const Zcu) Class { | |
| 26 | 24 | const ip = &zcu.intern_pool; |
| 27 | const target = zcu.getTarget(); | |
| 28 | if (!ty.hasRuntimeBitsIgnoreComptime(zcu)) return none; | |
| 25 | assert(ty.hasRuntimeBitsIgnoreComptime(zcu)); | |
| 29 | 26 | switch (ty.zigTypeTag(zcu)) { |
| 27 | .int, .@"enum", .error_set => return .{ .direct = ty }, | |
| 28 | .float => return .{ .direct = ty }, | |
| 29 | .bool => return .{ .direct = ty }, | |
| 30 | .vector => return .{ .direct = ty }, | |
| 31 | .array => return .indirect, | |
| 32 | .optional => { | |
| 33 | assert(ty.isPtrLikeOptional(zcu)); | |
| 34 | return .{ .direct = ty }; | |
| 35 | }, | |
| 36 | .pointer => { | |
| 37 | assert(!ty.isSlice(zcu)); | |
| 38 | return .{ .direct = ty }; | |
| 39 | }, | |
| 30 | 40 | .@"struct" => { |
| 31 | 41 | const struct_type = zcu.typeToStruct(ty).?; |
| 32 | 42 | if (struct_type.layout == .@"packed") { |
| 33 | if (ty.bitSize(zcu) <= 64) return direct; | |
| 34 | return .{ .direct, .direct }; | |
| 43 | return .{ .direct = ty }; | |
| 35 | 44 | } |
| 36 | 45 | if (struct_type.field_types.len > 1) { |
| 37 | 46 | // The struct type is non-scalar. |
| 38 | return memory; | |
| 47 | return .indirect; | |
| 39 | 48 | } |
| 40 | 49 | const field_ty = Type.fromInterned(struct_type.field_types.get(ip)[0]); |
| 41 | 50 | const explicit_align = struct_type.fieldAlign(ip, 0); |
| 42 | 51 | if (explicit_align != .none) { |
| 43 | 52 | if (explicit_align.compareStrict(.gt, field_ty.abiAlignment(zcu))) |
| 44 | return memory; | |
| 53 | return .indirect; | |
| 45 | 54 | } |
| 46 | 55 | return classifyType(field_ty, zcu); |
| 47 | 56 | }, |
| 48 | .int, .@"enum", .error_set => { | |
| 49 | const int_bits = ty.intInfo(zcu).bits; | |
| 50 | if (int_bits <= 64) return direct; | |
| 51 | if (int_bits <= 128) return .{ .direct, .direct }; | |
| 52 | return memory; | |
| 53 | }, | |
| 54 | .float => { | |
| 55 | const float_bits = ty.floatBits(target); | |
| 56 | if (float_bits <= 64) return direct; | |
| 57 | if (float_bits <= 128) return .{ .direct, .direct }; | |
| 58 | return memory; | |
| 59 | }, | |
| 60 | .bool => return direct, | |
| 61 | .vector => return direct, | |
| 62 | .array => return memory, | |
| 63 | .optional => { | |
| 64 | assert(ty.isPtrLikeOptional(zcu)); | |
| 65 | return direct; | |
| 66 | }, | |
| 67 | .pointer => { | |
| 68 | assert(!ty.isSlice(zcu)); | |
| 69 | return direct; | |
| 70 | }, | |
| 71 | 57 | .@"union" => { |
| 72 | 58 | const union_obj = zcu.typeToUnion(ty).?; |
| 73 | 59 | if (union_obj.flagsUnordered(ip).layout == .@"packed") { |
| 74 | if (ty.bitSize(zcu) <= 64) return direct; | |
| 75 | return .{ .direct, .direct }; | |
| 60 | return .{ .direct = ty }; | |
| 76 | 61 | } |
| 77 | 62 | const layout = ty.unionGetLayout(zcu); |
| 78 | 63 | assert(layout.tag_size == 0); |
| 79 | if (union_obj.field_types.len > 1) return memory; | |
| 64 | if (union_obj.field_types.len > 1) return .indirect; | |
| 80 | 65 | const first_field_ty = Type.fromInterned(union_obj.field_types.get(ip)[0]); |
| 81 | 66 | return classifyType(first_field_ty, zcu); |
| 82 | 67 | }, |
| ... | ... | @@ -97,32 +82,6 @@ pub fn classifyType(ty: Type, zcu: *const Zcu) [2]Class { |
| 97 | 82 | } |
| 98 | 83 | } |
| 99 | 84 | |
| 100 | /// Returns the scalar type a given type can represent. | |
| 101 | /// Asserts given type can be represented as scalar, such as | |
| 102 | /// a struct with a single scalar field. | |
| 103 | pub fn scalarType(ty: Type, zcu: *Zcu) Type { | |
| 104 | const ip = &zcu.intern_pool; | |
| 105 | switch (ty.zigTypeTag(zcu)) { | |
| 106 | .@"struct" => { | |
| 107 | if (zcu.typeToPackedStruct(ty)) |packed_struct| { | |
| 108 | return scalarType(Type.fromInterned(packed_struct.backingIntTypeUnordered(ip)), zcu); | |
| 109 | } else { | |
| 110 | assert(ty.structFieldCount(zcu) == 1); | |
| 111 | return scalarType(ty.fieldType(0, zcu), zcu); | |
| 112 | } | |
| 113 | }, | |
| 114 | .@"union" => { | |
| 115 | const union_obj = zcu.typeToUnion(ty).?; | |
| 116 | if (union_obj.flagsUnordered(ip).layout != .@"packed") { | |
| 117 | const layout = Type.getUnionLayout(union_obj, zcu); | |
| 118 | if (layout.payload_size == 0 and layout.tag_size != 0) { | |
| 119 | return scalarType(ty.unionTagTypeSafety(zcu).?, zcu); | |
| 120 | } | |
| 121 | assert(union_obj.field_types.len == 1); | |
| 122 | } | |
| 123 | const first_field_ty = Type.fromInterned(union_obj.field_types.get(ip)[0]); | |
| 124 | return scalarType(first_field_ty, zcu); | |
| 125 | }, | |
| 126 | else => return ty, | |
| 127 | } | |
| 85 | pub fn lowerAsDoubleI64(scalar_ty: Type, zcu: *const Zcu) bool { | |
| 86 | return scalar_ty.bitSize(zcu) > 64; | |
| 128 | 87 | } |
src/codegen/llvm.zig+25-23| ... | ... | @@ -12094,7 +12094,7 @@ fn firstParamSRet(fn_info: InternPool.Key.FuncType, zcu: *Zcu, target: std.Targe |
| 12094 | 12094 | .x86_64_win => x86_64_abi.classifyWindows(return_type, zcu) == .memory, |
| 12095 | 12095 | .x86_sysv, .x86_win => isByRef(return_type, zcu), |
| 12096 | 12096 | .x86_stdcall => !isScalar(zcu, return_type), |
| 12097 | .wasm_mvp => wasm_c_abi.classifyType(return_type, zcu)[0] == .indirect, | |
| 12097 | .wasm_mvp => wasm_c_abi.classifyType(return_type, zcu) == .indirect, | |
| 12098 | 12098 | .aarch64_aapcs, |
| 12099 | 12099 | .aarch64_aapcs_darwin, |
| 12100 | 12100 | .aarch64_aapcs_win, |
| ... | ... | @@ -12179,18 +12179,9 @@ fn lowerFnRetTy(o: *Object, fn_info: InternPool.Key.FuncType) Allocator.Error!Bu |
| 12179 | 12179 | return o.builder.structType(.normal, types[0..types_len]); |
| 12180 | 12180 | }, |
| 12181 | 12181 | }, |
| 12182 | .wasm_mvp => { | |
| 12183 | if (isScalar(zcu, return_type)) { | |
| 12184 | return o.lowerType(return_type); | |
| 12185 | } | |
| 12186 | const classes = wasm_c_abi.classifyType(return_type, zcu); | |
| 12187 | if (classes[0] == .indirect or classes[0] == .none) { | |
| 12188 | return .void; | |
| 12189 | } | |
| 12190 | ||
| 12191 | assert(classes[0] == .direct and classes[1] == .none); | |
| 12192 | const scalar_type = wasm_c_abi.scalarType(return_type, zcu); | |
| 12193 | return o.builder.intType(@intCast(scalar_type.abiSize(zcu) * 8)); | |
| 12182 | .wasm_mvp => switch (wasm_c_abi.classifyType(return_type, zcu)) { | |
| 12183 | .direct => |scalar_ty| return o.lowerType(scalar_ty), | |
| 12184 | .indirect => return .void, | |
| 12194 | 12185 | }, |
| 12195 | 12186 | // TODO investigate other callconvs |
| 12196 | 12187 | else => return o.lowerType(return_type), |
| ... | ... | @@ -12444,17 +12435,28 @@ const ParamTypeIterator = struct { |
| 12444 | 12435 | }, |
| 12445 | 12436 | } |
| 12446 | 12437 | }, |
| 12447 | .wasm_mvp => { | |
| 12448 | it.zig_index += 1; | |
| 12449 | it.llvm_index += 1; | |
| 12450 | if (isScalar(zcu, ty)) { | |
| 12451 | return .byval; | |
| 12452 | } | |
| 12453 | const classes = wasm_c_abi.classifyType(ty, zcu); | |
| 12454 | if (classes[0] == .indirect) { | |
| 12438 | .wasm_mvp => switch (wasm_c_abi.classifyType(ty, zcu)) { | |
| 12439 | .direct => |scalar_ty| { | |
| 12440 | if (isScalar(zcu, ty)) { | |
| 12441 | it.zig_index += 1; | |
| 12442 | it.llvm_index += 1; | |
| 12443 | return .byval; | |
| 12444 | } else { | |
| 12445 | var types_buffer: [8]Builder.Type = undefined; | |
| 12446 | types_buffer[0] = try it.object.lowerType(scalar_ty); | |
| 12447 | it.types_buffer = types_buffer; | |
| 12448 | it.types_len = 1; | |
| 12449 | it.llvm_index += 1; | |
| 12450 | it.zig_index += 1; | |
| 12451 | return .multiple_llvm_types; | |
| 12452 | } | |
| 12453 | }, | |
| 12454 | .indirect => { | |
| 12455 | it.zig_index += 1; | |
| 12456 | it.llvm_index += 1; | |
| 12457 | it.byval_attr = true; | |
| 12455 | 12458 | return .byref; |
| 12456 | } | |
| 12457 | return .abi_sized_int; | |
| 12459 | }, | |
| 12458 | 12460 | }, |
| 12459 | 12461 | // TODO investigate other callconvs |
| 12460 | 12462 | else => { |
src/link/Wasm.zig+17-16| ... | ... | @@ -4617,10 +4617,13 @@ fn convertZcuFnType( |
| 4617 | 4617 | try params_buffer.append(gpa, .i32); // memory address is always a 32-bit handle |
| 4618 | 4618 | } else if (return_type.hasRuntimeBitsIgnoreComptime(zcu)) { |
| 4619 | 4619 | if (cc == .wasm_mvp) { |
| 4620 | const res_classes = abi.classifyType(return_type, zcu); | |
| 4621 | assert(res_classes[0] == .direct and res_classes[1] == .none); | |
| 4622 | const scalar_type = abi.scalarType(return_type, zcu); | |
| 4623 | try returns_buffer.append(gpa, CodeGen.typeToValtype(scalar_type, zcu, target)); | |
| 4620 | switch (abi.classifyType(return_type, zcu)) { | |
| 4621 | .direct => |scalar_ty| { | |
| 4622 | assert(!abi.lowerAsDoubleI64(scalar_ty, zcu)); | |
| 4623 | try returns_buffer.append(gpa, CodeGen.typeToValtype(scalar_ty, zcu, target)); | |
| 4624 | }, | |
| 4625 | .indirect => unreachable, | |
| 4626 | } | |
| 4624 | 4627 | } else { |
| 4625 | 4628 | try returns_buffer.append(gpa, CodeGen.typeToValtype(return_type, zcu, target)); |
| 4626 | 4629 | } |
| ... | ... | @@ -4635,18 +4638,16 @@ fn convertZcuFnType( |
| 4635 | 4638 | |
| 4636 | 4639 | switch (cc) { |
| 4637 | 4640 | .wasm_mvp => { |
| 4638 | const param_classes = abi.classifyType(param_type, zcu); | |
| 4639 | if (param_classes[1] == .none) { | |
| 4640 | if (param_classes[0] == .direct) { | |
| 4641 | const scalar_type = abi.scalarType(param_type, zcu); | |
| 4642 | try params_buffer.append(gpa, CodeGen.typeToValtype(scalar_type, zcu, target)); | |
| 4643 | } else { | |
| 4644 | try params_buffer.append(gpa, CodeGen.typeToValtype(param_type, zcu, target)); | |
| 4645 | } | |
| 4646 | } else { | |
| 4647 | // i128/f128 | |
| 4648 | try params_buffer.append(gpa, .i64); | |
| 4649 | try params_buffer.append(gpa, .i64); | |
| 4641 | switch (abi.classifyType(param_type, zcu)) { | |
| 4642 | .direct => |scalar_ty| { | |
| 4643 | if (!abi.lowerAsDoubleI64(scalar_ty, zcu)) { | |
| 4644 | try params_buffer.append(gpa, CodeGen.typeToValtype(scalar_ty, zcu, target)); | |
| 4645 | } else { | |
| 4646 | try params_buffer.append(gpa, .i64); | |
| 4647 | try params_buffer.append(gpa, .i64); | |
| 4648 | } | |
| 4649 | }, | |
| 4650 | .indirect => try params_buffer.append(gpa, CodeGen.typeToValtype(param_type, zcu, target)), | |
| 4650 | 4651 | } |
| 4651 | 4652 | }, |
| 4652 | 4653 | else => try params_buffer.append(gpa, CodeGen.typeToValtype(param_type, zcu, target)), |
test/c_abi/cfuncs.c+70| ... | ... | @@ -227,6 +227,38 @@ void c_struct_u64_u64_8(size_t, size_t, size_t, size_t, size_t, size_t, size_t, |
| 227 | 227 | assert_or_panic(s.b == 40); |
| 228 | 228 | } |
| 229 | 229 | |
| 230 | struct Struct_f32 { | |
| 231 | float a; | |
| 232 | }; | |
| 233 | ||
| 234 | struct Struct_f32 zig_ret_struct_f32(void); | |
| 235 | ||
| 236 | void zig_struct_f32(struct Struct_f32); | |
| 237 | ||
| 238 | struct Struct_f32 c_ret_struct_f32(void) { | |
| 239 | return (struct Struct_f32){ 2.5f }; | |
| 240 | } | |
| 241 | ||
| 242 | void c_struct_f32(struct Struct_f32 s) { | |
| 243 | assert_or_panic(s.a == 2.5f); | |
| 244 | } | |
| 245 | ||
| 246 | struct Struct_f64 { | |
| 247 | double a; | |
| 248 | }; | |
| 249 | ||
| 250 | struct Struct_f64 zig_ret_struct_f64(void); | |
| 251 | ||
| 252 | void zig_struct_f64(struct Struct_f64); | |
| 253 | ||
| 254 | struct Struct_f64 c_ret_struct_f64(void) { | |
| 255 | return (struct Struct_f64){ 2.5 }; | |
| 256 | } | |
| 257 | ||
| 258 | void c_struct_f64(struct Struct_f64 s) { | |
| 259 | assert_or_panic(s.a == 2.5); | |
| 260 | } | |
| 261 | ||
| 230 | 262 | struct Struct_f32f32_f32 { |
| 231 | 263 | struct { |
| 232 | 264 | float b, c; |
| ... | ... | @@ -296,6 +328,13 @@ void c_struct_u32_union_u32_u32u32(struct Struct_u32_Union_u32_u32u32 s) { |
| 296 | 328 | assert_or_panic(s.b.c.e == 3); |
| 297 | 329 | } |
| 298 | 330 | |
| 331 | struct Struct_i32_i32 { | |
| 332 | int32_t a; | |
| 333 | int32_t b; | |
| 334 | }; | |
| 335 | ||
| 336 | void zig_struct_i32_i32(struct Struct_i32_i32); | |
| 337 | ||
| 299 | 338 | struct BigStruct { |
| 300 | 339 | uint64_t a; |
| 301 | 340 | uint64_t b; |
| ... | ... | @@ -2674,6 +2713,18 @@ void run_c_tests(void) { |
| 2674 | 2713 | } |
| 2675 | 2714 | |
| 2676 | 2715 | #if !defined(ZIG_RISCV64) |
| 2716 | { | |
| 2717 | struct Struct_f32 s = zig_ret_struct_f32(); | |
| 2718 | assert_or_panic(s.a == 2.5f); | |
| 2719 | zig_struct_f32((struct Struct_f32){ 2.5f }); | |
| 2720 | } | |
| 2721 | ||
| 2722 | { | |
| 2723 | struct Struct_f64 s = zig_ret_struct_f64(); | |
| 2724 | assert_or_panic(s.a == 2.5); | |
| 2725 | zig_struct_f64((struct Struct_f64){ 2.5 }); | |
| 2726 | } | |
| 2727 | ||
| 2677 | 2728 | { |
| 2678 | 2729 | struct Struct_f32f32_f32 s = zig_ret_struct_f32f32_f32(); |
| 2679 | 2730 | assert_or_panic(s.a.b == 1.0f); |
| ... | ... | @@ -2699,6 +2750,10 @@ void run_c_tests(void) { |
| 2699 | 2750 | assert_or_panic(s.b.c.e == 3); |
| 2700 | 2751 | zig_struct_u32_union_u32_u32u32(s); |
| 2701 | 2752 | } |
| 2753 | { | |
| 2754 | struct Struct_i32_i32 s = {1, 2}; | |
| 2755 | zig_struct_i32_i32(s); | |
| 2756 | } | |
| 2702 | 2757 | #endif |
| 2703 | 2758 | |
| 2704 | 2759 | { |
| ... | ... | @@ -5024,6 +5079,21 @@ double complex c_cmultd(double complex a, double complex b) { |
| 5024 | 5079 | return 1.5 + I * 13.5; |
| 5025 | 5080 | } |
| 5026 | 5081 | |
| 5082 | struct Struct_i32_i32 c_mut_struct_i32_i32(struct Struct_i32_i32 s) { | |
| 5083 | assert_or_panic(s.a == 1); | |
| 5084 | assert_or_panic(s.b == 2); | |
| 5085 | s.a += 100; | |
| 5086 | s.b += 250; | |
| 5087 | assert_or_panic(s.a == 101); | |
| 5088 | assert_or_panic(s.b == 252); | |
| 5089 | return s; | |
| 5090 | } | |
| 5091 | ||
| 5092 | void c_struct_i32_i32(struct Struct_i32_i32 s) { | |
| 5093 | assert_or_panic(s.a == 1); | |
| 5094 | assert_or_panic(s.b == 2); | |
| 5095 | } | |
| 5096 | ||
| 5027 | 5097 | void c_big_struct(struct BigStruct x) { |
| 5028 | 5098 | assert_or_panic(x.a == 1); |
| 5029 | 5099 | assert_or_panic(x.b == 2); |
test/c_abi/main.zig+122-52| ... | ... | @@ -13,7 +13,7 @@ const expectEqual = std.testing.expectEqual; |
| 13 | 13 | const have_i128 = builtin.cpu.arch != .x86 and !builtin.cpu.arch.isArm() and |
| 14 | 14 | !builtin.cpu.arch.isMIPS() and !builtin.cpu.arch.isPowerPC32(); |
| 15 | 15 | |
| 16 | const have_f128 = builtin.cpu.arch.isX86() and !builtin.os.tag.isDarwin(); | |
| 16 | const have_f128 = builtin.cpu.arch.isWasm() or (builtin.cpu.arch.isX86() and !builtin.os.tag.isDarwin()); | |
| 17 | 17 | const have_f80 = builtin.cpu.arch.isX86(); |
| 18 | 18 | |
| 19 | 19 | extern fn run_c_tests() void; |
| ... | ... | @@ -339,6 +339,56 @@ test "C ABI struct u64 u64" { |
| 339 | 339 | c_struct_u64_u64_8(0, 1, 2, 3, 4, 5, 6, 7, .{ .a = 39, .b = 40 }); |
| 340 | 340 | } |
| 341 | 341 | |
| 342 | const Struct_f32 = extern struct { | |
| 343 | a: f32, | |
| 344 | }; | |
| 345 | ||
| 346 | export fn zig_ret_struct_f32() Struct_f32 { | |
| 347 | return .{ .a = 2.5 }; | |
| 348 | } | |
| 349 | ||
| 350 | export fn zig_struct_f32(s: Struct_f32) void { | |
| 351 | expect(s.a == 2.5) catch @panic("test failure"); | |
| 352 | } | |
| 353 | ||
| 354 | extern fn c_ret_struct_f32() Struct_f32; | |
| 355 | ||
| 356 | extern fn c_struct_f32(Struct_f32) void; | |
| 357 | ||
| 358 | test "C ABI struct f32" { | |
| 359 | if (builtin.cpu.arch.isMIPS64()) return error.SkipZigTest; | |
| 360 | if (builtin.cpu.arch.isPowerPC32()) return error.SkipZigTest; | |
| 361 | ||
| 362 | const s = c_ret_struct_f32(); | |
| 363 | try expect(s.a == 2.5); | |
| 364 | c_struct_f32(.{ .a = 2.5 }); | |
| 365 | } | |
| 366 | ||
| 367 | const Struct_f64 = extern struct { | |
| 368 | a: f64, | |
| 369 | }; | |
| 370 | ||
| 371 | export fn zig_ret_struct_f64() Struct_f64 { | |
| 372 | return .{ .a = 2.5 }; | |
| 373 | } | |
| 374 | ||
| 375 | export fn zig_struct_f64(s: Struct_f64) void { | |
| 376 | expect(s.a == 2.5) catch @panic("test failure"); | |
| 377 | } | |
| 378 | ||
| 379 | extern fn c_ret_struct_f64() Struct_f64; | |
| 380 | ||
| 381 | extern fn c_struct_f64(Struct_f64) void; | |
| 382 | ||
| 383 | test "C ABI struct f64" { | |
| 384 | if (builtin.cpu.arch.isMIPS64()) return error.SkipZigTest; | |
| 385 | if (builtin.cpu.arch.isPowerPC32()) return error.SkipZigTest; | |
| 386 | ||
| 387 | const s = c_ret_struct_f64(); | |
| 388 | try expect(s.a == 2.5); | |
| 389 | c_struct_f64(.{ .a = 2.5 }); | |
| 390 | } | |
| 391 | ||
| 342 | 392 | const Struct_f32f32_f32 = extern struct { |
| 343 | 393 | a: extern struct { b: f32, c: f32 }, |
| 344 | 394 | d: f32, |
| ... | ... | @@ -434,6 +484,34 @@ test "C ABI struct{u32,union{u32,struct{u32,u32}}}" { |
| 434 | 484 | c_struct_u32_union_u32_u32u32(.{ .a = 1, .b = .{ .c = .{ .d = 2, .e = 3 } } }); |
| 435 | 485 | } |
| 436 | 486 | |
| 487 | const Struct_i32_i32 = extern struct { | |
| 488 | a: i32, | |
| 489 | b: i32, | |
| 490 | }; | |
| 491 | extern fn c_mut_struct_i32_i32(Struct_i32_i32) Struct_i32_i32; | |
| 492 | extern fn c_struct_i32_i32(Struct_i32_i32) void; | |
| 493 | ||
| 494 | test "C ABI struct i32 i32" { | |
| 495 | if (builtin.cpu.arch.isMIPS64()) return error.SkipZigTest; | |
| 496 | if (builtin.cpu.arch.isPowerPC()) return error.SkipZigTest; | |
| 497 | ||
| 498 | const s: Struct_i32_i32 = .{ | |
| 499 | .a = 1, | |
| 500 | .b = 2, | |
| 501 | }; | |
| 502 | const mut_res = c_mut_struct_i32_i32(s); | |
| 503 | try expect(s.a == 1); | |
| 504 | try expect(s.b == 2); | |
| 505 | try expect(mut_res.a == 101); | |
| 506 | try expect(mut_res.b == 252); | |
| 507 | c_struct_i32_i32(s); | |
| 508 | } | |
| 509 | ||
| 510 | export fn zig_struct_i32_i32(s: Struct_i32_i32) void { | |
| 511 | expect(s.a == 1) catch @panic("test failure: zig_struct_i32_i32 1"); | |
| 512 | expect(s.b == 2) catch @panic("test failure: zig_struct_i32_i32 2"); | |
| 513 | } | |
| 514 | ||
| 437 | 515 | const BigStruct = extern struct { |
| 438 | 516 | a: u64, |
| 439 | 517 | b: u64, |
| ... | ... | @@ -5591,64 +5669,56 @@ test "f80 extra struct" { |
| 5591 | 5669 | try expect(a.b == 24); |
| 5592 | 5670 | } |
| 5593 | 5671 | |
| 5594 | comptime { | |
| 5595 | skip: { | |
| 5596 | if (builtin.target.cpu.arch.isWasm()) break :skip; | |
| 5597 | ||
| 5598 | _ = struct { | |
| 5599 | export fn zig_f128(x: f128) f128 { | |
| 5600 | expect(x == 12) catch @panic("test failure"); | |
| 5601 | return 34; | |
| 5602 | } | |
| 5603 | extern fn c_f128(f128) f128; | |
| 5604 | test "f128 bare" { | |
| 5605 | if (!have_f128) return error.SkipZigTest; | |
| 5672 | export fn zig_f128(x: f128) f128 { | |
| 5673 | expect(x == 12) catch @panic("test failure"); | |
| 5674 | return 34; | |
| 5675 | } | |
| 5676 | extern fn c_f128(f128) f128; | |
| 5677 | test "f128 bare" { | |
| 5678 | if (!have_f128) return error.SkipZigTest; | |
| 5606 | 5679 | |
| 5607 | const a = c_f128(12.34); | |
| 5608 | try expect(@as(f64, @floatCast(a)) == 56.78); | |
| 5609 | } | |
| 5680 | const a = c_f128(12.34); | |
| 5681 | try expect(@as(f64, @floatCast(a)) == 56.78); | |
| 5682 | } | |
| 5610 | 5683 | |
| 5611 | const f128_struct = extern struct { | |
| 5612 | a: f128, | |
| 5613 | }; | |
| 5614 | export fn zig_f128_struct(a: f128_struct) f128_struct { | |
| 5615 | expect(a.a == 12345) catch @panic("test failure"); | |
| 5616 | return .{ .a = 98765 }; | |
| 5617 | } | |
| 5618 | extern fn c_f128_struct(f128_struct) f128_struct; | |
| 5619 | test "f128 struct" { | |
| 5620 | if (!have_f128) return error.SkipZigTest; | |
| 5684 | const f128_struct = extern struct { | |
| 5685 | a: f128, | |
| 5686 | }; | |
| 5687 | export fn zig_f128_struct(a: f128_struct) f128_struct { | |
| 5688 | expect(a.a == 12345) catch @panic("test failure"); | |
| 5689 | return .{ .a = 98765 }; | |
| 5690 | } | |
| 5691 | extern fn c_f128_struct(f128_struct) f128_struct; | |
| 5692 | test "f128 struct" { | |
| 5693 | if (!have_f128) return error.SkipZigTest; | |
| 5621 | 5694 | |
| 5622 | const a = c_f128_struct(.{ .a = 12.34 }); | |
| 5623 | try expect(@as(f64, @floatCast(a.a)) == 56.78); | |
| 5695 | const a = c_f128_struct(.{ .a = 12.34 }); | |
| 5696 | try expect(@as(f64, @floatCast(a.a)) == 56.78); | |
| 5624 | 5697 | |
| 5625 | const b = c_f128_f128_struct(.{ .a = 12.34, .b = 87.65 }); | |
| 5626 | try expect(@as(f64, @floatCast(b.a)) == 56.78); | |
| 5627 | try expect(@as(f64, @floatCast(b.b)) == 43.21); | |
| 5628 | } | |
| 5698 | const b = c_f128_f128_struct(.{ .a = 12.34, .b = 87.65 }); | |
| 5699 | try expect(@as(f64, @floatCast(b.a)) == 56.78); | |
| 5700 | try expect(@as(f64, @floatCast(b.b)) == 43.21); | |
| 5701 | } | |
| 5629 | 5702 | |
| 5630 | const f128_f128_struct = extern struct { | |
| 5631 | a: f128, | |
| 5632 | b: f128, | |
| 5633 | }; | |
| 5634 | export fn zig_f128_f128_struct(a: f128_f128_struct) f128_f128_struct { | |
| 5635 | expect(a.a == 13) catch @panic("test failure"); | |
| 5636 | expect(a.b == 57) catch @panic("test failure"); | |
| 5637 | return .{ .a = 24, .b = 68 }; | |
| 5638 | } | |
| 5639 | extern fn c_f128_f128_struct(f128_f128_struct) f128_f128_struct; | |
| 5640 | test "f128 f128 struct" { | |
| 5641 | if (!have_f128) return error.SkipZigTest; | |
| 5703 | const f128_f128_struct = extern struct { | |
| 5704 | a: f128, | |
| 5705 | b: f128, | |
| 5706 | }; | |
| 5707 | export fn zig_f128_f128_struct(a: f128_f128_struct) f128_f128_struct { | |
| 5708 | expect(a.a == 13) catch @panic("test failure"); | |
| 5709 | expect(a.b == 57) catch @panic("test failure"); | |
| 5710 | return .{ .a = 24, .b = 68 }; | |
| 5711 | } | |
| 5712 | extern fn c_f128_f128_struct(f128_f128_struct) f128_f128_struct; | |
| 5713 | test "f128 f128 struct" { | |
| 5714 | if (!have_f128) return error.SkipZigTest; | |
| 5642 | 5715 | |
| 5643 | const a = c_f128_struct(.{ .a = 12.34 }); | |
| 5644 | try expect(@as(f64, @floatCast(a.a)) == 56.78); | |
| 5716 | const a = c_f128_struct(.{ .a = 12.34 }); | |
| 5717 | try expect(@as(f64, @floatCast(a.a)) == 56.78); | |
| 5645 | 5718 | |
| 5646 | const b = c_f128_f128_struct(.{ .a = 12.34, .b = 87.65 }); | |
| 5647 | try expect(@as(f64, @floatCast(b.a)) == 56.78); | |
| 5648 | try expect(@as(f64, @floatCast(b.b)) == 43.21); | |
| 5649 | } | |
| 5650 | }; | |
| 5651 | } | |
| 5719 | const b = c_f128_f128_struct(.{ .a = 12.34, .b = 87.65 }); | |
| 5720 | try expect(@as(f64, @floatCast(b.a)) == 56.78); | |
| 5721 | try expect(@as(f64, @floatCast(b.b)) == 43.21); | |
| 5652 | 5722 | } |
| 5653 | 5723 | |
| 5654 | 5724 | // The stdcall attribute on C functions is ignored when compiled on non-x86 |