| ... | ... | @@ -560,6 +560,9 @@ mir_extra: std.ArrayListUnmanaged(u32) = .{}, |
| 560 | 560 | /// When a function is executing, we store the the current stack pointer's value within this local. |
| 561 | 561 | /// This value is then used to restore the stack pointer to the original value at the return of the function. |
| 562 | 562 | initial_stack_value: WValue = .none, |
| 563 | /// The current stack pointer substracted with the stack size. From this value, we will calculate |
| 564 | /// all offsets of the stack values. |
| 565 | bottom_stack_value: WValue = .none, |
| 563 | 566 | /// Arguments of this function declaration |
| 564 | 567 | /// This will be set after `resolveCallingConventionValues` |
| 565 | 568 | args: []WValue = &.{}, |
| ... | ... | @@ -567,6 +570,14 @@ args: []WValue = &.{}, |
| 567 | 570 | /// When it returns a pointer to the stack, the `.local` tag will be active and must be populated |
| 568 | 571 | /// before this function returns its execution to the caller. |
| 569 | 572 | return_value: WValue = .none, |
| 573 | /// The size of the stack this function occupies. In the function prologue |
| 574 | /// we will move the stack pointer by this number, forward aligned with the `stack_alignment`. |
| 575 | stack_size: u32 = 0, |
| 576 | /// The stack alignment, which is 16 bytes by default. This is specified by the |
| 577 | /// tool-conventions: https://github.com/WebAssembly/tool-conventions/blob/main/BasicCABI.md |
| 578 | /// and also what the llvm backend will emit. |
| 579 | /// However, local variables or the usage of `@setAlignStack` can overwrite this default. |
| 580 | stack_alignment: u32 = 16, |
| 570 | 581 | |
| 571 | 582 | const InnerError = error{ |
| 572 | 583 | OutOfMemory, |
| ... | ... | @@ -654,13 +665,6 @@ fn addInst(self: *Self, inst: Mir.Inst) error{OutOfMemory}!void { |
| 654 | 665 | try self.mir_instructions.append(self.gpa, inst); |
| 655 | 666 | } |
| 656 | 667 | |
| 657 | | /// Inserts a Mir instruction at the given `offset`. |
| 658 | | /// Asserts offset is within bound. |
| 659 | | fn addInstAt(self: *Self, offset: usize, inst: Mir.Inst) error{OutOfMemory}!void { |
| 660 | | try self.mir_instructions.ensureUnusedCapacity(self.gpa, 1); |
| 661 | | self.mir_instructions.insertAssumeCapacity(offset, inst); |
| 662 | | } |
| 663 | | |
| 664 | 668 | fn addTag(self: *Self, tag: Mir.Inst.Tag) error{OutOfMemory}!void { |
| 665 | 669 | try self.addInst(.{ .tag = tag, .data = .{ .tag = {} } }); |
| 666 | 670 | } |
| ... | ... | @@ -845,10 +849,43 @@ pub fn genFunc(self: *Self) InnerError!void { |
| 845 | 849 | try self.addTag(.@"unreachable"); |
| 846 | 850 | } |
| 847 | 851 | } |
| 848 | | |
| 849 | 852 | // End of function body |
| 850 | 853 | try self.addTag(.end); |
| 851 | 854 | |
| 855 | // check if we have to initialize and allocate anything into the stack frame. |
| 856 | // If so, create enough stack space and insert the instructions at the front of the list. |
| 857 | if (self.stack_size > 0) { |
| 858 | var prologue = std.ArrayList(Mir.Inst).init(self.gpa); |
| 859 | defer prologue.deinit(); |
| 860 | |
| 861 | // load stack pointer |
| 862 | try prologue.append(.{ .tag = .global_get, .data = .{ .label = 0 } }); |
| 863 | // store stack pointer so we can restore it when we return from the function |
| 864 | try prologue.append(.{ .tag = .local_tee, .data = .{ .label = self.initial_stack_value.local } }); |
| 865 | // get the total stack size |
| 866 | const aligned_stack = std.mem.alignForwardGeneric(u32, self.stack_size, self.stack_alignment); |
| 867 | try prologue.append(.{ .tag = .i32_const, .data = .{ .imm32 = @intCast(i32, aligned_stack) } }); |
| 868 | // substract it from the current stack pointer |
| 869 | try prologue.append(.{ .tag = .i32_sub, .data = .{ .tag = {} } }); |
| 870 | // Get negative stack aligment |
| 871 | try prologue.append(.{ .tag = .i32_const, .data = .{ .imm32 = @intCast(i32, self.stack_alignment) * -1 } }); |
| 872 | // Bit and the value to get the new stack pointer to ensure the pointers are aligned with the abi alignment |
| 873 | try prologue.append(.{ .tag = .i32_and, .data = .{ .tag = {} } }); |
| 874 | // store the current stack pointer as the bottom, which will be used to calculate all stack pointer offsets |
| 875 | try prologue.append(.{ .tag = .local_tee, .data = .{ .label = self.bottom_stack_value.local } }); |
| 876 | // Store the current stack pointer value into the global stack pointer so other function calls will |
| 877 | // start from this value instead and not overwrite the current stack. |
| 878 | try prologue.append(.{ .tag = .global_set, .data = .{ .label = 0 } }); |
| 879 | |
| 880 | // reserve space and insert all prologue instructions at the front of the instruction list |
| 881 | // We insert them in reserve order as there is no insertSlice in multiArrayList. |
| 882 | try self.mir_instructions.ensureUnusedCapacity(self.gpa, prologue.items.len); |
| 883 | for (prologue.items) |_, index| { |
| 884 | const inst = prologue.items[prologue.items.len - 1 - index]; |
| 885 | self.mir_instructions.insertAssumeCapacity(0, inst); |
| 886 | } |
| 887 | } |
| 888 | |
| 852 | 889 | var mir: Mir = .{ |
| 853 | 890 | .instructions = self.mir_instructions.toOwnedSlice(), |
| 854 | 891 | .extra = self.mir_extra.toOwnedSlice(self.gpa), |
| ... | ... | @@ -1137,7 +1174,7 @@ pub const DeclGen = struct { |
| 1137 | 1174 | }, |
| 1138 | 1175 | .decl_ref => { |
| 1139 | 1176 | const decl = val.castTag(.decl_ref).?.data; |
| 1140 | | return self.lowerDeclRefValue(ty, val, decl, writer, 0); |
| 1177 | return self.lowerDeclRefValue(ty, val, decl, 0); |
| 1141 | 1178 | }, |
| 1142 | 1179 | .slice => { |
| 1143 | 1180 | const slice = val.castTag(.slice).?.data; |
| ... | ... | @@ -1161,9 +1198,9 @@ pub const DeclGen = struct { |
| 1161 | 1198 | const elem_ptr = val.castTag(.elem_ptr).?.data; |
| 1162 | 1199 | const elem_size = ty.childType().abiSize(self.target()); |
| 1163 | 1200 | const offset = elem_ptr.index * elem_size; |
| 1164 | | return self.lowerParentPtr(elem_ptr.array_ptr, writer, offset); |
| 1201 | return self.lowerParentPtr(elem_ptr.array_ptr, offset); |
| 1165 | 1202 | }, |
| 1166 | | .int_u64 => return self.genTypedValue(Type.usize, val, writer), |
| 1203 | .int_u64 => return self.genTypedValue(Type.usize, val), |
| 1167 | 1204 | else => return self.fail("TODO: Implement zig decl gen for pointer type value: '{s}'", .{@tagName(val.tag())}), |
| 1168 | 1205 | }, |
| 1169 | 1206 | .ErrorUnion => { |
| ... | ... | @@ -1309,22 +1346,16 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) InnerError!CallWValu |
| 1309 | 1346 | return result; |
| 1310 | 1347 | } |
| 1311 | 1348 | |
| 1312 | | /// Retrieves the stack pointer's value from the global variable and stores |
| 1313 | | /// it in a local |
| 1349 | /// Creates a local for the initial stack value |
| 1314 | 1350 | /// Asserts `initial_stack_value` is `.none` |
| 1315 | 1351 | fn initializeStack(self: *Self) !void { |
| 1316 | 1352 | assert(self.initial_stack_value == .none); |
| 1317 | | // reserve space for immediate value |
| 1318 | | // get stack pointer global |
| 1319 | | try self.addLabel(.global_get, 0); |
| 1320 | | |
| 1321 | 1353 | // Reserve a local to store the current stack pointer |
| 1322 | 1354 | // We can later use this local to set the stack pointer back to the value |
| 1323 | 1355 | // we have stored here. |
| 1324 | | self.initial_stack_value = try self.allocLocal(Type.initTag(.i32)); |
| 1325 | | |
| 1326 | | // save the value to the local |
| 1327 | | try self.addLabel(.local_set, self.initial_stack_value.local); |
| 1356 | self.initial_stack_value = try self.allocLocal(Type.usize); |
| 1357 | // Also reserve a local to store the bottom stack value |
| 1358 | self.bottom_stack_value = try self.allocLocal(Type.usize); |
| 1328 | 1359 | } |
| 1329 | 1360 | |
| 1330 | 1361 | /// Reads the stack pointer from `Context.initial_stack_value` and writes it |
| ... | ... | @@ -1339,36 +1370,75 @@ fn restoreStackPointer(self: *Self) !void { |
| 1339 | 1370 | try self.addLabel(.global_set, 0); |
| 1340 | 1371 | } |
| 1341 | 1372 | |
| 1342 | | /// Moves the stack pointer by given `offset` |
| 1343 | | /// It does this by retrieving the stack pointer, subtracting `offset` and storing |
| 1344 | | /// the result back into the stack pointer. |
| 1345 | | fn moveStack(self: *Self, offset: u32, local: u32) !void { |
| 1346 | | if (offset == 0) return; |
| 1347 | | try self.addLabel(.global_get, 0); |
| 1348 | | try self.addImm32(@bitCast(i32, offset)); |
| 1349 | | try self.addTag(.i32_sub); |
| 1350 | | try self.addLabel(.local_tee, local); |
| 1351 | | try self.addLabel(.global_set, 0); |
| 1373 | /// Saves the current stack size's stack pointer position into a given local |
| 1374 | /// It does this by retrieving the bottom stack pointer, adding `self.stack_size` and storing |
| 1375 | /// the result back into the local. |
| 1376 | fn saveStack(self: *Self) !WValue { |
| 1377 | const local = try self.allocLocal(Type.usize); |
| 1378 | try self.addLabel(.local_get, self.bottom_stack_value.local); |
| 1379 | try self.addImm32(@intCast(i32, self.stack_size)); |
| 1380 | try self.addTag(.i32_add); |
| 1381 | try self.addLabel(.local_set, local.local); |
| 1382 | return local; |
| 1352 | 1383 | } |
| 1353 | 1384 | |
| 1354 | 1385 | /// From a given type, will create space on the virtual stack to store the value of such type. |
| 1355 | 1386 | /// This returns a `WValue` with its active tag set to `local`, containing the index to the local |
| 1356 | 1387 | /// that points to the position on the virtual stack. This function should be used instead of |
| 1357 | | /// moveStack unless a local was already created to store the point. |
| 1388 | /// moveStack unless a local was already created to store the pointer. |
| 1358 | 1389 | /// |
| 1359 | 1390 | /// Asserts Type has codegenbits |
| 1360 | 1391 | fn allocStack(self: *Self, ty: Type) !WValue { |
| 1361 | 1392 | assert(ty.hasRuntimeBits()); |
| 1393 | if (self.initial_stack_value == .none) { |
| 1394 | try self.initializeStack(); |
| 1395 | } |
| 1362 | 1396 | |
| 1363 | | // calculate needed stack space |
| 1364 | 1397 | const abi_size = std.math.cast(u32, ty.abiSize(self.target)) catch { |
| 1365 | | return self.fail("Given type '{}' too big to fit into stack frame", .{ty}); |
| 1398 | return self.fail("Type {} with ABI size of {d} exceeds stack frame size", .{ ty, ty.abiSize(self.target) }); |
| 1366 | 1399 | }; |
| 1400 | const abi_align = ty.abiAlignment(self.target); |
| 1367 | 1401 | |
| 1368 | | // allocate a local using wasm's pointer size |
| 1369 | | const local = try self.allocLocal(Type.@"usize"); |
| 1370 | | try self.moveStack(abi_size, local.local); |
| 1371 | | return local; |
| 1402 | if (abi_align > self.stack_alignment) { |
| 1403 | self.stack_alignment = abi_align; |
| 1404 | } |
| 1405 | |
| 1406 | const offset = std.mem.alignForwardGeneric(u32, self.stack_size, abi_align); |
| 1407 | defer self.stack_size = offset + abi_size; |
| 1408 | |
| 1409 | // store the stack pointer and return a local to it |
| 1410 | return self.saveStack(); |
| 1411 | } |
| 1412 | |
| 1413 | /// From a given AIR instruction generates a pointer to the stack where |
| 1414 | /// the value of its type will live. |
| 1415 | /// This is different from allocStack where this will use the pointer's alignment |
| 1416 | /// if it is set, to ensure the stack alignment will be set correctly. |
| 1417 | fn allocStackPtr(self: *Self, inst: Air.Inst.Index) !WValue { |
| 1418 | const ptr_ty = self.air.typeOfIndex(inst); |
| 1419 | const pointee_ty = ptr_ty.childType(); |
| 1420 | |
| 1421 | if (self.initial_stack_value == .none) { |
| 1422 | try self.initializeStack(); |
| 1423 | } |
| 1424 | |
| 1425 | if (!pointee_ty.hasRuntimeBits()) { |
| 1426 | return self.allocStack(Type.usize); // create a value containing just the stack pointer. |
| 1427 | } |
| 1428 | |
| 1429 | const abi_alignment = ptr_ty.ptrAlignment(self.target); |
| 1430 | const abi_size = std.math.cast(u32, pointee_ty.abiSize(self.target)) catch { |
| 1431 | return self.fail("Type {} with ABI size of {d} exceeds stack frame size", .{ pointee_ty, pointee_ty.abiSize(self.target) }); |
| 1432 | }; |
| 1433 | if (abi_alignment > self.stack_alignment) { |
| 1434 | self.stack_alignment = abi_alignment; |
| 1435 | } |
| 1436 | |
| 1437 | const offset = std.mem.alignForwardGeneric(u32, self.stack_size, abi_alignment); |
| 1438 | defer self.stack_size = offset + abi_size; |
| 1439 | |
| 1440 | // store the stack pointer and return a local to it |
| 1441 | return self.saveStack(); |
| 1372 | 1442 | } |
| 1373 | 1443 | |
| 1374 | 1444 | /// From given zig bitsize, returns the wasm bitsize |
| ... | ... | @@ -1667,12 +1737,7 @@ fn airRetPtr(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 1667 | 1737 | if (isByRef(child_type, self.target)) { |
| 1668 | 1738 | return self.return_value; |
| 1669 | 1739 | } |
| 1670 | | |
| 1671 | | // Initialize the stack |
| 1672 | | if (self.initial_stack_value == .none) { |
| 1673 | | try self.initializeStack(); |
| 1674 | | } |
| 1675 | | return self.allocStack(child_type); |
| 1740 | return self.allocStackPtr(inst); |
| 1676 | 1741 | } |
| 1677 | 1742 | |
| 1678 | 1743 | fn airRetLoad(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| ... | ... | @@ -1764,20 +1829,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 1764 | 1829 | } |
| 1765 | 1830 | |
| 1766 | 1831 | fn airAlloc(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 1767 | | const pointee_type = self.air.typeOfIndex(inst).childType(); |
| 1768 | | |
| 1769 | | // Initialize the stack |
| 1770 | | if (self.initial_stack_value == .none) { |
| 1771 | | try self.initializeStack(); |
| 1772 | | } |
| 1773 | | |
| 1774 | | if (!pointee_type.hasRuntimeBits()) { |
| 1775 | | // when the pointee is zero-sized, we still want to create a pointer. |
| 1776 | | // but instead use a default pointer type as storage. |
| 1777 | | const zero_ptr = try self.allocStack(Type.usize); |
| 1778 | | return zero_ptr; |
| 1779 | | } |
| 1780 | | return self.allocStack(pointee_type); |
| 1832 | return self.allocStackPtr(inst); |
| 1781 | 1833 | } |
| 1782 | 1834 | |
| 1783 | 1835 | fn airStore(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |