diff --git a/src/Zcu.zig b/src/Zcu.zig index c100e8b5f8..8c052d2993 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -4120,3 +4120,14 @@ pub fn codegenFailTypeMsg(zcu: *Zcu, ty_index: InternPool.Index, msg: *ErrorMsg) zcu.failed_types.putAssumeCapacityNoClobber(ty_index, msg); return error.CodegenFail; } + +/// Check if nav is an alias to a function, in which case we want to lower the +/// actual nav, rather than the alias itself. +pub fn chaseNav(zcu: *const Zcu, nav: InternPool.Nav.Index) InternPool.Nav.Index { + return switch (zcu.intern_pool.indexToKey(zcu.navValue(nav).toIntern())) { + .func => |f| f.owner_nav, + .variable => |variable| variable.owner_nav, + .@"extern" => |@"extern"| @"extern".owner_nav, + else => nav, + }; +} diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index 161a4d0cbc..5990e683c9 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -146,19 +146,14 @@ const WValue = union(enum) { float32: f32, /// A constant 64bit float value float64: f64, - /// A value that represents a pointer to the data section. - memory: InternPool.Index, - /// A value that represents a parent pointer and an offset - /// from that pointer. i.e. when slicing with constant values. - memory_offset: struct { - pointer: InternPool.Index, - /// Offset will be set as addend when relocating - offset: u32, + nav_ref: struct { + nav_index: InternPool.Nav.Index, + offset: i32 = 0, + }, + uav_ref: struct { + ip_index: InternPool.Index, + offset: i32 = 0, }, - /// Represents a function pointer - /// In wasm function pointers are indexes into a function table, - /// rather than an address in the data section. - function_index: InternPool.Index, /// Offset from the bottom of the virtual stack, with the offset /// pointing to where the value lives. stack_offset: struct { @@ -752,7 +747,7 @@ fn resolveInst(func: *CodeGen, ref: Air.Inst.Ref) InnerError!WValue { const ty = func.typeOf(ref); if (!ty.hasRuntimeBitsIgnoreComptime(zcu) and !ty.isInt(zcu) and !ty.isError(zcu)) { gop.value_ptr.* = .none; - return gop.value_ptr.*; + return .none; } // When we need to pass the value by reference (such as a struct), we will @@ -762,7 +757,7 @@ fn resolveInst(func: *CodeGen, ref: Air.Inst.Ref) InnerError!WValue { // In the other cases, we will simply lower the constant to a value that fits // into a single local (such as a pointer, integer, bool, etc). const result: WValue = if (isByRef(ty, pt, func.target)) - .{ .memory = val.toIntern() } + .{ .uav_ref = .{ .ip_index = val.toIntern() } } else try func.lowerConstant(val, ty); @@ -956,6 +951,7 @@ fn addExtraAssumeCapacity(func: *CodeGen, extra: anytype) error{OutOfMemory}!u32 u32 => @field(extra, field.name), i32 => @bitCast(@field(extra, field.name)), InternPool.Index => @intFromEnum(@field(extra, field.name)), + InternPool.Nav.Index => @intFromEnum(@field(extra, field.name)), else => |field_type| @compileError("Unsupported field type " ++ @typeName(field_type)), }); } @@ -1028,17 +1024,36 @@ fn emitWValue(func: *CodeGen, value: WValue) InnerError!void { .imm128 => |val| try func.addImm128(val), .float32 => |val| try func.addInst(.{ .tag = .f32_const, .data = .{ .float32 = val } }), .float64 => |val| try func.addFloat64(val), - .memory => |ptr| try func.addInst(.{ .tag = .uav_ref, .data = .{ .ip_index = ptr } }), - .memory_offset => |mo| try func.addInst(.{ - .tag = .uav_ref_off, - .data = .{ - .payload = try func.addExtra(Mir.UavRefOff{ - .ip_index = mo.pointer, - .offset = @intCast(mo.offset), // TODO should not be an assert - }), - }, - }), - .function_index => |index| try func.addIpIndex(.function_index, index), + .nav_ref => |nav_ref| { + if (nav_ref.offset == 0) { + try func.addInst(.{ .tag = .nav_ref, .data = .{ .nav_index = nav_ref.nav_index } }); + } else { + try func.addInst(.{ + .tag = .nav_ref_off, + .data = .{ + .payload = try func.addExtra(Mir.NavRefOff{ + .nav_index = nav_ref.nav_index, + .offset = nav_ref.offset, + }), + }, + }); + } + }, + .uav_ref => |uav| { + if (uav.offset == 0) { + try func.addInst(.{ .tag = .uav_ref, .data = .{ .ip_index = uav.ip_index } }); + } else { + try func.addInst(.{ + .tag = .uav_ref_off, + .data = .{ + .payload = try func.addExtra(Mir.UavRefOff{ + .ip_index = uav.ip_index, + .offset = uav.offset, + }), + }, + }); + } + }, .stack_offset => try func.addLabel(.local_get, func.bottom_stack_value.local.value), // caller must ensure to address the offset } } @@ -1466,10 +1481,7 @@ fn lowerArg(func: *CodeGen, cc: std.builtin.CallingConvention, ty: Type, value: assert(ty_classes[0] == .direct); const scalar_type = abi.scalarType(ty, zcu); switch (value) { - .memory, - .memory_offset, - .stack_offset, - => _ = try func.load(value, scalar_type, 0), + .nav_ref, .stack_offset => _ = try func.load(value, scalar_type, 0), .dead => unreachable, else => try func.emitWValue(value), } @@ -3117,8 +3129,8 @@ fn lowerPtr(func: *CodeGen, ptr_val: InternPool.Index, prev_offset: u64) InnerEr const ptr = zcu.intern_pool.indexToKey(ptr_val).ptr; const offset: u64 = prev_offset + ptr.byte_offset; return switch (ptr.base_addr) { - .nav => |nav| return func.lowerNavRef(nav, @intCast(offset)), - .uav => |uav| return func.lowerUavRef(uav, @intCast(offset)), + .nav => |nav| return .{ .nav_ref = .{ .nav_index = zcu.chaseNav(nav), .offset = @intCast(offset) } }, + .uav => |uav| return .{ .uav_ref = .{ .ip_index = uav.val, .offset = @intCast(offset) } }, .int => return func.lowerConstant(try pt.intValue(Type.usize, offset), Type.usize), .eu_payload => return func.fail("Wasm TODO: lower error union payload pointer", .{}), .opt_payload => |opt_ptr| return func.lowerPtr(opt_ptr, offset), @@ -3162,51 +3174,6 @@ fn lowerPtr(func: *CodeGen, ptr_val: InternPool.Index, prev_offset: u64) InnerEr }; } -fn lowerUavRef( - func: *CodeGen, - uav: InternPool.Key.Ptr.BaseAddr.Uav, - offset: u32, -) InnerError!WValue { - const pt = func.pt; - const zcu = pt.zcu; - const ty = Type.fromInterned(zcu.intern_pool.typeOf(uav.val)); - - const is_fn_body = ty.zigTypeTag(zcu) == .@"fn"; - if (!is_fn_body and !ty.hasRuntimeBitsIgnoreComptime(zcu)) { - return .{ .imm32 = 0xaaaaaaaa }; - } - - return if (is_fn_body) .{ - .function_index = uav.val, - } else if (offset == 0) .{ - .memory = uav.val, - } else .{ .memory_offset = .{ - .pointer = uav.val, - .offset = offset, - } }; -} - -fn lowerNavRef(func: *CodeGen, nav_index: InternPool.Nav.Index, offset: u32) InnerError!WValue { - const pt = func.pt; - const zcu = pt.zcu; - const ip = &zcu.intern_pool; - - const nav_ty = ip.getNav(nav_index).typeOf(ip); - if (!ip.isFunctionType(nav_ty) and !Type.fromInterned(nav_ty).hasRuntimeBitsIgnoreComptime(zcu)) { - return .{ .imm32 = 0xaaaaaaaa }; - } - - const atom_index = try func.wasm.getOrCreateAtomForNav(pt, nav_index); - const atom = func.wasm.getAtom(atom_index); - - const target_sym_index = @intFromEnum(atom.sym_index); - if (ip.isFunctionType(nav_ty)) { - return .{ .function_index = target_sym_index }; - } else if (offset == 0) { - return .{ .memory = target_sym_index }; - } else return .{ .memory_offset = .{ .pointer = target_sym_index, .offset = offset } }; -} - /// Asserts that `isByRef` returns `false` for `ty`. fn lowerConstant(func: *CodeGen, val: Value, ty: Type) InnerError!WValue { const pt = func.pt; @@ -3307,7 +3274,7 @@ fn lowerConstant(func: *CodeGen, val: Value, ty: Type) InnerError!WValue { .f64 => |f64_val| return .{ .float64 = f64_val }, else => unreachable, }, - .slice => return .{ .memory = val.toIntern() }, + .slice => unreachable, // isByRef == true .ptr => return func.lowerPtr(val.toIntern(), 0), .opt => if (ty.optionalReprIsPayload(zcu)) { const pl_ty = ty.optionalChild(zcu); diff --git a/src/arch/wasm/Emit.zig b/src/arch/wasm/Emit.zig index 87610f8edd..46ae20ccbf 100644 --- a/src/arch/wasm/Emit.zig +++ b/src/arch/wasm/Emit.zig @@ -33,6 +33,9 @@ pub fn lowerToCode(emit: *Emit) Error!void { var inst: u32 = 0; loop: switch (tags[inst]) { + .dbg_epilogue_begin => { + return; + }, .block, .loop => { const block_type = datas[inst].block_type; try code.ensureUnusedCapacity(gpa, 2); @@ -42,16 +45,23 @@ pub fn lowerToCode(emit: *Emit) Error!void { inst += 1; continue :loop tags[inst]; }, - .uav_ref => { try uavRefOff(wasm, code, .{ .ip_index = datas[inst].ip_index, .offset = 0 }); - inst += 1; continue :loop tags[inst]; }, .uav_ref_off => { try uavRefOff(wasm, code, mir.extraData(Mir.UavRefOff, datas[inst].payload).data); - + inst += 1; + continue :loop tags[inst]; + }, + .nav_ref => { + try navRefOff(wasm, code, .{ .ip_index = datas[inst].ip_index, .offset = 0 }); + inst += 1; + continue :loop tags[inst]; + }, + .nav_ref_off => { + try navRefOff(wasm, code, mir.extraData(Mir.NavRefOff, datas[inst].payload).data); inst += 1; continue :loop tags[inst]; }, @@ -60,10 +70,6 @@ pub fn lowerToCode(emit: *Emit) Error!void { inst += 1; continue :loop tags[inst]; }, - .dbg_epilogue_begin => { - return; - }, - .br_if, .br, .memory_grow, .memory_size => { try code.ensureUnusedCapacity(gpa, 11); code.appendAssumeCapacity(@intFromEnum(tags[inst])); @@ -431,7 +437,7 @@ pub fn lowerToCode(emit: *Emit) Error!void { _ => unreachable, } - unreachable; + comptime unreachable; }, .simd_prefix => { try code.ensureUnusedCapacity(gpa, 6 + 20); @@ -487,7 +493,7 @@ pub fn lowerToCode(emit: *Emit) Error!void { }, _ => unreachable, } - unreachable; + comptime unreachable; }, .atomics_prefix => { try code.ensureUnusedCapacity(gpa, 6 + 20); @@ -576,13 +582,13 @@ pub fn lowerToCode(emit: *Emit) Error!void { continue :loop tags[inst]; }, } - unreachable; + comptime unreachable; }, } - unreachable; + comptime unreachable; } -/// Assert 20 unused capacity. +/// Asserts 20 unused capacity. fn encodeMemArg(code: *std.ArrayListUnmanaged(u8), mem_arg: Mir.MemArg) void { assert(code.unusedCapacitySlice().len >= 20); // Wasm encodes alignment as power of 2, rather than natural alignment. @@ -619,3 +625,48 @@ fn uavRefOff(wasm: *link.File.Wasm, code: *std.ArrayListUnmanaged(u8), data: Mir const addr: i64 = try wasm.uavAddr(data.ip_index); leb.writeUleb128(code.fixedWriter(), addr + data.offset) catch unreachable; } + +fn navRefOff(wasm: *link.File.Wasm, code: *std.ArrayListUnmanaged(u8), data: Mir.NavRefOff) !void { + const comp = wasm.base.comp; + const zcu = comp.zcu.?; + const ip = &zcu.intern_pool; + const gpa = comp.gpa; + const is_obj = comp.config.output_mode == .Obj; + const target = &comp.root_mod.resolved_target.result; + const nav_ty = ip.getNav(data.nav_index).typeOf(ip); + + try code.ensureUnusedCapacity(gpa, 11); + + if (ip.isFunctionType(nav_ty)) { + code.appendAssumeCapacity(std.wasm.Opcode.i32_const); + assert(data.offset == 0); + if (is_obj) { + try wasm.out_relocs.append(gpa, .{ + .offset = @intCast(code.items.len), + .index = try wasm.navSymbolIndex(data.nav_index), + .tag = .TABLE_INDEX_SLEB, + .addend = data.offset, + }); + code.appendNTimesAssumeCapacity(0, 5); + } else { + const addr: i64 = try wasm.navAddr(data.nav_index); + leb.writeUleb128(code.fixedWriter(), addr + data.offset) catch unreachable; + } + } else { + const is_wasm32 = target.cpu.arch == .wasm32; + const opcode: std.wasm.Opcode = if (is_wasm32) .i32_const else .i64_const; + code.appendAssumeCapacity(@intFromEnum(opcode)); + if (is_obj) { + try wasm.out_relocs.append(gpa, .{ + .offset = @intCast(code.items.len), + .index = try wasm.navSymbolIndex(data.nav_index), + .tag = if (is_wasm32) .MEMORY_ADDR_LEB else .MEMORY_ADDR_LEB64, + .addend = data.offset, + }); + code.appendNTimesAssumeCapacity(0, if (is_wasm32) 5 else 10); + } else { + const addr: i64 = try wasm.navAddr(data.nav_index); + leb.writeUleb128(code.fixedWriter(), addr + data.offset) catch unreachable; + } + } +} diff --git a/src/arch/wasm/Mir.zig b/src/arch/wasm/Mir.zig index e699879ee7..9009056e2a 100644 --- a/src/arch/wasm/Mir.zig +++ b/src/arch/wasm/Mir.zig @@ -32,8 +32,12 @@ pub const Inst = struct { /// Some tags match wasm opcode values to facilitate trivial lowering. pub const Tag = enum(u8) { - /// Uses `nop` + /// Uses `tag`. @"unreachable" = 0x00, + /// Emits epilogue begin debug information. Marks the end of the function. + /// + /// Uses `tag` (no additional data). + dbg_epilogue_begin, /// Creates a new block that can be jump from. /// /// Type of the block is given in data `block_type` @@ -46,34 +50,51 @@ pub const Inst = struct { /// memory address of an unnamed constant. When emitting an object /// file, this adds a relocation. /// - /// Data is `ip_index`. + /// This may not refer to a function. + /// + /// Uses `ip_index`. uav_ref, /// Lowers to an i32_const (wasm32) or i64_const (wasm64) which is the /// memory address of an unnamed constant, offset by an integer value. /// When emitting an object file, this adds a relocation. /// - /// Data is `payload` pointing to a `UavRefOff`. + /// This may not refer to a function. + /// + /// Uses `payload` pointing to a `UavRefOff`. uav_ref_off, + /// Lowers to an i32_const (wasm32) or i64_const (wasm64) which is the + /// memory address of a named constant. + /// + /// When this refers to a function, this always lowers to an i32_const + /// which is the function index. When emitting an object file, this + /// adds a `Wasm.Relocation.Tag.TABLE_INDEX_SLEB` relocation. + /// + /// Uses `nav_index`. + nav_ref, + /// Lowers to an i32_const (wasm32) or i64_const (wasm64) which is the + /// memory address of named constant, offset by an integer value. + /// When emitting an object file, this adds a relocation. + /// + /// This may not refer to a function. + /// + /// Uses `payload` pointing to a `NavRefOff`. + nav_ref_off, /// Inserts debug information about the current line and column /// of the source code /// /// Uses `payload` of which the payload type is `DbgLineColumn` - dbg_line = 0x06, - /// Emits epilogue begin debug information. Marks the end of the function. - /// - /// Uses `nop` - dbg_epilogue_begin = 0x07, + dbg_line, /// Represents the end of a function body or an initialization expression /// - /// Payload is `nop` + /// Uses `tag` (no additional data). end = 0x0B, /// Breaks from the current block to a label /// - /// Data is `label` where index represents the label to jump to + /// Uses `label` where index represents the label to jump to br = 0x0C, /// Breaks from the current block if the stack value is non-zero /// - /// Data is `label` where index represents the label to jump to + /// Uses `label` where index represents the label to jump to br_if = 0x0D, /// Jump table that takes the stack value as an index where each value /// represents the label to jump to. @@ -82,7 +103,7 @@ pub const Inst = struct { br_table = 0x0E, /// Returns from the function /// - /// Uses `nop` + /// Uses `tag`. @"return" = 0x0F, /// Calls a function using `nav_index`. call_nav, @@ -98,10 +119,6 @@ pub const Inst = struct { /// The function is the auto-generated tag name function for the type /// provided in `ip_index`. call_tag_name, - /// Lowers to an i32_const containing the index of a function. - /// When emitting an object file, this adds a relocation. - /// Uses `ip_index`. - function_index, /// Pops three values from the stack and pushes /// the first or second value dependent on the third value. @@ -663,6 +680,11 @@ pub const UavRefOff = struct { offset: i32, }; +pub const NavRefOff = struct { + nav_index: InternPool.Nav.Index, + offset: i32, +}; + /// Maps a source line with wasm bytecode pub const DbgLineColumn = struct { line: u32, diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index b94ccfd1cc..7d078e40b1 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -1508,6 +1508,10 @@ pub fn updateFunc(wasm: *Wasm, pt: Zcu.PerThread, func_index: InternPool.Index, dev.check(.wasm_backend); + // This converts AIR to MIR but does not yet lower to wasm code. + // That lowering happens during `flush`, after garbage collection, which + // can affect function and global indexes, which affects the LEB integer + // encoding, which affects the output binary size. try wasm.zcu_funcs.put(pt.zcu.gpa, func_index, .{ .function = try CodeGen.function(wasm, pt, func_index, air, liveness), }); @@ -1729,7 +1733,7 @@ pub fn prelink(wasm: *Wasm, prog_node: std.Progress.Node) link.File.FlushError!v continue; } } - wasm.functions_len = @intCast(wasm.functions.items.len); + wasm.functions_len = @intCast(wasm.functions.entries.len); wasm.function_imports_init_keys = try gpa.dupe(String, wasm.function_imports.keys()); wasm.function_imports_init_vals = try gpa.dupe(FunctionImportId, wasm.function_imports.vals()); wasm.function_exports_len = @intCast(wasm.function_exports.items.len);