| ... | @@ -560,6 +560,9 @@ mir_extra: std.ArrayListUnmanaged(u32) = .{}, | ... | @@ -560,6 +560,9 @@ mir_extra: std.ArrayListUnmanaged(u32) = .{}, |
| 560 | /// When a function is executing, we store the the current stack pointer's value within this local. | 560 | /// When a function is executing, we store the the current stack pointer's value within this local. |
| 561 | /// This value is then used to restore the stack pointer to the original value at the return of the function. | 561 | /// This value is then used to restore the stack pointer to the original value at the return of the function. |
| 562 | initial_stack_value: WValue = .none, | 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 | /// Arguments of this function declaration | 566 | /// Arguments of this function declaration |
| 564 | /// This will be set after `resolveCallingConventionValues` | 567 | /// This will be set after `resolveCallingConventionValues` |
| 565 | args: []WValue = &.{}, | 568 | args: []WValue = &.{}, |
| ... | @@ -567,6 +570,14 @@ args: []WValue = &.{}, | ... | @@ -567,6 +570,14 @@ args: []WValue = &.{}, |
| 567 | /// When it returns a pointer to the stack, the `.local` tag will be active and must be populated | 570 | /// When it returns a pointer to the stack, the `.local` tag will be active and must be populated |
| 568 | /// before this function returns its execution to the caller. | 571 | /// before this function returns its execution to the caller. |
| 569 | return_value: WValue = .none, | 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 | const InnerError = error{ | 582 | const InnerError = error{ |
| 572 | OutOfMemory, | 583 | OutOfMemory, |
| ... | @@ -654,13 +665,6 @@ fn addInst(self: *Self, inst: Mir.Inst) error{OutOfMemory}!void { | ... | @@ -654,13 +665,6 @@ fn addInst(self: *Self, inst: Mir.Inst) error{OutOfMemory}!void { |
| 654 | try self.mir_instructions.append(self.gpa, inst); | 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 | fn addTag(self: *Self, tag: Mir.Inst.Tag) error{OutOfMemory}!void { | 668 | fn addTag(self: *Self, tag: Mir.Inst.Tag) error{OutOfMemory}!void { |
| 665 | try self.addInst(.{ .tag = tag, .data = .{ .tag = {} } }); | 669 | try self.addInst(.{ .tag = tag, .data = .{ .tag = {} } }); |
| 666 | } | 670 | } |
| ... | @@ -845,10 +849,43 @@ pub fn genFunc(self: *Self) InnerError!void { | ... | @@ -845,10 +849,43 @@ pub fn genFunc(self: *Self) InnerError!void { |
| 845 | try self.addTag(.@"unreachable"); | 849 | try self.addTag(.@"unreachable"); |
| 846 | } | 850 | } |
| 847 | } | 851 | } |
| 848 | | | |
| 849 | // End of function body | 852 | // End of function body |
| 850 | try self.addTag(.end); | 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 | var mir: Mir = .{ | 889 | var mir: Mir = .{ |
| 853 | .instructions = self.mir_instructions.toOwnedSlice(), | 890 | .instructions = self.mir_instructions.toOwnedSlice(), |
| 854 | .extra = self.mir_extra.toOwnedSlice(self.gpa), | 891 | .extra = self.mir_extra.toOwnedSlice(self.gpa), |
| ... | @@ -1137,7 +1174,7 @@ pub const DeclGen = struct { | ... | @@ -1137,7 +1174,7 @@ pub const DeclGen = struct { |
| 1137 | }, | 1174 | }, |
| 1138 | .decl_ref => { | 1175 | .decl_ref => { |
| 1139 | const decl = val.castTag(.decl_ref).?.data; | 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 | .slice => { | 1179 | .slice => { |
| 1143 | const slice = val.castTag(.slice).?.data; | 1180 | const slice = val.castTag(.slice).?.data; |
| ... | @@ -1161,9 +1198,9 @@ pub const DeclGen = struct { | ... | @@ -1161,9 +1198,9 @@ pub const DeclGen = struct { |
| 1161 | const elem_ptr = val.castTag(.elem_ptr).?.data; | 1198 | const elem_ptr = val.castTag(.elem_ptr).?.data; |
| 1162 | const elem_size = ty.childType().abiSize(self.target()); | 1199 | const elem_size = ty.childType().abiSize(self.target()); |
| 1163 | const offset = elem_ptr.index * elem_size; | 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 | else => return self.fail("TODO: Implement zig decl gen for pointer type value: '{s}'", .{@tagName(val.tag())}), | 1204 | else => return self.fail("TODO: Implement zig decl gen for pointer type value: '{s}'", .{@tagName(val.tag())}), |
| 1168 | }, | 1205 | }, |
| 1169 | .ErrorUnion => { | 1206 | .ErrorUnion => { |
| ... | @@ -1309,22 +1346,16 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) InnerError!CallWValu | ... | @@ -1309,22 +1346,16 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) InnerError!CallWValu |
| 1309 | return result; | 1346 | return result; |
| 1310 | } | 1347 | } |
| 1311 | | 1348 | |
| 1312 | /// Retrieves the stack pointer's value from the global variable and stores | 1349 | /// Creates a local for the initial stack value |
| 1313 | /// it in a local | | |
| 1314 | /// Asserts `initial_stack_value` is `.none` | 1350 | /// Asserts `initial_stack_value` is `.none` |
| 1315 | fn initializeStack(self: *Self) !void { | 1351 | fn initializeStack(self: *Self) !void { |
| 1316 | assert(self.initial_stack_value == .none); | 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 | // Reserve a local to store the current stack pointer | 1353 | // Reserve a local to store the current stack pointer |
| 1322 | // We can later use this local to set the stack pointer back to the value | 1354 | // We can later use this local to set the stack pointer back to the value |
| 1323 | // we have stored here. | 1355 | // we have stored here. |
| 1324 | self.initial_stack_value = try self.allocLocal(Type.initTag(.i32)); | 1356 | self.initial_stack_value = try self.allocLocal(Type.usize); |
| 1325 | | 1357 | // Also reserve a local to store the bottom stack value |
| 1326 | // save the value to the local | 1358 | self.bottom_stack_value = try self.allocLocal(Type.usize); |
| 1327 | try self.addLabel(.local_set, self.initial_stack_value.local); | | |
| 1328 | } | 1359 | } |
| 1329 | | 1360 | |
| 1330 | /// Reads the stack pointer from `Context.initial_stack_value` and writes it | 1361 | /// Reads the stack pointer from `Context.initial_stack_value` and writes it |
| ... | @@ -1339,36 +1370,75 @@ fn restoreStackPointer(self: *Self) !void { | ... | @@ -1339,36 +1370,75 @@ fn restoreStackPointer(self: *Self) !void { |
| 1339 | try self.addLabel(.global_set, 0); | 1370 | try self.addLabel(.global_set, 0); |
| 1340 | } | 1371 | } |
| 1341 | | 1372 | |
| 1342 | /// Moves the stack pointer by given `offset` | 1373 | /// Saves the current stack size's stack pointer position into a given local |
| 1343 | /// It does this by retrieving the stack pointer, subtracting `offset` and storing | 1374 | /// It does this by retrieving the bottom stack pointer, adding `self.stack_size` and storing |
| 1344 | /// the result back into the stack pointer. | 1375 | /// the result back into the local. |
| 1345 | fn moveStack(self: *Self, offset: u32, local: u32) !void { | 1376 | fn saveStack(self: *Self) !WValue { |
| 1346 | if (offset == 0) return; | 1377 | const local = try self.allocLocal(Type.usize); |
| 1347 | try self.addLabel(.global_get, 0); | 1378 | try self.addLabel(.local_get, self.bottom_stack_value.local); |
| 1348 | try self.addImm32(@bitCast(i32, offset)); | 1379 | try self.addImm32(@intCast(i32, self.stack_size)); |
| 1349 | try self.addTag(.i32_sub); | 1380 | try self.addTag(.i32_add); |
| 1350 | try self.addLabel(.local_tee, local); | 1381 | try self.addLabel(.local_set, local.local); |
| 1351 | try self.addLabel(.global_set, 0); | 1382 | return local; |
| 1352 | } | 1383 | } |
| 1353 | | 1384 | |
| 1354 | /// From a given type, will create space on the virtual stack to store the value of such type. | 1385 | /// From a given type, will create space on the virtual stack to store the value of such type. |
| 1355 | /// This returns a `WValue` with its active tag set to `local`, containing the index to the local | 1386 | /// This returns a `WValue` with its active tag set to `local`, containing the index to the local |
| 1356 | /// that points to the position on the virtual stack. This function should be used instead of | 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 | /// Asserts Type has codegenbits | 1390 | /// Asserts Type has codegenbits |
| 1360 | fn allocStack(self: *Self, ty: Type) !WValue { | 1391 | fn allocStack(self: *Self, ty: Type) !WValue { |
| 1361 | assert(ty.hasRuntimeBits()); | 1392 | assert(ty.hasRuntimeBits()); |
| | 1393 | if (self.initial_stack_value == .none) { |
| | 1394 | try self.initializeStack(); |
| | 1395 | } |
| 1362 | | 1396 | |
| 1363 | // calculate needed stack space | | |
| 1364 | const abi_size = std.math.cast(u32, ty.abiSize(self.target)) catch { | 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 | 1402 | if (abi_align > self.stack_alignment) { |
| 1369 | const local = try self.allocLocal(Type.@"usize"); | 1403 | self.stack_alignment = abi_align; |
| 1370 | try self.moveStack(abi_size, local.local); | 1404 | } |
| 1371 | return local; | 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 | /// From given zig bitsize, returns the wasm bitsize | 1444 | /// From given zig bitsize, returns the wasm bitsize |
| ... | @@ -1667,12 +1737,7 @@ fn airRetPtr(self: *Self, inst: Air.Inst.Index) InnerError!WValue { | ... | @@ -1667,12 +1737,7 @@ fn airRetPtr(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 1667 | if (isByRef(child_type, self.target)) { | 1737 | if (isByRef(child_type, self.target)) { |
| 1668 | return self.return_value; | 1738 | return self.return_value; |
| 1669 | } | 1739 | } |
| 1670 | | 1740 | return self.allocStackPtr(inst); |
| 1671 | // Initialize the stack | | |
| 1672 | if (self.initial_stack_value == .none) { | | |
| 1673 | try self.initializeStack(); | | |
| 1674 | } | | |
| 1675 | return self.allocStack(child_type); | | |
| 1676 | } | 1741 | } |
| 1677 | | 1742 | |
| 1678 | fn airRetLoad(self: *Self, inst: Air.Inst.Index) InnerError!WValue { | 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,20 +1829,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 1764 | } | 1829 | } |
| 1765 | | 1830 | |
| 1766 | fn airAlloc(self: *Self, inst: Air.Inst.Index) InnerError!WValue { | 1831 | fn airAlloc(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 1767 | const pointee_type = self.air.typeOfIndex(inst).childType(); | 1832 | return self.allocStackPtr(inst); |
| 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); | | |
| 1781 | } | 1833 | } |
| 1782 | | 1834 | |
| 1783 | fn airStore(self: *Self, inst: Air.Inst.Index) InnerError!WValue { | 1835 | fn airStore(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |