diff --git a/src/codegen/wasm/CodeGen.zig b/src/codegen/wasm/CodeGen.zig index d8d8933cc3..1d5b25074c 100644 --- a/src/codegen/wasm/CodeGen.zig +++ b/src/codegen/wasm/CodeGen.zig @@ -1230,12 +1230,17 @@ pub fn generate( fn generateInner(cg: *CodeGen, any_returns: bool) InnerError!Mir { const zcu = cg.pt.zcu; try cg.branches.append(cg.gpa, .{}); - // clean up outer branch + defer { + var const_branch = cg.branches.pop().?; + const_branch.deinit(cg.gpa); + assert(cg.branches.items.len == 0); // missing branch merge + } + try cg.branches.append(cg.gpa, .{}); defer { var outer_branch = cg.branches.pop().?; outer_branch.deinit(cg.gpa); - assert(cg.branches.items.len == 0); // missing branch merge } + // Generate MIR for function body try cg.genBody(cg.air.getMainBody()); @@ -1384,7 +1389,7 @@ fn lowerArg(cg: *CodeGen, cc: std.builtin.CallingConvention, ty: Type, value: WV return cg.lowerToStack(value); } else { switch (value) { - .nav_ref, .stack_offset => _ = try cg.load(value, scalar_type, 0), + .nav_ref, .uav_ref, .stack_offset => _ = try cg.load(value, scalar_type, 0), .dead => unreachable, else => try cg.emitWValue(value), } @@ -2401,6 +2406,7 @@ fn store(cg: *CodeGen, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerErr } else if (abi_size > 16) { assert(offset == 0); try cg.memcpy(lhs, rhs, .{ .imm32 = @as(u32, @intCast(ty.abiSize(zcu))) }); + return; }, else => if (abi_size > 8) { return cg.fail("TODO: `store` for type `{f}` with abisize `{d}`", .{ ty.fmt(pt), abi_size }); @@ -2450,7 +2456,8 @@ fn airLoad(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { const loaded = try cg.load(operand, ty, 0); const ty_size = ty.abiSize(zcu); if (ty.isAbiInt(zcu) and ty_size * 8 > ty.bitSize(zcu)) { - const int_elem_ty = try pt.intType(.unsigned, @intCast(ty_size * 8)); + const int_info = ty.intInfo(zcu); + const int_elem_ty = try pt.intType(int_info.signedness, @intCast(ty_size * 8)); break :result try cg.trunc(loaded, ty, int_elem_ty); } else { break :result loaded; @@ -2496,11 +2503,15 @@ fn load(cg: *CodeGen, operand: WValue, ty: Type, offset: u32) InnerError!WValue } const abi_size: u8 = @intCast(ty.abiSize(zcu)); + const is_signed = switch (ty.zigTypeTag(zcu)) { + .int, .@"struct", .@"enum" => ty.intInfo(zcu).signedness == .signed, + else => false, + }; const opcode = buildOpcode(.{ .valtype1 = typeToValtype(ty, zcu, cg.target), .width = abi_size * 8, .op = .load, - .signedness = if (ty.isSignedInt(zcu)) .signed else .unsigned, + .signedness = if (is_signed) .signed else .unsigned, }); try cg.addMemArg( @@ -3026,10 +3037,15 @@ fn wrapOperand(cg: *CodeGen, operand: WValue, ty: Type) InnerError!WValue { if (wasm_bits == int_bits) return operand; + const is_signed = switch (ty.zigTypeTag(zcu)) { + .int, .@"struct", .@"enum" => ty.intInfo(zcu).signedness == .signed, + else => false, + }; + switch (wasm_bits) { 32 => { try cg.emitWValue(operand); - if (ty.isSignedInt(zcu)) { + if (is_signed) { try cg.addImm32(32 - int_bits); try cg.addTag(.i32_shl); try cg.addImm32(32 - int_bits); @@ -3042,7 +3058,7 @@ fn wrapOperand(cg: *CodeGen, operand: WValue, ty: Type) InnerError!WValue { }, 64 => { try cg.emitWValue(operand); - if (ty.isSignedInt(zcu)) { + if (is_signed) { try cg.addImm64(64 - int_bits); try cg.addTag(.i64_shl); try cg.addImm64(64 - int_bits); @@ -3063,7 +3079,7 @@ fn wrapOperand(cg: *CodeGen, operand: WValue, ty: Type) InnerError!WValue { try cg.emitWValue(result); _ = try cg.load(operand, Type.u64, 8); - if (ty.isSignedInt(zcu)) { + if (is_signed) { try cg.addImm64(128 - int_bits); try cg.addTag(.i64_shl); try cg.addImm64(128 - int_bits); @@ -3083,12 +3099,41 @@ fn wrapOperand(cg: *CodeGen, operand: WValue, ty: Type) InnerError!WValue { fn lowerPtr(cg: *CodeGen, ptr_val: InternPool.Index, prev_offset: u64) InnerError!WValue { const pt = cg.pt; const zcu = pt.zcu; - const ptr = zcu.intern_pool.indexToKey(ptr_val).ptr; + const ip = &zcu.intern_pool; + const ptr = ip.indexToKey(ptr_val).ptr; const offset: u64 = prev_offset + ptr.byte_offset; return switch (ptr.base_addr) { - .nav => |nav| return .{ .nav_ref = .{ .nav_index = nav, .offset = @intCast(offset) } }, - .uav => |uav| return .{ .uav_ref = .{ .ip_index = uav.val, .offset = @intCast(offset), .orig_ptr_ty = uav.orig_ty } }, - .int => return cg.lowerConstant(try pt.intValue(Type.usize, offset), Type.usize), + .nav => |nav| { + const nav_val = ip.getNav(nav); + const nav_ty = Type.fromInterned(nav_val.typeOf(ip)); + const is_fn_body = nav_ty.zigTypeTag(zcu) == .@"fn"; + + if (!is_fn_body and !nav_ty.hasRuntimeBitsIgnoreComptime(zcu)) { + return switch (cg.ptr_size) { + .wasm32 => .{ .imm32 = 0xaaaaaaaa }, + .wasm64 => .{ .imm64 = 0xaaaaaaaaaaaaaaaa }, + }; + } + + return .{ .nav_ref = .{ .nav_index = nav, .offset = @intCast(offset) } }; + }, + .uav => |uav| { + const uav_ty = Type.fromInterned(ip.typeOf(uav.val)); + const is_fn_body = uav_ty.zigTypeTag(zcu) == .@"fn"; + + if (!is_fn_body and !uav_ty.hasRuntimeBitsIgnoreComptime(zcu)) { + return switch (cg.ptr_size) { + .wasm32 => .{ .imm32 = 0xaaaaaaaa }, + .wasm64 => .{ .imm64 = 0xaaaaaaaaaaaaaaaa }, + }; + } + + return .{ .uav_ref = .{ .ip_index = uav.val, .offset = @intCast(offset), .orig_ptr_ty = uav.orig_ty } }; + }, + .int => return switch (cg.ptr_size) { + .wasm32 => .{ .imm32 = @intCast(offset) }, + .wasm64 => .{ .imm64 = offset }, + }, .eu_payload => |eu_ptr| try cg.lowerPtr( eu_ptr, offset + codegen.errUnionPayloadOffset( @@ -7116,7 +7161,7 @@ fn airTagName(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { const result_ptr = try cg.allocStack(cg.typeOfIndex(inst)); try cg.lowerToStack(result_ptr); - try cg.emitWValue(operand); + try cg.lowerToStack(operand); try cg.addInst(.{ .tag = .call_tag_name, .data = .{ .ip_index = enum_ty.toIntern() } }); return cg.finishAir(inst, result_ptr, &.{un_op}); diff --git a/src/dev.zig b/src/dev.zig index eb5ac52550..26803c1918 100644 --- a/src/dev.zig +++ b/src/dev.zig @@ -201,6 +201,7 @@ pub const Env = enum { .wasm => switch (feature) { .stdio_listen, .incremental, + .legalize, .wasm_backend, .wasm_linker, => true, diff --git a/src/link/Wasm/Flush.zig b/src/link/Wasm/Flush.zig index b5d93259e2..b5903c5d48 100644 --- a/src/link/Wasm/Flush.zig +++ b/src/link/Wasm/Flush.zig @@ -1853,7 +1853,6 @@ fn emitTagNameFunction( ) !void { const comp = wasm.base.comp; const gpa = comp.gpa; - const diags = &comp.link_diags; const zcu = comp.zcu.?; const ip = &zcu.intern_pool; const enum_type = ip.loadEnumType(enum_type_ip); @@ -1884,52 +1883,74 @@ fn emitTagNameFunction( appendReservedUleb32(code, 0); } else { const int_info = Zcu.Type.intInfo(.fromInterned(enum_type.tag_ty), zcu); - const outer_block_type: std.wasm.BlockType = switch (int_info.bits) { - 0...32 => .i32, - 33...64 => .i64, - else => return diags.fail("wasm linker does not yet implement @tagName for sparse enums with more than 64 bit integer tag types", .{}), - }; + const is_big_int = int_info.bits > 64; code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.local_get)); appendReservedUleb32(code, 0); // Outer block that computes table offset. + // use i32 block type since we're computing a table offset code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.block)); - code.appendAssumeCapacity(@intFromEnum(outer_block_type)); + code.appendAssumeCapacity(@intFromEnum(std.wasm.BlockType.i32)); for (tag_values, 0..) |tag_value, tag_index| { // block for this if case code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.block)); code.appendAssumeCapacity(@intFromEnum(std.wasm.BlockType.empty)); - // Tag value whose name should be returned. - code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.local_get)); - appendReservedUleb32(code, 1); - const val: Zcu.Value = .fromInterned(tag_value); - switch (outer_block_type) { - .i32 => { + if (is_big_int) { + var val_space: Zcu.Value.BigIntSpace = undefined; + const val_bigint = val.toBigInt(&val_space, zcu); + const num_limbs = (int_info.bits + 63) / 64; + + const limbs = try gpa.alloc(u64, num_limbs); + defer gpa.free(limbs); + val_bigint.writeTwosComplement(@ptrCast(limbs), .little); + + try code.ensureUnusedCapacity(gpa, 7 * 5 + 6 + 1 * 6 + 20 * num_limbs); + + for (0..num_limbs) |limb_index| { + // load the limb from memory (local 1 is pointer) + code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.local_get)); + appendReservedUleb32(code, 1); + + code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i64_load)); + appendReservedUleb32(code, @ctz(@as(u32, 8))); // alignment for i64 + appendReservedUleb32(code, @intCast(limb_index * 8)); + + appendReservedI64Const(code, limbs[limb_index]); + code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i64_ne)); + + // if not equal, break out of current case block + code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.br_if)); + appendReservedUleb32(code, 0); + } + } else { + // Tag value whose name should be returned. + code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.local_get)); + appendReservedUleb32(code, 1); + + if (int_info.bits <= 32) { const x: u32 = switch (int_info.signedness) { .signed => @bitCast(@as(i32, @intCast(val.toSignedInt(zcu)))), .unsigned => @intCast(val.toUnsignedInt(zcu)), }; appendReservedI32Const(code, x); code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i32_ne)); - }, - .i64 => { + } else { const x: u64 = switch (int_info.signedness) { .signed => @bitCast(val.toSignedInt(zcu)), .unsigned => val.toUnsignedInt(zcu), }; appendReservedI64Const(code, x); code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i64_ne)); - }, - else => unreachable, - } + } - // if they're not equal, break out of current branch - code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.br_if)); - appendReservedUleb32(code, 0); + // if they're not equal, break out of current branch + code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.br_if)); + appendReservedUleb32(code, 0); + } // Put the table offset of the result on the stack. appendReservedI32Const(code, @intCast(tag_index * slice_abi_size)); @@ -1962,7 +1983,7 @@ fn appendReservedI32Const(bytes: *ArrayList(u8), val: u32) void { bytes.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i32_const)); var w: std.Io.Writer = .fromArrayList(bytes); defer bytes.* = w.toArrayList(); - return w.writeSleb128(val) catch |err| switch (err) { + return w.writeSleb128(@as(i32, @bitCast(val))) catch |err| switch (err) { error.WriteFailed => unreachable, }; } @@ -1972,7 +1993,7 @@ fn appendReservedI64Const(bytes: *ArrayList(u8), val: u64) void { bytes.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i64_const)); var w: std.Io.Writer = .fromArrayList(bytes); defer bytes.* = w.toArrayList(); - return w.writeSleb128(val) catch |err| switch (err) { + return w.writeSleb128(@as(i64, @bitCast(val))) catch |err| switch (err) { error.WriteFailed => unreachable, }; } diff --git a/test/behavior/enum.zig b/test/behavior/enum.zig index 8fb5d288e3..1c1bf2398e 100644 --- a/test/behavior/enum.zig +++ b/test/behavior/enum.zig @@ -1084,6 +1084,102 @@ test "tag name with large enum values" { try expect(mem.eql(u8, @tagName(kdf), "argon2id")); } +test "@tagName with exotic integer enum types" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + + const S = struct { + fn testEnumSigned(comptime T: type) !void { + { + const E1 = enum(T) { + a = -125, + b = 125, + c = std.math.minInt(T), + d = std.math.maxInt(T), + }; + + var e: E1 = .a; + try expect(mem.eql(u8, @tagName(e), "a")); + e = .b; + try expect(mem.eql(u8, @tagName(e), "b")); + e = .c; + try expect(mem.eql(u8, @tagName(e), "c")); + e = .d; + try expect(mem.eql(u8, @tagName(e), "d")); + } + { + const E2 = enum(T) { + a = -125, + b = 125, + c = std.math.minInt(T), + d = std.math.maxInt(T), + _, + }; + + var e: E2 = .a; + try expect(mem.eql(u8, @tagName(e), "a")); + e = .b; + try expect(mem.eql(u8, @tagName(e), "b")); + e = .c; + try expect(mem.eql(u8, @tagName(e), "c")); + e = .d; + try expect(mem.eql(u8, @tagName(e), "d")); + } + } + + fn testEnumUnsigned(comptime T: type) !void { + { + const E1 = enum(T) { + a = std.math.maxInt(T) - 125, + b = 125, + c = std.math.minInt(T), + d = std.math.maxInt(T), + }; + + var e: E1 = .a; + try expect(mem.eql(u8, @tagName(e), "a")); + e = .b; + try expect(mem.eql(u8, @tagName(e), "b")); + e = .c; + try expect(mem.eql(u8, @tagName(e), "c")); + e = .d; + try expect(mem.eql(u8, @tagName(e), "d")); + } + { + const E2 = enum(T) { + a = std.math.maxInt(T) - 125, + b = 125, + c = std.math.minInt(T), + d = std.math.maxInt(T), + _, + }; + + var e: E2 = .a; + try expect(mem.eql(u8, @tagName(e), "a")); + e = .b; + try expect(mem.eql(u8, @tagName(e), "b")); + e = .c; + try expect(mem.eql(u8, @tagName(e), "c")); + e = .d; + try expect(mem.eql(u8, @tagName(e), "d")); + } + } + + fn doTheTest() !void { + try testEnumSigned(i33); + try testEnumSigned(i95); + try testEnumSigned(i127); + + try testEnumUnsigned(u33); + try testEnumUnsigned(u95); + try testEnumUnsigned(u127); + } + }; + + try S.doTheTest(); + try comptime S.doTheTest(); +} + test "@tagName in callconv(.c) function" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; diff --git a/test/behavior/field_parent_ptr.zig b/test/behavior/field_parent_ptr.zig index 0475fcebe5..c307040635 100644 --- a/test/behavior/field_parent_ptr.zig +++ b/test/behavior/field_parent_ptr.zig @@ -1033,6 +1033,7 @@ test "@fieldParentPtr packed struct first zero-bit field" { if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; const C = packed struct { a: u0 = 0, @@ -1139,6 +1140,7 @@ test "@fieldParentPtr packed struct middle zero-bit field" { if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; const C = packed struct { a: f32 = 3.14, @@ -1245,6 +1247,7 @@ test "@fieldParentPtr packed struct last zero-bit field" { if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; const C = packed struct { a: f32 = 3.14, diff --git a/test/behavior/struct.zig b/test/behavior/struct.zig index 03a49b70d1..866b74865c 100644 --- a/test/behavior/struct.zig +++ b/test/behavior/struct.zig @@ -749,6 +749,7 @@ test "packed struct with fp fields" { if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; const S = packed struct { data0: f32, diff --git a/test/behavior/vector.zig b/test/behavior/vector.zig index e046c54ebd..6db7e14a00 100644 --- a/test/behavior/vector.zig +++ b/test/behavior/vector.zig @@ -9,6 +9,7 @@ const expectEqual = std.testing.expectEqual; test "implicit cast vector to array - bool" { if (builtin.cpu.arch == .aarch64_be and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; const S = struct { fn doTheTest() !void { @@ -32,6 +33,7 @@ test "implicit cast vector to array - bool" { test "implicit cast array to vector - bool" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; const S = struct { fn doTheTest() !void { diff --git a/test/tests.zig b/test/tests.zig index 3625b560cd..22c3bdc804 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -1366,18 +1366,16 @@ const test_targets = blk: { // WASI Targets - // Disabled due to no active maintainer (feel free to fix the failures - // and then re-enable at any time). The failures occur due to backend - // miscompilation of different AIR from the frontend. - //.{ - // .target = .{ - // .cpu_arch = .wasm32, - // .os_tag = .wasi, - // .abi = .none, - // }, - // .use_llvm = false, - // .use_lld = false, - //}, + .{ + .target = .{ + .cpu_arch = .wasm32, + .os_tag = .wasi, + .abi = .none, + }, + .skip_modules = &.{ "compiler-rt", "std" }, + .use_llvm = false, + .use_lld = false, + }, .{ .target = .{ .cpu_arch = .wasm32,