authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-11-17 09:08:32+01:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-11-21 21:07:54+01:00
log261f13414b6bafbe075ce5066964b36a3a5b5e16
tree18df3c82a434a6400cb30251258f18dab8d6a072
parentc18bc08e3c658f50faf7668f8940a11326f3947a
signaturelock-open Commit is signed but in an unrecognized format.

wasm: Implement emulated stack

All non-temporary locals will now use stack memory. When `airAlloc` is called, we create a new local, move the stack pointer, and write its offset into the local. Arguments act as a register and do not use any stack space. We no longer use offsets for binary operations, but instead write the result into a local. In this case, the local is simply used as a register, and does not require stack space. This allows us to ensure the order of instructions is correct, and we no longer require any patching/inserting at a specific offset. print_air was missing the logic to print the type of a `ty_str`.

2 files changed, 111 insertions(+), 62 deletions(-)

src/arch/wasm/CodeGen.zig+110-61
......@@ -210,7 +210,12 @@ fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode {
210210 },
211211 32 => switch (args.valtype1.?) {
212212 .i64 => return .i64_store32,
213 .i32, .f32, .f64 => unreachable,
213 .i32 => return .i32_store,
214 .f32, .f64 => unreachable,
215 },
216 64 => switch (args.valtype1.?) {
217 .i64 => return .i64_store,
218 else => unreachable,
214219 },
215220 else => unreachable,
216221 }
......@@ -529,6 +534,9 @@ global_error_set: std.StringHashMapUnmanaged(Module.ErrorInt),
529534mir_instructions: std.MultiArrayList(Mir.Inst) = .{},
530535/// Contains extra data for MIR
531536mir_extra: std.ArrayListUnmanaged(u32) = .{},
537/// When a function is executing, we store the the current stack pointer's value within this local.
538/// This value is then used to restore the stack pointer to the original value at the return of the function.
539initial_stack_value: WValue = .none,
532540
533541const InnerError = error{
534542 OutOfMemory,
......@@ -686,9 +694,7 @@ fn emitWValue(self: *Self, val: WValue) InnerError!void {
686694 switch (val) {
687695 .multi_value => unreachable, // multi_value can never be written directly, and must be accessed individually
688696 .none, .mir_offset => {}, // no-op
689 .local => |idx| {
690 try self.addLabel(.local_get, idx);
691 },
697 .local => |idx| try self.addLabel(.local_get, idx),
692698 .constant => |tv| try self.emitConstant(tv.val, tv.ty), // Creates a new constant on the stack
693699 }
694700}
......@@ -884,6 +890,59 @@ pub fn gen(self: *Self, ty: Type, val: Value) InnerError!Result {
884890 }
885891}
886892
893/// Retrieves the stack pointer's value from the global variable and stores
894/// it in a local
895fn initializeStack(self: *Self) !void {
896 // reserve space for immediate value
897 // get stack pointer global
898 // TODO: For now, we hardcode the stack pointer to index '0',
899 // once the linker is further implemented, we can replace this by inserting
900 // a relocation and have the linker resolve the correct index to the stack pointer global.
901 // NOTE: relocations of the type GLOBAL_INDEX_LEB are 5-bytes big
902 try self.addLabel(.global_get, 0);
903
904 // Reserve a local to store the current stack pointer
905 // We can later use this local to set the stack pointer back to the value
906 // we have stored here.
907 self.initial_stack_value = try self.allocLocal(Type.initTag(.i32));
908
909 // save the value to the local
910 try self.addLabel(.local_set, self.initial_stack_value.local);
911}
912
913/// Reads the stack pointer from `Context.initial_stack_value` and writes it
914/// to the global stack pointer variable
915fn restoreStackPointer(self: *Self) !void {
916 // only restore the pointer if it was initialized
917 if (self.initial_stack_value == .none) return;
918 // Get the original stack pointer's value
919 try self.emitWValue(self.initial_stack_value);
920
921 // save its value in the global stack pointer
922 try self.addLabel(.global_set, 0);
923}
924
925/// Moves the stack pointer by given `offset`
926/// It does this by retrieving the stack pointer, subtracting `offset` and storing
927/// the result back into the stack pointer.
928fn moveStack(self: *Self, offset: u32, local: u32) !void {
929 if (offset == 0) return;
930 // Generates the following code:
931 //
932 // global.get 0
933 // i32.const [offset]
934 // i32.sub
935 // global.set 0
936
937 // TODO: Rather than hardcode the stack pointer to position 0,
938 // have the linker resolve it.
939 try self.addLabel(.global_get, 0);
940 try self.addImm32(@bitCast(i32, offset));
941 try self.addTag(.i32_sub);
942 try self.addLabel(.local_tee, local);
943 try self.addLabel(.global_set, 0);
944}
945
887946fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
888947 const air_tags = self.air.instructions.items(.tag);
889948 return switch (air_tags[inst]) {
......@@ -963,6 +1022,7 @@ fn airRet(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
9631022 const un_op = self.air.instructions.items(.data)[inst].un_op;
9641023 const operand = self.resolveInst(un_op);
9651024 try self.emitWValue(operand);
1025 try self.restoreStackPointer();
9661026 try self.addTag(.@"return");
9671027 return .none;
9681028}
......@@ -989,13 +1049,24 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
9891049 }
9901050
9911051 try self.addLabel(.call, target.link.wasm.symbol_index);
992
9931052 return .none;
9941053}
9951054
9961055fn airAlloc(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
9971056 const elem_type = self.air.typeOfIndex(inst).elemType();
998 return self.allocLocal(elem_type);
1057
1058 // Initialize the stack
1059 if (self.initial_stack_value == .none) {
1060 try self.initializeStack();
1061 }
1062
1063 const abi_size = elem_type.abiSize(self.target);
1064 if (abi_size == 0) return WValue{ .none = {} };
1065
1066 const local = try self.allocLocal(elem_type);
1067 try self.moveStack(@intCast(u32, abi_size), local.local);
1068
1069 return local;
9991070}
10001071
10011072fn airStore(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
......@@ -1004,48 +1075,35 @@ fn airStore(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
10041075 const lhs = self.resolveInst(bin_op.lhs);
10051076 const rhs = self.resolveInst(bin_op.rhs);
10061077
1007 switch (lhs) {
1008 .multi_value => |multi_value| switch (rhs) {
1009 // When assigning a value to a multi_value such as a struct,
1010 // we simply assign the local_index to the rhs one.
1011 // This allows us to update struct fields without having to individually
1012 // set each local as each field's index will be calculated off the struct's base index
1013 .multi_value => self.values.put(self.gpa, Air.refToIndex(bin_op.lhs).?, rhs) catch unreachable, // Instruction does not dominate all uses!
1014 .constant, .none => {
1015 // emit all values onto the stack if constant
1016 try self.emitWValue(rhs);
1017
1018 // for each local, pop the stack value into the local
1019 // As the last element is on top of the stack, we must populate the locals
1020 // in reverse.
1021 var i: u32 = multi_value.count;
1022 while (i > 0) : (i -= 1) {
1023 try self.addLabel(.local_set, multi_value.index + i - 1);
1024 }
1025 },
1026 .local => {
1027 // This can occur when we wrap a single value into a multi-value,
1028 // such as wrapping a non-optional value into an optional.
1029 // This means we must zero the null-tag, and set the payload.
1030 assert(multi_value.count == 2);
1031 // set payload
1032 try self.emitWValue(rhs);
1033 try self.addLabel(.local_set, multi_value.index + 1);
1034 },
1035 else => unreachable,
1036 },
1037 .local => |local| {
1038 try self.emitWValue(rhs);
1039 try self.addLabel(.local_set, local);
1040 },
1041 else => unreachable,
1042 }
1078 // get lhs stack position
1079 try self.emitWValue(lhs);
1080 // get rhs value
1081 try self.emitWValue(rhs);
1082
1083 const ty = self.air.typeOf(bin_op.lhs);
1084 const valtype = try self.typeToValtype(ty);
1085
1086 const opcode = buildOpcode(.{
1087 .valtype1 = valtype,
1088 .width = @intCast(u8, Type.abiSize(ty, self.target) * 8), // use bitsize instead of byte size
1089 .op = .store,
1090 });
1091 // store rhs value at stack pointer's location in memory
1092 const mem_arg_index = try self.addExtra(Mir.MemArg{ .offset = 0, .alignment = 0 });
1093 try self.addInst(.{ .tag = Mir.Inst.Tag.fromOpcode(opcode), .data = .{ .payload = mem_arg_index } });
1094
10431095 return .none;
10441096}
10451097
10461098fn airLoad(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
10471099 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
1048 return self.resolveInst(ty_op.operand);
1100 const lhs = self.resolveInst(ty_op.operand);
1101
1102 // load local's value from memory by its stack position
1103 try self.emitWValue(lhs);
1104 const mem_arg_index = try self.addExtra(Mir.MemArg{ .offset = 0, .alignment = 0 });
1105 try self.addInst(.{ .tag = .i32_load, .data = .{ .payload = mem_arg_index } });
1106 return .none;
10491107}
10501108
10511109fn airArg(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
......@@ -1060,14 +1118,6 @@ fn airBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {
10601118 const lhs = self.resolveInst(bin_op.lhs);
10611119 const rhs = self.resolveInst(bin_op.rhs);
10621120
1063 // it's possible for both lhs and/or rhs to return an offset as well,
1064 // in which case we return the first offset occurrence we find.
1065 const offset = blk: {
1066 if (lhs == .mir_offset) break :blk lhs.mir_offset;
1067 if (rhs == .mir_offset) break :blk rhs.mir_offset;
1068 break :blk self.mir_instructions.len;
1069 };
1070
10711121 try self.emitWValue(lhs);
10721122 try self.emitWValue(rhs);
10731123
......@@ -1078,7 +1128,11 @@ fn airBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {
10781128 .signedness = if (bin_ty.isSignedInt()) .signed else .unsigned,
10791129 });
10801130 try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));
1081 return WValue{ .mir_offset = offset };
1131
1132 // save the result in a temporary
1133 const bin_local = try self.allocLocal(bin_ty);
1134 try self.addLabel(.local_set, bin_local.local);
1135 return bin_local;
10821136}
10831137
10841138fn airWrapBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {
......@@ -1086,14 +1140,6 @@ fn airWrapBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {
10861140 const lhs = self.resolveInst(bin_op.lhs);
10871141 const rhs = self.resolveInst(bin_op.rhs);
10881142
1089 // it's possible for both lhs and/or rhs to return an offset as well,
1090 // in which case we return the first offset occurrence we find.
1091 const offset = blk: {
1092 if (lhs == .mir_offset) break :blk lhs.mir_offset;
1093 if (rhs == .mir_offset) break :blk rhs.mir_offset;
1094 break :blk self.mir_instructions.len;
1095 };
1096
10971143 try self.emitWValue(lhs);
10981144 try self.emitWValue(rhs);
10991145
......@@ -1132,7 +1178,10 @@ fn airWrapBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {
11321178 return self.fail("TODO wasm: Integer wrapping for bitsizes larger than 64", .{});
11331179 }
11341180
1135 return WValue{ .mir_offset = offset };
1181 // save the result in a temporary
1182 const bin_local = try self.allocLocal(bin_ty);
1183 try self.addLabel(.local_set, bin_local.local);
1184 return bin_local;
11361185}
11371186
11381187fn emitConstant(self: *Self, val: Value, ty: Type) InnerError!void {
src/print_air.zig+1-1
......@@ -234,7 +234,7 @@ const Writer = struct {
234234 fn writeTyStr(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
235235 const ty_str = w.air.instructions.items(.data)[inst].ty_str;
236236 const name = w.zir.nullTerminatedString(ty_str.str);
237 try s.print("\"{}\", {}", .{ std.zig.fmtEscapes(name), ty_str.ty });
237 try s.print("\"{}\", {}", .{ std.zig.fmtEscapes(name), w.air.getRefType(ty_str.ty) });
238238 }
239239
240240 fn writeBinOp(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {