From d4ceb12ae9d409dbd52c1f5c96312a1e6ad7d6bc Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Sun, 16 Apr 2023 15:10:45 +0200 Subject: [PATCH] wasm: implement `error_set_has_value` This implements the safety check for error casts. The instruction generates a jump table with 2 possibilities. The operand is used as an index into the jump table. For cases where the value does not exist within the error set, it will generate a jump to the 'false' block. For cases where it does exist, it will generate a jump to the 'true' block. By calculating the highest and lowest value we can keep the jump table smaller, as it doesn't need to contain an index into the entire error set. --- src/Module.zig | 2 +- src/arch/wasm/CodeGen.zig | 85 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 85 insertions(+), 2 deletions(-) diff --git a/src/Module.zig b/src/Module.zig index fa91e8c1edd977ed9463fd88c12308380fe285c8..27f7b24e7a06af5f5233c5df9944e2c763e85d7c 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -6626,7 +6626,7 @@ pub fn backendSupportsFeature(mod: Module, feature: Feature) bool { .safety_check_formatted => mod.comp.bin_file.options.use_llvm, .error_return_trace => mod.comp.bin_file.options.use_llvm, .is_named_enum_value => mod.comp.bin_file.options.use_llvm, - .error_set_has_value => mod.comp.bin_file.options.use_llvm, + .error_set_has_value => mod.comp.bin_file.options.use_llvm or mod.comp.bin_file.options.target.isWasm(), .field_reordering => mod.comp.bin_file.options.use_llvm, }; } diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index 0d73e351851a055f30c848378029ffdabdcc81f9..c0bab03428bf1699c9dc47f812d32f3b5e18c7fb 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -1946,6 +1946,8 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { .ret_addr => func.airRetAddr(inst), .tag_name => func.airTagName(inst), + .error_set_has_value => func.airErrorSetHasValue(inst), + .mul_sat, .mod, .assembly, @@ -1967,7 +1969,6 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { .set_err_return_trace, .save_err_return_trace_index, .is_named_enum_value, - .error_set_has_value, .addrspace_cast, .vector_store_elem, .c_va_arg, @@ -6514,3 +6515,85 @@ fn getTagNameFunction(func: *CodeGen, enum_ty: Type) InnerError!u32 { const func_type = try genFunctype(arena, .Unspecified, &.{int_tag_ty}, slice_ty, func.target); return func.bin_file.createFunction(func_name, func_type, &body_list, &relocs); } + +fn airErrorSetHasValue(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { + const ty_op = func.air.instructions.items(.data)[inst].ty_op; + if (func.liveness.isUnused(inst)) return func.finishAir(inst, .none, &.{ty_op.operand}); + + const operand = try func.resolveInst(ty_op.operand); + const error_set_ty = func.air.getRefType(ty_op.ty); + const result = try func.allocLocal(Type.bool); + + const names = error_set_ty.errorSetNames(); + var values = try std.ArrayList(u32).initCapacity(func.gpa, names.len); + defer values.deinit(); + + const module = func.bin_file.base.options.module.?; + var lowest: ?u32 = null; + var highest: ?u32 = null; + for (names) |name| { + const err_int = module.global_error_set.get(name).?; + if (lowest) |*l| { + if (err_int < l.*) { + l.* = err_int; + } + } else { + lowest = err_int; + } + if (highest) |*h| { + if (err_int > h.*) { + highest = err_int; + } + } else { + highest = err_int; + } + + values.appendAssumeCapacity(err_int); + } + + // start block for 'true' branch + try func.startBlock(.block, wasm.block_empty); + // start block for 'false' branch + try func.startBlock(.block, wasm.block_empty); + // block for the jump table itself + try func.startBlock(.block, wasm.block_empty); + + // lower operand to determine jump table target + try func.emitWValue(operand); + try func.addImm32(@intCast(i32, lowest.?)); + try func.addTag(.i32_sub); + + // Account for default branch so always add '1' + const depth = @intCast(u32, highest.? - lowest.? + 1); + const jump_table: Mir.JumpTable = .{ .length = depth }; + const table_extra_index = try func.addExtra(jump_table); + try func.addInst(.{ .tag = .br_table, .data = .{ .payload = table_extra_index } }); + try func.mir_extra.ensureUnusedCapacity(func.gpa, depth); + + var value: u32 = lowest.?; + while (value <= highest.?) : (value += 1) { + const idx: u32 = blk: { + for (values.items) |val| { + if (val == value) break :blk 1; + } + break :blk 0; + }; + func.mir_extra.appendAssumeCapacity(idx); + } + try func.endBlock(); + + // 'false' branch (i.e. error set does not have value + // ensure we set local to 0 in case the local was re-used. + try func.addImm32(0); + try func.addLabel(.local_set, result.local.value); + try func.addLabel(.br, 1); + try func.endBlock(); + + // 'true' branch + try func.addImm32(1); + try func.addLabel(.local_set, result.local.value); + try func.addLabel(.br, 0); + try func.endBlock(); + + return func.finishAir(inst, result, &.{ty_op.operand}); +} -- 2.54.0