authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-01-13 15:19:24-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-01-15 15:11:36-08:00
log8abdebecdca3a905099fa17f2497e8bf5f918e8a
treee1a49978c7483cd298bdcac5d747f88dbb728412
parentb5261599d70dfdb5709a19fabc0e5fe9a3d0e3aa

wasm linker: implement `@tagName` for sparse enums


7 files changed, 136 insertions(+), 47 deletions(-)

lib/std/wasm.zig+12-1
...@@ -655,7 +655,18 @@ pub const function_type: u8 = 0x60;...@@ -655,7 +655,18 @@ pub const function_type: u8 = 0x60;
655pub const result_type: u8 = 0x40;655pub const result_type: u8 = 0x40;
656656
657/// Represents a block which will not return a value657/// Represents a block which will not return a value
658pub const block_empty: u8 = 0x40;658pub const BlockType = enum(u8) {
659 empty = 0x40,
660 i32 = 0x7F,
661 i64 = 0x7E,
662 f32 = 0x7D,
663 f64 = 0x7C,
664 v128 = 0x7B,
665
666 pub fn fromValtype(valtype: Valtype) BlockType {
667 return @enumFromInt(@intFromEnum(valtype));
668 }
669};
659670
660// binary constants671// binary constants
661pub const magic = [_]u8{ 0x00, 0x61, 0x73, 0x6D }; // \0asm672pub const magic = [_]u8{ 0x00, 0x61, 0x73, 0x6D }; // \0asm
src/Value.zig+4-4
...@@ -241,12 +241,12 @@ pub fn getVariable(val: Value, mod: *Zcu) ?InternPool.Key.Variable {...@@ -241,12 +241,12 @@ pub fn getVariable(val: Value, mod: *Zcu) ?InternPool.Key.Variable {
241241
242/// If the value fits in a u64, return it, otherwise null.242/// If the value fits in a u64, return it, otherwise null.
243/// Asserts not undefined.243/// Asserts not undefined.
244pub fn getUnsignedInt(val: Value, zcu: *Zcu) ?u64 {244pub fn getUnsignedInt(val: Value, zcu: *const Zcu) ?u64 {
245 return getUnsignedIntInner(val, .normal, zcu, {}) catch unreachable;245 return getUnsignedIntInner(val, .normal, zcu, {}) catch unreachable;
246}246}
247247
248/// Asserts the value is an integer and it fits in a u64248/// Asserts the value is an integer and it fits in a u64
249pub fn toUnsignedInt(val: Value, zcu: *Zcu) u64 {249pub fn toUnsignedInt(val: Value, zcu: *const Zcu) u64 {
250 return getUnsignedInt(val, zcu).?;250 return getUnsignedInt(val, zcu).?;
251}251}
252252
...@@ -259,7 +259,7 @@ pub fn getUnsignedIntSema(val: Value, pt: Zcu.PerThread) !?u64 {...@@ -259,7 +259,7 @@ pub fn getUnsignedIntSema(val: Value, pt: Zcu.PerThread) !?u64 {
259pub fn getUnsignedIntInner(259pub fn getUnsignedIntInner(
260 val: Value,260 val: Value,
261 comptime strat: ResolveStrat,261 comptime strat: ResolveStrat,
262 zcu: *Zcu,262 zcu: strat.ZcuPtr(),
263 tid: strat.Tid(),263 tid: strat.Tid(),
264) !?u64 {264) !?u64 {
265 return switch (val.toIntern()) {265 return switch (val.toIntern()) {
...@@ -304,7 +304,7 @@ pub fn toUnsignedIntSema(val: Value, pt: Zcu.PerThread) !u64 {...@@ -304,7 +304,7 @@ pub fn toUnsignedIntSema(val: Value, pt: Zcu.PerThread) !u64 {
304}304}
305305
306/// Asserts the value is an integer and it fits in a i64306/// Asserts the value is an integer and it fits in a i64
307pub fn toSignedInt(val: Value, zcu: *Zcu) i64 {307pub fn toSignedInt(val: Value, zcu: *const Zcu) i64 {
308 return switch (val.toIntern()) {308 return switch (val.toIntern()) {
309 .bool_false => 0,309 .bool_false => 0,
310 .bool_true => 1,310 .bool_true => 1,
src/arch/wasm/CodeGen.zig+25-30
...@@ -80,7 +80,7 @@ start_mir_extra_off: u32,...@@ -80,7 +80,7 @@ start_mir_extra_off: u32,
80start_locals_off: u32,80start_locals_off: u32,
81/// List of all locals' types generated throughout this declaration81/// List of all locals' types generated throughout this declaration
82/// used to emit locals count at start of 'code' section.82/// used to emit locals count at start of 'code' section.
83locals: *std.ArrayListUnmanaged(u8),83locals: *std.ArrayListUnmanaged(std.wasm.Valtype),
84/// When a function is executing, we store the the current stack pointer's value within this local.84/// When a function is executing, we store the the current stack pointer's value within this local.
85/// This value is then used to restore the stack pointer to the original value at the return of the function.85/// This value is then used to restore the stack pointer to the original value at the return of the function.
86initial_stack_value: WValue = .none,86initial_stack_value: WValue = .none,
...@@ -210,7 +210,7 @@ const WValue = union(enum) {...@@ -210,7 +210,7 @@ const WValue = union(enum) {
210 if (local_value < reserved + 2) return; // reserved locals may never be re-used. Also accounts for 2 stack locals.210 if (local_value < reserved + 2) return; // reserved locals may never be re-used. Also accounts for 2 stack locals.
211211
212 const index = local_value - reserved;212 const index = local_value - reserved;
213 const valtype: std.wasm.Valtype = @enumFromInt(gen.locals.items[gen.start_locals_off + index]);213 const valtype = gen.locals.items[gen.start_locals_off + index];
214 switch (valtype) {214 switch (valtype) {
215 .i32 => gen.free_locals_i32.append(gen.gpa, local_value) catch return, // It's ok to fail any of those, a new local can be allocated instead215 .i32 => gen.free_locals_i32.append(gen.gpa, local_value) catch return, // It's ok to fail any of those, a new local can be allocated instead
216 .i64 => gen.free_locals_i64.append(gen.gpa, local_value) catch return,216 .i64 => gen.free_locals_i64.append(gen.gpa, local_value) catch return,
...@@ -995,18 +995,13 @@ pub fn typeToValtype(ty: Type, zcu: *const Zcu, target: *const std.Target) std.w...@@ -995,18 +995,13 @@ pub fn typeToValtype(ty: Type, zcu: *const Zcu, target: *const std.Target) std.w
995 };995 };
996}996}
997997
998/// Using a given `Type`, returns the byte representation of its wasm value type
999fn genValtype(ty: Type, zcu: *const Zcu, target: *const std.Target) u8 {
1000 return @intFromEnum(typeToValtype(ty, zcu, target));
1001}
1002
1003/// Using a given `Type`, returns the corresponding wasm value type998/// Using a given `Type`, returns the corresponding wasm value type
1004/// Differently from `genValtype` this also allows `void` to create a block999/// Differently from `typeToValtype` this also allows `void` to create a block
1005/// with no return type1000/// with no return type
1006fn genBlockType(ty: Type, zcu: *const Zcu, target: *const std.Target) u8 {1001fn genBlockType(ty: Type, zcu: *const Zcu, target: *const std.Target) std.wasm.BlockType {
1007 return switch (ty.ip_index) {1002 return switch (ty.ip_index) {
1008 .void_type, .noreturn_type => std.wasm.block_empty,1003 .void_type, .noreturn_type => .empty,
1009 else => genValtype(ty, zcu, target),1004 else => .fromValtype(typeToValtype(ty, zcu, target)),
1010 };1005 };
1011}1006}
10121007
...@@ -1145,7 +1140,7 @@ fn allocLocal(cg: *CodeGen, ty: Type) InnerError!WValue {...@@ -1145,7 +1140,7 @@ fn allocLocal(cg: *CodeGen, ty: Type) InnerError!WValue {
1145/// to use a zero-initialized local.1140/// to use a zero-initialized local.
1146fn ensureAllocLocal(cg: *CodeGen, ty: Type) InnerError!WValue {1141fn ensureAllocLocal(cg: *CodeGen, ty: Type) InnerError!WValue {
1147 const zcu = cg.pt.zcu;1142 const zcu = cg.pt.zcu;
1148 try cg.locals.append(cg.gpa, genValtype(ty, zcu, cg.target));1143 try cg.locals.append(cg.gpa, typeToValtype(ty, zcu, cg.target));
1149 const initial_index = cg.local_index;1144 const initial_index = cg.local_index;
1150 cg.local_index += 1;1145 cg.local_index += 1;
1151 return .{ .local = .{ .value = initial_index, .references = 1 } };1146 return .{ .local = .{ .value = initial_index, .references = 1 } };
...@@ -1197,7 +1192,7 @@ pub const Function = extern struct {...@@ -1197,7 +1192,7 @@ pub const Function = extern struct {
1197 std.leb.writeUleb128(code.fixedWriter(), @as(u32, @intCast(locals.len))) catch unreachable;1192 std.leb.writeUleb128(code.fixedWriter(), @as(u32, @intCast(locals.len))) catch unreachable;
1198 for (locals) |local| {1193 for (locals) |local| {
1199 std.leb.writeUleb128(code.fixedWriter(), @as(u32, 1)) catch unreachable;1194 std.leb.writeUleb128(code.fixedWriter(), @as(u32, 1)) catch unreachable;
1200 code.appendAssumeCapacity(local);1195 code.appendAssumeCapacity(@intFromEnum(local));
1201 }1196 }
12021197
1203 // Stack management section of function prologue.1198 // Stack management section of function prologue.
...@@ -1651,8 +1646,8 @@ fn memcpy(cg: *CodeGen, dst: WValue, src: WValue, len: WValue) !void {...@@ -1651,8 +1646,8 @@ fn memcpy(cg: *CodeGen, dst: WValue, src: WValue, len: WValue) !void {
1651 try cg.addLocal(.local_set, offset.local.value);1646 try cg.addLocal(.local_set, offset.local.value);
16521647
1653 // outer block to jump to when loop is done1648 // outer block to jump to when loop is done
1654 try cg.startBlock(.block, std.wasm.block_empty);1649 try cg.startBlock(.block, .empty);
1655 try cg.startBlock(.loop, std.wasm.block_empty);1650 try cg.startBlock(.loop, .empty);
16561651
1657 // loop condition (offset == length -> break)1652 // loop condition (offset == length -> break)
1658 {1653 {
...@@ -3387,12 +3382,12 @@ fn lowerBlock(cg: *CodeGen, inst: Air.Inst.Index, block_ty: Type, body: []const...@@ -3387,12 +3382,12 @@ fn lowerBlock(cg: *CodeGen, inst: Air.Inst.Index, block_ty: Type, body: []const
3387 const wasm_block_ty = genBlockType(block_ty, zcu, cg.target);3382 const wasm_block_ty = genBlockType(block_ty, zcu, cg.target);
33883383
3389 // if wasm_block_ty is non-empty, we create a register to store the temporary value3384 // if wasm_block_ty is non-empty, we create a register to store the temporary value
3390 const block_result: WValue = if (wasm_block_ty != std.wasm.block_empty) blk: {3385 const block_result: WValue = if (wasm_block_ty != .empty) blk: {
3391 const ty: Type = if (isByRef(block_ty, zcu, cg.target)) Type.u32 else block_ty;3386 const ty: Type = if (isByRef(block_ty, zcu, cg.target)) Type.u32 else block_ty;
3392 break :blk try cg.ensureAllocLocal(ty); // make sure it's a clean local as it may never get overwritten3387 break :blk try cg.ensureAllocLocal(ty); // make sure it's a clean local as it may never get overwritten
3393 } else .none;3388 } else .none;
33943389
3395 try cg.startBlock(.block, std.wasm.block_empty);3390 try cg.startBlock(.block, .empty);
3396 // Here we set the current block idx, so breaks know the depth to jump3391 // Here we set the current block idx, so breaks know the depth to jump
3397 // to when breaking out.3392 // to when breaking out.
3398 try cg.blocks.putNoClobber(cg.gpa, inst, .{3393 try cg.blocks.putNoClobber(cg.gpa, inst, .{
...@@ -3410,11 +3405,11 @@ fn lowerBlock(cg: *CodeGen, inst: Air.Inst.Index, block_ty: Type, body: []const...@@ -3410,11 +3405,11 @@ fn lowerBlock(cg: *CodeGen, inst: Air.Inst.Index, block_ty: Type, body: []const
3410}3405}
34113406
3412/// appends a new wasm block to the code section and increases the `block_depth` by 13407/// appends a new wasm block to the code section and increases the `block_depth` by 1
3413fn startBlock(cg: *CodeGen, block_tag: std.wasm.Opcode, valtype: u8) !void {3408fn startBlock(cg: *CodeGen, block_tag: std.wasm.Opcode, block_type: std.wasm.BlockType) !void {
3414 cg.block_depth += 1;3409 cg.block_depth += 1;
3415 try cg.addInst(.{3410 try cg.addInst(.{
3416 .tag = Mir.Inst.Tag.fromOpcode(block_tag),3411 .tag = Mir.Inst.Tag.fromOpcode(block_tag),
3417 .data = .{ .block_type = valtype },3412 .data = .{ .block_type = block_type },
3418 });3413 });
3419}3414}
34203415
...@@ -3431,7 +3426,7 @@ fn airLoop(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -3431,7 +3426,7 @@ fn airLoop(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
34313426
3432 // result type of loop is always 'noreturn', meaning we can always3427 // result type of loop is always 'noreturn', meaning we can always
3433 // emit the wasm type 'block_empty'.3428 // emit the wasm type 'block_empty'.
3434 try cg.startBlock(.loop, std.wasm.block_empty);3429 try cg.startBlock(.loop, .empty);
34353430
3436 try cg.loops.putNoClobber(cg.gpa, inst, cg.block_depth);3431 try cg.loops.putNoClobber(cg.gpa, inst, cg.block_depth);
3437 defer assert(cg.loops.remove(inst));3432 defer assert(cg.loops.remove(inst));
...@@ -3451,7 +3446,7 @@ fn airCondBr(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -3451,7 +3446,7 @@ fn airCondBr(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
3451 const liveness_condbr = cg.liveness.getCondBr(inst);3446 const liveness_condbr = cg.liveness.getCondBr(inst);
34523447
3453 // result type is always noreturn, so use `block_empty` as type.3448 // result type is always noreturn, so use `block_empty` as type.
3454 try cg.startBlock(.block, std.wasm.block_empty);3449 try cg.startBlock(.block, .empty);
3455 // emit the conditional value3450 // emit the conditional value
3456 try cg.emitWValue(condition);3451 try cg.emitWValue(condition);
34573452
...@@ -3940,7 +3935,7 @@ fn airSwitchBr(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -3940,7 +3935,7 @@ fn airSwitchBr(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
3940 const pt = cg.pt;3935 const pt = cg.pt;
3941 const zcu = pt.zcu;3936 const zcu = pt.zcu;
3942 // result type is always 'noreturn'3937 // result type is always 'noreturn'
3943 const blocktype = std.wasm.block_empty;3938 const blocktype: std.wasm.BlockType = .empty;
3944 const switch_br = cg.air.unwrapSwitch(inst);3939 const switch_br = cg.air.unwrapSwitch(inst);
3945 const target = try cg.resolveInst(switch_br.operand);3940 const target = try cg.resolveInst(switch_br.operand);
3946 const target_ty = cg.typeOf(switch_br.operand);3941 const target_ty = cg.typeOf(switch_br.operand);
...@@ -4832,8 +4827,8 @@ fn memset(cg: *CodeGen, elem_ty: Type, ptr: WValue, len: WValue, value: WValue)...@@ -4832,8 +4827,8 @@ fn memset(cg: *CodeGen, elem_ty: Type, ptr: WValue, len: WValue, value: WValue)
4832 try cg.addLocal(.local_set, end_ptr.local.value);4827 try cg.addLocal(.local_set, end_ptr.local.value);
48334828
4834 // outer block to jump to when loop is done4829 // outer block to jump to when loop is done
4835 try cg.startBlock(.block, std.wasm.block_empty);4830 try cg.startBlock(.block, .empty);
4836 try cg.startBlock(.loop, std.wasm.block_empty);4831 try cg.startBlock(.loop, .empty);
48374832
4838 // check for condition for loop end4833 // check for condition for loop end
4839 try cg.emitWValue(new_ptr);4834 try cg.emitWValue(new_ptr);
...@@ -5410,7 +5405,7 @@ fn cmpOptionals(cg: *CodeGen, lhs: WValue, rhs: WValue, operand_ty: Type, op: st...@@ -5410,7 +5405,7 @@ fn cmpOptionals(cg: *CodeGen, lhs: WValue, rhs: WValue, operand_ty: Type, op: st
5410 var result = try cg.ensureAllocLocal(Type.i32);5405 var result = try cg.ensureAllocLocal(Type.i32);
5411 defer result.free(cg);5406 defer result.free(cg);
54125407
5413 try cg.startBlock(.block, std.wasm.block_empty);5408 try cg.startBlock(.block, .empty);
5414 _ = try cg.isNull(lhs, operand_ty, .i32_eq);5409 _ = try cg.isNull(lhs, operand_ty, .i32_eq);
5415 _ = try cg.isNull(rhs, operand_ty, .i32_eq);5410 _ = try cg.isNull(rhs, operand_ty, .i32_eq);
5416 try cg.addTag(.i32_ne); // inverse so we can exit early5411 try cg.addTag(.i32_ne); // inverse so we can exit early
...@@ -6420,7 +6415,7 @@ fn lowerTry(...@@ -6420,7 +6415,7 @@ fn lowerTry(
64206415
6421 if (!err_union_ty.errorUnionSet(zcu).errorSetIsEmpty(zcu)) {6416 if (!err_union_ty.errorUnionSet(zcu).errorSetIsEmpty(zcu)) {
6422 // Block we can jump out of when error is not set6417 // Block we can jump out of when error is not set
6423 try cg.startBlock(.block, std.wasm.block_empty);6418 try cg.startBlock(.block, .empty);
64246419
6425 // check if the error tag is set for the error union.6420 // check if the error tag is set for the error union.
6426 try cg.emitWValue(err_union);6421 try cg.emitWValue(err_union);
...@@ -7105,11 +7100,11 @@ fn airErrorSetHasValue(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -7105,11 +7100,11 @@ fn airErrorSetHasValue(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
7105 }7100 }
71067101
7107 // start block for 'true' branch7102 // start block for 'true' branch
7108 try cg.startBlock(.block, std.wasm.block_empty);7103 try cg.startBlock(.block, .empty);
7109 // start block for 'false' branch7104 // start block for 'false' branch
7110 try cg.startBlock(.block, std.wasm.block_empty);7105 try cg.startBlock(.block, .empty);
7111 // block for the jump table itself7106 // block for the jump table itself
7112 try cg.startBlock(.block, std.wasm.block_empty);7107 try cg.startBlock(.block, .empty);
71137108
7114 // lower operand to determine jump table target7109 // lower operand to determine jump table target
7115 try cg.emitWValue(operand);7110 try cg.emitWValue(operand);
...@@ -7274,7 +7269,7 @@ fn airAtomicRmw(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -7274,7 +7269,7 @@ fn airAtomicRmw(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
7274 const value = try tmp.toLocal(cg, ty);7269 const value = try tmp.toLocal(cg, ty);
72757270
7276 // create a loop to cmpxchg the new value7271 // create a loop to cmpxchg the new value
7277 try cg.startBlock(.loop, std.wasm.block_empty);7272 try cg.startBlock(.loop, .empty);
72787273
7279 try cg.emitWValue(ptr);7274 try cg.emitWValue(ptr);
7280 try cg.emitWValue(value);7275 try cg.emitWValue(value);
src/arch/wasm/Emit.zig+1-1
...@@ -43,7 +43,7 @@ pub fn lowerToCode(emit: *Emit) Error!void {...@@ -43,7 +43,7 @@ pub fn lowerToCode(emit: *Emit) Error!void {
43 const block_type = datas[inst].block_type;43 const block_type = datas[inst].block_type;
44 try code.ensureUnusedCapacity(gpa, 2);44 try code.ensureUnusedCapacity(gpa, 2);
45 code.appendAssumeCapacity(@intFromEnum(tags[inst]));45 code.appendAssumeCapacity(@intFromEnum(tags[inst]));
46 code.appendAssumeCapacity(block_type);46 code.appendAssumeCapacity(@intFromEnum(block_type));
4747
48 inst += 1;48 inst += 1;
49 continue :loop tags[inst];49 continue :loop tags[inst];
src/arch/wasm/Mir.zig+1-1
...@@ -588,7 +588,7 @@ pub const Inst = struct {...@@ -588,7 +588,7 @@ pub const Inst = struct {
588 /// Uses no additional data588 /// Uses no additional data
589 tag: void,589 tag: void,
590 /// Contains the result type of a block590 /// Contains the result type of a block
591 block_type: u8,591 block_type: std.wasm.BlockType,
592 /// Label: Each structured control instruction introduces an implicit label.592 /// Label: Each structured control instruction introduces an implicit label.
593 /// Labels are targets for branch instructions that reference them with593 /// Labels are targets for branch instructions that reference them with
594 /// label indices. Unlike with other index spaces, indexing of labels594 /// label indices. Unlike with other index spaces, indexing of labels
src/link/Wasm.zig+1-1
...@@ -283,7 +283,7 @@ mir_instructions: std.MultiArrayList(Mir.Inst) = .{},...@@ -283,7 +283,7 @@ mir_instructions: std.MultiArrayList(Mir.Inst) = .{},
283/// Corresponds to `mir_instructions`.283/// Corresponds to `mir_instructions`.
284mir_extra: std.ArrayListUnmanaged(u32) = .empty,284mir_extra: std.ArrayListUnmanaged(u32) = .empty,
285/// All local types for all Zcu functions.285/// All local types for all Zcu functions.
286all_zcu_locals: std.ArrayListUnmanaged(u8) = .empty,286all_zcu_locals: std.ArrayListUnmanaged(std.wasm.Valtype) = .empty,
287287
288params_scratch: std.ArrayListUnmanaged(std.wasm.Valtype) = .empty,288params_scratch: std.ArrayListUnmanaged(std.wasm.Valtype) = .empty,
289returns_scratch: std.ArrayListUnmanaged(std.wasm.Valtype) = .empty,289returns_scratch: std.ArrayListUnmanaged(std.wasm.Valtype) = .empty,
src/link/Wasm/Flush.zig+92-9
...@@ -780,9 +780,10 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {...@@ -780,9 +780,10 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {
780780
781 const zcu = comp.zcu.?;781 const zcu = comp.zcu.?;
782 const ip = &zcu.intern_pool;782 const ip = &zcu.intern_pool;
783 switch (ip.indexToKey(i.key(wasm).*)) {783 const ip_index = i.key(wasm).*;
784 switch (ip.indexToKey(ip_index)) {
784 .enum_type => {785 .enum_type => {
785 try emitTagNameFunction(gpa, binary_bytes, f.data_segments.get(.__zig_tag_name_table).?, i.value(wasm).tag_name.table_index);786 try emitTagNameFunction(wasm, binary_bytes, f.data_segments.get(.__zig_tag_name_table).?, i.value(wasm).tag_name.table_index, ip_index);
786 },787 },
787 else => try i.value(wasm).function.lower(wasm, binary_bytes),788 else => try i.value(wasm).function.lower(wasm, binary_bytes),
788 }789 }
...@@ -1772,13 +1773,13 @@ fn emitInitMemoryFunction(...@@ -1772,13 +1773,13 @@ fn emitInitMemoryFunction(
1772 // destination blocks1773 // destination blocks
1773 // based on values we jump to corresponding label1774 // based on values we jump to corresponding label
1774 binary_bytes.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.block)); // $drop1775 binary_bytes.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.block)); // $drop
1775 binary_bytes.appendAssumeCapacity(std.wasm.block_empty); // block type1776 binary_bytes.appendAssumeCapacity(@intFromEnum(std.wasm.BlockType.empty));
17761777
1777 binary_bytes.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.block)); // $wait1778 binary_bytes.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.block)); // $wait
1778 binary_bytes.appendAssumeCapacity(std.wasm.block_empty); // block type1779 binary_bytes.appendAssumeCapacity(@intFromEnum(std.wasm.BlockType.empty));
17791780
1780 binary_bytes.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.block)); // $init1781 binary_bytes.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.block)); // $init
1781 binary_bytes.appendAssumeCapacity(std.wasm.block_empty); // block type1782 binary_bytes.appendAssumeCapacity(@intFromEnum(std.wasm.BlockType.empty));
17821783
1783 // atomically check1784 // atomically check
1784 appendReservedI32Const(binary_bytes, flag_address);1785 appendReservedI32Const(binary_bytes, flag_address);
...@@ -1898,18 +1899,25 @@ fn emitInitMemoryFunction(...@@ -1898,18 +1899,25 @@ fn emitInitMemoryFunction(
1898}1899}
18991900
1900fn emitTagNameFunction(1901fn emitTagNameFunction(
1901 gpa: Allocator,1902 wasm: *Wasm,
1902 code: *std.ArrayListUnmanaged(u8),1903 code: *std.ArrayListUnmanaged(u8),
1903 table_base_addr: u32,1904 table_base_addr: u32,
1904 table_index: u32,1905 table_index: u32,
1905) Allocator.Error!void {1906 enum_type_ip: InternPool.Index,
1907) !void {
1908 const comp = wasm.base.comp;
1909 const gpa = comp.gpa;
1910 const diags = &comp.link_diags;
1911 const zcu = comp.zcu.?;
1912 const ip = &zcu.intern_pool;
1913 const enum_type = ip.loadEnumType(enum_type_ip);
1914
1906 try code.ensureUnusedCapacity(gpa, 7 * 5 + 6 + 1 * 6);1915 try code.ensureUnusedCapacity(gpa, 7 * 5 + 6 + 1 * 6);
1907 appendReservedUleb32(code, 0); // no locals1916 appendReservedUleb32(code, 0); // no locals
19081917
1909 const slice_abi_size = 8;1918 const slice_abi_size = 8;
1910 const encoded_alignment = @ctz(@as(u32, 4));1919 const encoded_alignment = @ctz(@as(u32, 4));
1911 const all_tag_values_autoassigned = true;1920 if (enum_type.tag_mode == .auto) {
1912 if (all_tag_values_autoassigned) {
1913 // Then it's a direct table lookup.1921 // Then it's a direct table lookup.
1914 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.local_get));1922 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.local_get));
1915 appendReservedUleb32(code, 0);1923 appendReservedUleb32(code, 0);
...@@ -1924,6 +1932,75 @@ fn emitTagNameFunction(...@@ -1924,6 +1932,75 @@ fn emitTagNameFunction(
1924 appendReservedUleb32(code, encoded_alignment);1932 appendReservedUleb32(code, encoded_alignment);
1925 appendReservedUleb32(code, table_base_addr + table_index * 8);1933 appendReservedUleb32(code, table_base_addr + table_index * 8);
19261934
1935 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i64_store));
1936 appendReservedUleb32(code, encoded_alignment);
1937 appendReservedUleb32(code, 0);
1938 } else {
1939 const int_info = Zcu.Type.intInfo(.fromInterned(enum_type.tag_ty), zcu);
1940 const outer_block_type: std.wasm.BlockType = switch (int_info.bits) {
1941 0...32 => .i32,
1942 33...64 => .i64,
1943 else => return diags.fail("wasm linker does not yet implement @tagName for sparse enums with more than 64 bit integer tag types", .{}),
1944 };
1945
1946 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.local_get));
1947 appendReservedUleb32(code, 0);
1948
1949 // Outer block that computes table offset.
1950 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.block));
1951 code.appendAssumeCapacity(@intFromEnum(outer_block_type));
1952
1953 for (enum_type.values.get(ip), 0..) |tag_value, tag_index| {
1954 // block for this if case
1955 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.block));
1956 code.appendAssumeCapacity(@intFromEnum(std.wasm.BlockType.empty));
1957
1958 // Tag value whose name should be returned.
1959 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.local_get));
1960 appendReservedUleb32(code, 1);
1961
1962 const val: Zcu.Value = .fromInterned(tag_value);
1963 switch (outer_block_type) {
1964 .i32 => {
1965 const x: u32 = switch (int_info.signedness) {
1966 .signed => @bitCast(@as(i32, @intCast(val.toSignedInt(zcu)))),
1967 .unsigned => @intCast(val.toUnsignedInt(zcu)),
1968 };
1969 appendReservedI32Const(code, x);
1970 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i32_ne));
1971 },
1972 .i64 => {
1973 const x: u64 = switch (int_info.signedness) {
1974 .signed => @bitCast(val.toSignedInt(zcu)),
1975 .unsigned => val.toUnsignedInt(zcu),
1976 };
1977 appendReservedI64Const(code, x);
1978 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i64_ne));
1979 },
1980 else => unreachable,
1981 }
1982
1983 // if they're not equal, break out of current branch
1984 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.br_if));
1985 appendReservedUleb32(code, 0);
1986
1987 // Put the table offset of the result on the stack.
1988 appendReservedI32Const(code, @intCast(tag_index * slice_abi_size));
1989
1990 // break outside blocks
1991 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.br));
1992 appendReservedUleb32(code, 1);
1993
1994 // end the block for this case
1995 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.end));
1996 }
1997 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.@"unreachable"));
1998 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.end));
1999
2000 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i64_load));
2001 appendReservedUleb32(code, encoded_alignment);
2002 appendReservedUleb32(code, table_base_addr + table_index * 8);
2003
1927 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i64_store));2004 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i64_store));
1928 appendReservedUleb32(code, encoded_alignment);2005 appendReservedUleb32(code, encoded_alignment);
1929 appendReservedUleb32(code, 0);2006 appendReservedUleb32(code, 0);
...@@ -1939,6 +2016,12 @@ fn appendReservedI32Const(bytes: *std.ArrayListUnmanaged(u8), val: u32) void {...@@ -1939,6 +2016,12 @@ fn appendReservedI32Const(bytes: *std.ArrayListUnmanaged(u8), val: u32) void {
1939 leb.writeIleb128(bytes.fixedWriter(), @as(i32, @bitCast(val))) catch unreachable;2016 leb.writeIleb128(bytes.fixedWriter(), @as(i32, @bitCast(val))) catch unreachable;
1940}2017}
19412018
2019/// Writes an unsigned 64-bit integer as a LEB128-encoded 'i64.const' value.
2020fn appendReservedI64Const(bytes: *std.ArrayListUnmanaged(u8), val: u64) void {
2021 bytes.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i64_const));
2022 leb.writeIleb128(bytes.fixedWriter(), @as(i64, @bitCast(val))) catch unreachable;
2023}
2024
1942fn appendReservedUleb32(bytes: *std.ArrayListUnmanaged(u8), val: u32) void {2025fn appendReservedUleb32(bytes: *std.ArrayListUnmanaged(u8), val: u32) void {
1943 leb.writeUleb128(bytes.fixedWriter(), val) catch unreachable;2026 leb.writeUleb128(bytes.fixedWriter(), val) catch unreachable;
1944}2027}