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 {...@@ -210,7 +210,12 @@ fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode {
210 },210 },
211 32 => switch (args.valtype1.?) {211 32 => switch (args.valtype1.?) {
212 .i64 => return .i64_store32,212 .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,
214 },219 },
215 else => unreachable,220 else => unreachable,
216 }221 }
...@@ -529,6 +534,9 @@ global_error_set: std.StringHashMapUnmanaged(Module.ErrorInt),...@@ -529,6 +534,9 @@ global_error_set: std.StringHashMapUnmanaged(Module.ErrorInt),
529mir_instructions: std.MultiArrayList(Mir.Inst) = .{},534mir_instructions: std.MultiArrayList(Mir.Inst) = .{},
530/// Contains extra data for MIR535/// Contains extra data for MIR
531mir_extra: std.ArrayListUnmanaged(u32) = .{},536mir_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
533const InnerError = error{541const InnerError = error{
534 OutOfMemory,542 OutOfMemory,
...@@ -686,9 +694,7 @@ fn emitWValue(self: *Self, val: WValue) InnerError!void {...@@ -686,9 +694,7 @@ fn emitWValue(self: *Self, val: WValue) InnerError!void {
686 switch (val) {694 switch (val) {
687 .multi_value => unreachable, // multi_value can never be written directly, and must be accessed individually695 .multi_value => unreachable, // multi_value can never be written directly, and must be accessed individually
688 .none, .mir_offset => {}, // no-op696 .none, .mir_offset => {}, // no-op
689 .local => |idx| {697 .local => |idx| try self.addLabel(.local_get, idx),
690 try self.addLabel(.local_get, idx);
691 },
692 .constant => |tv| try self.emitConstant(tv.val, tv.ty), // Creates a new constant on the stack698 .constant => |tv| try self.emitConstant(tv.val, tv.ty), // Creates a new constant on the stack
693 }699 }
694}700}
...@@ -884,6 +890,59 @@ pub fn gen(self: *Self, ty: Type, val: Value) InnerError!Result {...@@ -884,6 +890,59 @@ pub fn gen(self: *Self, ty: Type, val: Value) InnerError!Result {
884 }890 }
885}891}
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
887fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {946fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
888 const air_tags = self.air.instructions.items(.tag);947 const air_tags = self.air.instructions.items(.tag);
889 return switch (air_tags[inst]) {948 return switch (air_tags[inst]) {
...@@ -963,6 +1022,7 @@ fn airRet(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -963,6 +1022,7 @@ fn airRet(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
963 const un_op = self.air.instructions.items(.data)[inst].un_op;1022 const un_op = self.air.instructions.items(.data)[inst].un_op;
964 const operand = self.resolveInst(un_op);1023 const operand = self.resolveInst(un_op);
965 try self.emitWValue(operand);1024 try self.emitWValue(operand);
1025 try self.restoreStackPointer();
966 try self.addTag(.@"return");1026 try self.addTag(.@"return");
967 return .none;1027 return .none;
968}1028}
...@@ -989,13 +1049,24 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -989,13 +1049,24 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
989 }1049 }
9901050
991 try self.addLabel(.call, target.link.wasm.symbol_index);1051 try self.addLabel(.call, target.link.wasm.symbol_index);
992
993 return .none;1052 return .none;
994}1053}
9951054
996fn airAlloc(self: *Self, inst: Air.Inst.Index) InnerError!WValue {1055fn airAlloc(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
997 const elem_type = self.air.typeOfIndex(inst).elemType();1056 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;
999}1070}
10001071
1001fn airStore(self: *Self, inst: Air.Inst.Index) InnerError!WValue {1072fn airStore(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
...@@ -1004,48 +1075,35 @@ fn airStore(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -1004,48 +1075,35 @@ fn airStore(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1004 const lhs = self.resolveInst(bin_op.lhs);1075 const lhs = self.resolveInst(bin_op.lhs);
1005 const rhs = self.resolveInst(bin_op.rhs);1076 const rhs = self.resolveInst(bin_op.rhs);
10061077
1007 switch (lhs) {1078 // get lhs stack position
1008 .multi_value => |multi_value| switch (rhs) {1079 try self.emitWValue(lhs);
1009 // When assigning a value to a multi_value such as a struct,1080 // get rhs value
1010 // we simply assign the local_index to the rhs one.1081 try self.emitWValue(rhs);
1011 // This allows us to update struct fields without having to individually1082
1012 // set each local as each field's index will be calculated off the struct's base index1083 const ty = self.air.typeOf(bin_op.lhs);
1013 .multi_value => self.values.put(self.gpa, Air.refToIndex(bin_op.lhs).?, rhs) catch unreachable, // Instruction does not dominate all uses!1084 const valtype = try self.typeToValtype(ty);
1014 .constant, .none => {1085
1015 // emit all values onto the stack if constant1086 const opcode = buildOpcode(.{
1016 try self.emitWValue(rhs);1087 .valtype1 = valtype,
10171088 .width = @intCast(u8, Type.abiSize(ty, self.target) * 8), // use bitsize instead of byte size
1018 // for each local, pop the stack value into the local1089 .op = .store,
1019 // As the last element is on top of the stack, we must populate the locals1090 });
1020 // in reverse.1091 // store rhs value at stack pointer's location in memory
1021 var i: u32 = multi_value.count;1092 const mem_arg_index = try self.addExtra(Mir.MemArg{ .offset = 0, .alignment = 0 });
1022 while (i > 0) : (i -= 1) {1093 try self.addInst(.{ .tag = Mir.Inst.Tag.fromOpcode(opcode), .data = .{ .payload = mem_arg_index } });
1023 try self.addLabel(.local_set, multi_value.index + i - 1);1094
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 }
1043 return .none;1095 return .none;
1044}1096}
10451097
1046fn airLoad(self: *Self, inst: Air.Inst.Index) InnerError!WValue {1098fn airLoad(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1047 const ty_op = self.air.instructions.items(.data)[inst].ty_op;1099 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;
1049}1107}
10501108
1051fn airArg(self: *Self, inst: Air.Inst.Index) InnerError!WValue {1109fn 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 {...@@ -1060,14 +1118,6 @@ fn airBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {
1060 const lhs = self.resolveInst(bin_op.lhs);1118 const lhs = self.resolveInst(bin_op.lhs);
1061 const rhs = self.resolveInst(bin_op.rhs);1119 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
1071 try self.emitWValue(lhs);1121 try self.emitWValue(lhs);
1072 try self.emitWValue(rhs);1122 try self.emitWValue(rhs);
10731123
...@@ -1078,7 +1128,11 @@ fn airBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {...@@ -1078,7 +1128,11 @@ fn airBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {
1078 .signedness = if (bin_ty.isSignedInt()) .signed else .unsigned,1128 .signedness = if (bin_ty.isSignedInt()) .signed else .unsigned,
1079 });1129 });
1080 try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));1130 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;
1082}1136}
10831137
1084fn airWrapBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {1138fn 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 {...@@ -1086,14 +1140,6 @@ fn airWrapBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {
1086 const lhs = self.resolveInst(bin_op.lhs);1140 const lhs = self.resolveInst(bin_op.lhs);
1087 const rhs = self.resolveInst(bin_op.rhs);1141 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
1097 try self.emitWValue(lhs);1143 try self.emitWValue(lhs);
1098 try self.emitWValue(rhs);1144 try self.emitWValue(rhs);
10991145
...@@ -1132,7 +1178,10 @@ fn airWrapBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {...@@ -1132,7 +1178,10 @@ fn airWrapBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {
1132 return self.fail("TODO wasm: Integer wrapping for bitsizes larger than 64", .{});1178 return self.fail("TODO wasm: Integer wrapping for bitsizes larger than 64", .{});
1133 }1179 }
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;
1136}1185}
11371186
1138fn emitConstant(self: *Self, val: Value, ty: Type) InnerError!void {1187fn emitConstant(self: *Self, val: Value, ty: Type) InnerError!void {
src/print_air.zig+1-1
...@@ -234,7 +234,7 @@ const Writer = struct {...@@ -234,7 +234,7 @@ const Writer = struct {
234 fn writeTyStr(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {234 fn writeTyStr(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
235 const ty_str = w.air.instructions.items(.data)[inst].ty_str;235 const ty_str = w.air.instructions.items(.data)[inst].ty_str;
236 const name = w.zir.nullTerminatedString(ty_str.str);236 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) });
238 }238 }
239239
240 fn writeBinOp(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {240 fn writeBinOp(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {