| ... | ... | @@ -26,7 +26,7 @@ data_segments: std.AutoArrayHashMapUnmanaged(Wasm.DataSegmentId, u32) = .empty, |
| 26 | 26 | /// Each time a `data_segment` offset equals zero it indicates a new group, and |
| 27 | 27 | /// the next element in this array will contain the total merged segment size. |
| 28 | 28 | /// Value is the virtual memory address of the end of the segment. |
| 29 | | data_segment_groups: std.ArrayListUnmanaged(u32) = .empty, |
| 29 | data_segment_groups: std.ArrayListUnmanaged(DataSegmentGroup) = .empty, |
| 30 | 30 | |
| 31 | 31 | binary_bytes: std.ArrayListUnmanaged(u8) = .empty, |
| 32 | 32 | missing_exports: std.AutoArrayHashMapUnmanaged(String, void) = .empty, |
| ... | ... | @@ -37,6 +37,11 @@ data_imports: std.AutoArrayHashMapUnmanaged(String, Wasm.DataImportId) = .empty, |
| 37 | 37 | /// For debug purposes only. |
| 38 | 38 | memory_layout_finished: bool = false, |
| 39 | 39 | |
| 40 | const DataSegmentGroup = struct { |
| 41 | first_segment: Wasm.DataSegmentId, |
| 42 | end_addr: u32, |
| 43 | }; |
| 44 | |
| 40 | 45 | pub fn clear(f: *Flush) void { |
| 41 | 46 | f.data_segments.clearRetainingCapacity(); |
| 42 | 47 | f.data_segment_groups.clearRetainingCapacity(); |
| ... | ... | @@ -280,15 +285,6 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void { |
| 280 | 285 | // Always place the stack at the start by default unless the user specified the global-base flag. |
| 281 | 286 | const place_stack_first, var memory_ptr: u64 = if (wasm.global_base) |base| .{ false, base } else .{ true, 0 }; |
| 282 | 287 | |
| 283 | | const VirtualAddrs = struct { |
| 284 | | stack_pointer: u32, |
| 285 | | heap_base: u32, |
| 286 | | heap_end: u32, |
| 287 | | tls_base: ?u32, |
| 288 | | tls_align: Alignment, |
| 289 | | tls_size: ?u32, |
| 290 | | init_memory_flag: ?u32, |
| 291 | | }; |
| 292 | 288 | var virtual_addrs: VirtualAddrs = .{ |
| 293 | 289 | .stack_pointer = undefined, |
| 294 | 290 | .heap_base = undefined, |
| ... | ... | @@ -309,9 +305,10 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void { |
| 309 | 305 | const segment_vaddrs = f.data_segments.values(); |
| 310 | 306 | assert(f.data_segment_groups.items.len == 0); |
| 311 | 307 | const data_vaddr: u32 = @intCast(memory_ptr); |
| 312 | | { |
| 308 | if (segment_ids.len > 0) { |
| 313 | 309 | var seen_tls: enum { before, during, after } = .before; |
| 314 | 310 | var category: Wasm.DataSegmentId.Category = undefined; |
| 311 | var first_segment: Wasm.DataSegmentId = segment_ids[0]; |
| 315 | 312 | for (segment_ids, segment_vaddrs, 0..) |segment_id, *segment_vaddr, i| { |
| 316 | 313 | const alignment = segment_id.alignment(wasm); |
| 317 | 314 | category = segment_id.category(wasm); |
| ... | ... | @@ -338,14 +335,21 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void { |
| 338 | 335 | }; |
| 339 | 336 | if (want_new_segment) { |
| 340 | 337 | log.debug("new segment at 0x{x} {} {s} {}", .{ start_addr, segment_id, segment_id.name(wasm), category }); |
| 341 | | try f.data_segment_groups.append(gpa, @intCast(memory_ptr)); |
| 338 | try f.data_segment_groups.append(gpa, .{ |
| 339 | .end_addr = @intCast(memory_ptr), |
| 340 | .first_segment = first_segment, |
| 341 | }); |
| 342 | first_segment = segment_id; |
| 342 | 343 | } |
| 343 | 344 | |
| 344 | 345 | const size = segment_id.size(wasm); |
| 345 | 346 | segment_vaddr.* = @intCast(start_addr); |
| 346 | 347 | memory_ptr = start_addr + size; |
| 347 | 348 | } |
| 348 | | if (category != .zero) try f.data_segment_groups.append(gpa, @intCast(memory_ptr)); |
| 349 | if (category != .zero) try f.data_segment_groups.append(gpa, .{ |
| 350 | .first_segment = first_segment, |
| 351 | .end_addr = @intCast(memory_ptr), |
| 352 | }); |
| 349 | 353 | } |
| 350 | 354 | |
| 351 | 355 | if (shared_memory and wasm.any_passive_inits) { |
| ... | ... | @@ -567,7 +571,7 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void { |
| 567 | 571 | binary_bytes.appendAssumeCapacity(@intFromEnum(std.wasm.Valtype.i32)); |
| 568 | 572 | binary_bytes.appendAssumeCapacity(1); // mutable |
| 569 | 573 | binary_bytes.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i32_const)); |
| 570 | | leb.writeUleb128(binary_bytes.fixedWriter(), virtual_addrs.stack_pointer) catch unreachable; |
| 574 | appendReservedUleb32(binary_bytes, virtual_addrs.stack_pointer); |
| 571 | 575 | binary_bytes.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.end)); |
| 572 | 576 | }, |
| 573 | 577 | .__tls_align => @panic("TODO"), |
| ... | ... | @@ -683,7 +687,11 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void { |
| 683 | 687 | defer replaceSize(binary_bytes, code_start); |
| 684 | 688 | try emitCallCtorsFunction(wasm, binary_bytes); |
| 685 | 689 | }, |
| 686 | | .__wasm_init_memory => @panic("TODO lower __wasm_init_memory "), |
| 690 | .__wasm_init_memory => { |
| 691 | const code_start = try reserveSize(gpa, binary_bytes); |
| 692 | defer replaceSize(binary_bytes, code_start); |
| 693 | try emitInitMemoryFunction(wasm, binary_bytes, &virtual_addrs); |
| 694 | }, |
| 687 | 695 | .__wasm_init_tls => @panic("TODO lower __wasm_init_tls "), |
| 688 | 696 | .object_function => |i| { |
| 689 | 697 | const ptr = i.ptr(wasm); |
| ... | ... | @@ -736,7 +744,7 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void { |
| 736 | 744 | var group_index: u32 = 0; |
| 737 | 745 | var segment_offset: u32 = 0; |
| 738 | 746 | var group_start_addr: u32 = data_vaddr; |
| 739 | | var group_end_addr = f.data_segment_groups.items[group_index]; |
| 747 | var group_end_addr = f.data_segment_groups.items[group_index].end_addr; |
| 740 | 748 | for (segment_ids, segment_vaddrs) |segment_id, segment_vaddr| { |
| 741 | 749 | if (segment_vaddr >= group_end_addr) { |
| 742 | 750 | try binary_bytes.appendNTimes(gpa, 0, group_end_addr - group_start_addr - segment_offset); |
| ... | ... | @@ -746,7 +754,7 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void { |
| 746 | 754 | break; |
| 747 | 755 | } |
| 748 | 756 | group_start_addr = group_end_addr; |
| 749 | | group_end_addr = f.data_segment_groups.items[group_index]; |
| 757 | group_end_addr = f.data_segment_groups.items[group_index].end_addr; |
| 750 | 758 | segment_offset = 0; |
| 751 | 759 | } |
| 752 | 760 | if (segment_offset == 0) { |
| ... | ... | @@ -865,6 +873,16 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void { |
| 865 | 873 | try file.setEndPos(binary_bytes.items.len); |
| 866 | 874 | } |
| 867 | 875 | |
| 876 | const VirtualAddrs = struct { |
| 877 | stack_pointer: u32, |
| 878 | heap_base: u32, |
| 879 | heap_end: u32, |
| 880 | tls_base: ?u32, |
| 881 | tls_align: Alignment, |
| 882 | tls_size: ?u32, |
| 883 | init_memory_flag: ?u32, |
| 884 | }; |
| 885 | |
| 868 | 886 | fn emitNameSection( |
| 869 | 887 | wasm: *Wasm, |
| 870 | 888 | data_segments: *const std.AutoArrayHashMapUnmanaged(Wasm.DataSegmentId, u32), |
| ... | ... | @@ -1575,7 +1593,7 @@ fn emitCallCtorsFunction(wasm: *const Wasm, binary_bytes: *std.ArrayListUnmanage |
| 1575 | 1593 | const gpa = wasm.base.comp.gpa; |
| 1576 | 1594 | |
| 1577 | 1595 | try binary_bytes.ensureUnusedCapacity(gpa, 5 + 1); |
| 1578 | | leb.writeUleb128(binary_bytes.fixedWriter(), @as(u32, 0)) catch unreachable; // no locals |
| 1596 | appendReservedUleb32(binary_bytes, 0); // no locals |
| 1579 | 1597 | |
| 1580 | 1598 | for (wasm.object_init_funcs.items) |init_func| { |
| 1581 | 1599 | const func = init_func.function_index.ptr(wasm); |
| ... | ... | @@ -1586,7 +1604,7 @@ fn emitCallCtorsFunction(wasm: *const Wasm, binary_bytes: *std.ArrayListUnmanage |
| 1586 | 1604 | try binary_bytes.ensureUnusedCapacity(gpa, 1 + 5 + n_returns + 1); |
| 1587 | 1605 | const call_index: Wasm.OutputFunctionIndex = .fromObjectFunction(wasm, init_func.function_index); |
| 1588 | 1606 | binary_bytes.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.call)); |
| 1589 | | leb.writeUleb128(binary_bytes.fixedWriter(), @intFromEnum(call_index)) catch unreachable; |
| 1607 | appendReservedUleb32(binary_bytes, @intFromEnum(call_index)); |
| 1590 | 1608 | |
| 1591 | 1609 | // drop all returned values from the stack as __wasm_call_ctors has no return value |
| 1592 | 1610 | binary_bytes.appendNTimesAssumeCapacity(@intFromEnum(std.wasm.Opcode.drop), n_returns); |
| ... | ... | @@ -1594,3 +1612,163 @@ fn emitCallCtorsFunction(wasm: *const Wasm, binary_bytes: *std.ArrayListUnmanage |
| 1594 | 1612 | |
| 1595 | 1613 | binary_bytes.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.end)); // end function body |
| 1596 | 1614 | } |
| 1615 | |
| 1616 | fn emitInitMemoryFunction( |
| 1617 | wasm: *const Wasm, |
| 1618 | binary_bytes: *std.ArrayListUnmanaged(u8), |
| 1619 | virtual_addrs: *const VirtualAddrs, |
| 1620 | ) Allocator.Error!void { |
| 1621 | const comp = wasm.base.comp; |
| 1622 | const gpa = comp.gpa; |
| 1623 | const shared_memory = comp.config.shared_memory; |
| 1624 | |
| 1625 | // Passive segments are used to avoid memory being reinitialized on each |
| 1626 | // thread's instantiation. These passive segments are initialized and |
| 1627 | // dropped in __wasm_init_memory, which is registered as the start function |
| 1628 | // We also initialize bss segments (using memory.fill) as part of this |
| 1629 | // function. |
| 1630 | assert(wasm.any_passive_inits); |
| 1631 | |
| 1632 | try binary_bytes.ensureUnusedCapacity(gpa, 5 + 1); |
| 1633 | appendReservedUleb32(binary_bytes, 0); // no locals |
| 1634 | |
| 1635 | if (virtual_addrs.init_memory_flag) |flag_address| { |
| 1636 | assert(shared_memory); |
| 1637 | try binary_bytes.ensureUnusedCapacity(gpa, 2 * 3 + 6 * 3 + 1 + 6 * 3 + 1 + 5 * 4 + 1 + 1); |
| 1638 | // destination blocks |
| 1639 | // based on values we jump to corresponding label |
| 1640 | binary_bytes.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.block)); // $drop |
| 1641 | binary_bytes.appendAssumeCapacity(std.wasm.block_empty); // block type |
| 1642 | |
| 1643 | binary_bytes.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.block)); // $wait |
| 1644 | binary_bytes.appendAssumeCapacity(std.wasm.block_empty); // block type |
| 1645 | |
| 1646 | binary_bytes.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.block)); // $init |
| 1647 | binary_bytes.appendAssumeCapacity(std.wasm.block_empty); // block type |
| 1648 | |
| 1649 | // atomically check |
| 1650 | appendReservedI32Const(binary_bytes, flag_address); |
| 1651 | appendReservedI32Const(binary_bytes, 0); |
| 1652 | appendReservedI32Const(binary_bytes, 1); |
| 1653 | binary_bytes.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.atomics_prefix)); |
| 1654 | appendReservedUleb32(binary_bytes, @intFromEnum(std.wasm.AtomicsOpcode.i32_atomic_rmw_cmpxchg)); |
| 1655 | appendReservedUleb32(binary_bytes, 2); // alignment |
| 1656 | appendReservedUleb32(binary_bytes, 0); // offset |
| 1657 | |
| 1658 | // based on the value from the atomic check, jump to the label. |
| 1659 | binary_bytes.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.br_table)); |
| 1660 | appendReservedUleb32(binary_bytes, 2); // length of the table (we have 3 blocks but because of the mandatory default the length is 2). |
| 1661 | appendReservedUleb32(binary_bytes, 0); // $init |
| 1662 | appendReservedUleb32(binary_bytes, 1); // $wait |
| 1663 | appendReservedUleb32(binary_bytes, 2); // $drop |
| 1664 | binary_bytes.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.end)); |
| 1665 | } |
| 1666 | |
| 1667 | const segment_groups = wasm.flush_buffer.data_segment_groups.items; |
| 1668 | var prev_end: u32 = 0; |
| 1669 | for (segment_groups, 0..) |group, segment_index| { |
| 1670 | defer prev_end = group.end_addr; |
| 1671 | const segment = group.first_segment; |
| 1672 | if (!segment.isPassive(wasm)) continue; |
| 1673 | |
| 1674 | const start_addr: u32 = @intCast(segment.alignment(wasm).forward(prev_end)); |
| 1675 | const segment_size: u32 = group.end_addr - start_addr; |
| 1676 | |
| 1677 | try binary_bytes.ensureUnusedCapacity(gpa, 6 + 6 + 1 + 5 + 6 + 6 + 1 + 6 * 2 + 1 + 1); |
| 1678 | |
| 1679 | // For passive BSS segments we can simply issue a memory.fill(0). For |
| 1680 | // non-BSS segments we do a memory.init. Both instructions take as |
| 1681 | // their first argument the destination address. |
| 1682 | appendReservedI32Const(binary_bytes, start_addr); |
| 1683 | |
| 1684 | if (shared_memory and segment.isTls(wasm)) { |
| 1685 | // When we initialize the TLS segment we also set the `__tls_base` |
| 1686 | // global. This allows the runtime to use this static copy of the |
| 1687 | // TLS data for the first/main thread. |
| 1688 | appendReservedI32Const(binary_bytes, start_addr); |
| 1689 | binary_bytes.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.global_set)); |
| 1690 | appendReservedUleb32(binary_bytes, virtual_addrs.tls_base.?); |
| 1691 | } |
| 1692 | |
| 1693 | appendReservedI32Const(binary_bytes, 0); |
| 1694 | appendReservedI32Const(binary_bytes, segment_size); |
| 1695 | binary_bytes.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.misc_prefix)); |
| 1696 | if (segment.isBss(wasm)) { |
| 1697 | // fill bss segment with zeroes |
| 1698 | appendReservedUleb32(binary_bytes, @intFromEnum(std.wasm.MiscOpcode.memory_fill)); |
| 1699 | } else { |
| 1700 | // initialize the segment |
| 1701 | appendReservedUleb32(binary_bytes, @intFromEnum(std.wasm.MiscOpcode.memory_init)); |
| 1702 | appendReservedUleb32(binary_bytes, @intCast(segment_index)); |
| 1703 | } |
| 1704 | binary_bytes.appendAssumeCapacity(0); // memory index immediate |
| 1705 | } |
| 1706 | |
| 1707 | if (virtual_addrs.init_memory_flag) |flag_address| { |
| 1708 | assert(shared_memory); |
| 1709 | try binary_bytes.ensureUnusedCapacity(gpa, 6 + 6 + 1 + 3 * 5 + 6 + 1 + 5 + 1 + 3 * 5 + 1 + 1 + 5 + 1 + 6 * 2 + 1 + 5 + 1 + 3 * 5 + 1 + 1 + 1); |
| 1710 | // we set the init memory flag to value '2' |
| 1711 | appendReservedI32Const(binary_bytes, flag_address); |
| 1712 | appendReservedI32Const(binary_bytes, 2); |
| 1713 | binary_bytes.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.atomics_prefix)); |
| 1714 | appendReservedUleb32(binary_bytes, @intFromEnum(std.wasm.AtomicsOpcode.i32_atomic_store)); |
| 1715 | appendReservedUleb32(binary_bytes, @as(u32, 2)); // alignment |
| 1716 | appendReservedUleb32(binary_bytes, @as(u32, 0)); // offset |
| 1717 | |
| 1718 | // notify any waiters for segment initialization completion |
| 1719 | appendReservedI32Const(binary_bytes, flag_address); |
| 1720 | binary_bytes.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i32_const)); |
| 1721 | leb.writeIleb128(binary_bytes.fixedWriter(), @as(i32, -1)) catch unreachable; // number of waiters |
| 1722 | binary_bytes.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.atomics_prefix)); |
| 1723 | appendReservedUleb32(binary_bytes, @intFromEnum(std.wasm.AtomicsOpcode.memory_atomic_notify)); |
| 1724 | appendReservedUleb32(binary_bytes, @as(u32, 2)); // alignment |
| 1725 | appendReservedUleb32(binary_bytes, @as(u32, 0)); // offset |
| 1726 | binary_bytes.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.drop)); |
| 1727 | |
| 1728 | // branch and drop segments |
| 1729 | binary_bytes.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.br)); |
| 1730 | appendReservedUleb32(binary_bytes, @as(u32, 1)); |
| 1731 | |
| 1732 | // wait for thread to initialize memory segments |
| 1733 | binary_bytes.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.end)); // end $wait |
| 1734 | appendReservedI32Const(binary_bytes, flag_address); |
| 1735 | appendReservedI32Const(binary_bytes, 1); // expected flag value |
| 1736 | binary_bytes.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i64_const)); |
| 1737 | leb.writeIleb128(binary_bytes.fixedWriter(), @as(i64, -1)) catch unreachable; // timeout |
| 1738 | binary_bytes.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.atomics_prefix)); |
| 1739 | appendReservedUleb32(binary_bytes, @intFromEnum(std.wasm.AtomicsOpcode.memory_atomic_wait32)); |
| 1740 | appendReservedUleb32(binary_bytes, @as(u32, 2)); // alignment |
| 1741 | appendReservedUleb32(binary_bytes, @as(u32, 0)); // offset |
| 1742 | binary_bytes.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.drop)); |
| 1743 | |
| 1744 | binary_bytes.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.end)); // end $drop |
| 1745 | } |
| 1746 | |
| 1747 | for (segment_groups, 0..) |group, segment_index| { |
| 1748 | const segment = group.first_segment; |
| 1749 | if (!segment.isPassive(wasm)) continue; |
| 1750 | if (segment.isBss(wasm)) continue; |
| 1751 | // The TLS region should not be dropped since its is needed |
| 1752 | // during the initialization of each thread (__wasm_init_tls). |
| 1753 | if (shared_memory and segment.isTls(wasm)) continue; |
| 1754 | |
| 1755 | try binary_bytes.ensureUnusedCapacity(gpa, 1 + 5 + 5 + 1); |
| 1756 | |
| 1757 | binary_bytes.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.misc_prefix)); |
| 1758 | appendReservedUleb32(binary_bytes, @intFromEnum(std.wasm.MiscOpcode.data_drop)); |
| 1759 | appendReservedUleb32(binary_bytes, @intCast(segment_index)); |
| 1760 | } |
| 1761 | |
| 1762 | // End of the function body |
| 1763 | binary_bytes.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.end)); |
| 1764 | } |
| 1765 | |
| 1766 | /// Writes an unsigned 32-bit integer as a LEB128-encoded 'i32.const' value. |
| 1767 | fn appendReservedI32Const(bytes: *std.ArrayListUnmanaged(u8), val: u32) void { |
| 1768 | bytes.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i32_const)); |
| 1769 | leb.writeIleb128(bytes.fixedWriter(), @as(i32, @bitCast(val))) catch unreachable; |
| 1770 | } |
| 1771 | |
| 1772 | fn appendReservedUleb32(bytes: *std.ArrayListUnmanaged(u8), val: u32) void { |
| 1773 | leb.writeUleb128(bytes.fixedWriter(), val) catch unreachable; |
| 1774 | } |