authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-01-04 14:54:50-05:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2022-01-04 14:54:50-05:00
log5087ec6f41ba928e14596e00822dc117aeb90a12
tree95d629f527158751b0828b274f96b5e32c924d12
parent5c228765f1094d30e64d13c0077c67b2867ecd6a
parent89b1fdc4437531776b46ed7133c44b7250c122f8
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #10508 from Luukdegram/wasm-behavior-tests

Stage2: wasm - Pass more behavior tests

5 files changed, 400 insertions(+), 137 deletions(-)

lib/std/wasm.zig+23-1
...@@ -212,6 +212,28 @@ test "Wasm - opcodes" {...@@ -212,6 +212,28 @@ test "Wasm - opcodes" {
212 try testing.expectEqual(@as(u16, 0xC4), i64_extend32_s);212 try testing.expectEqual(@as(u16, 0xC4), i64_extend32_s);
213}213}
214214
215/// Opcodes that require a prefix `0xFC`
216pub const PrefixedOpcode = enum(u8) {
217 i32_trunc_sat_f32_s = 0x00,
218 i32_trunc_sat_f32_u = 0x01,
219 i32_trunc_sat_f64_s = 0x02,
220 i32_trunc_sat_f64_u = 0x03,
221 i64_trunc_sat_f32_s = 0x04,
222 i64_trunc_sat_f32_u = 0x05,
223 i64_trunc_sat_f64_s = 0x06,
224 i64_trunc_sat_f64_u = 0x07,
225 memory_init = 0x08,
226 data_drop = 0x09,
227 memory_copy = 0x0A,
228 memory_fill = 0x0B,
229 table_init = 0x0C,
230 elem_drop = 0x0D,
231 table_copy = 0x0E,
232 table_grow = 0x0F,
233 table_size = 0x10,
234 table_fill = 0x11,
235};
236
215/// Enum representing all Wasm value types as per spec:237/// Enum representing all Wasm value types as per spec:
216/// https://webassembly.github.io/spec/core/binary/types.html238/// https://webassembly.github.io/spec/core/binary/types.html
217pub const Valtype = enum(u8) {239pub const Valtype = enum(u8) {
...@@ -266,7 +288,7 @@ pub const InitExpression = union(enum) {...@@ -266,7 +288,7 @@ pub const InitExpression = union(enum) {
266 global_get: u32,288 global_get: u32,
267};289};
268290
269///291/// Represents a function entry, holding the index to its type
270pub const Func = struct {292pub const Func = struct {
271 type_index: u32,293 type_index: u32,
272};294};
src/arch/wasm/CodeGen.zig+342-128
...@@ -623,6 +623,10 @@ fn addTag(self: *Self, tag: Mir.Inst.Tag) error{OutOfMemory}!void {...@@ -623,6 +623,10 @@ fn addTag(self: *Self, tag: Mir.Inst.Tag) error{OutOfMemory}!void {
623 try self.addInst(.{ .tag = tag, .data = .{ .tag = {} } });623 try self.addInst(.{ .tag = tag, .data = .{ .tag = {} } });
624}624}
625625
626fn addExtended(self: *Self, opcode: wasm.PrefixedOpcode) error{OutOfMemory}!void {
627 try self.addInst(.{ .tag = .extended, .secondary = @enumToInt(opcode), .data = .{ .tag = {} } });
628}
629
626fn addLabel(self: *Self, tag: Mir.Inst.Tag, label: u32) error{OutOfMemory}!void {630fn addLabel(self: *Self, tag: Mir.Inst.Tag, label: u32) error{OutOfMemory}!void {
627 try self.addInst(.{ .tag = tag, .data = .{ .label = label } });631 try self.addInst(.{ .tag = tag, .data = .{ .label = label } });
628}632}
...@@ -746,6 +750,13 @@ fn genFunctype(self: *Self, fn_ty: Type) !wasm.Type {...@@ -746,6 +750,13 @@ fn genFunctype(self: *Self, fn_ty: Type) !wasm.Type {
746 defer params.deinit();750 defer params.deinit();
747 var returns = std.ArrayList(wasm.Valtype).init(self.gpa);751 var returns = std.ArrayList(wasm.Valtype).init(self.gpa);
748 defer returns.deinit();752 defer returns.deinit();
753 const return_type = fn_ty.fnReturnType();
754
755 const want_sret = isByRef(return_type);
756
757 if (want_sret) {
758 try params.append(try self.typeToValtype(Type.usize));
759 }
749760
750 // param types761 // param types
751 if (fn_ty.fnParamLen() != 0) {762 if (fn_ty.fnParamLen() != 0) {
...@@ -759,11 +770,8 @@ fn genFunctype(self: *Self, fn_ty: Type) !wasm.Type {...@@ -759,11 +770,8 @@ fn genFunctype(self: *Self, fn_ty: Type) !wasm.Type {
759 }770 }
760771
761 // return type772 // return type
762 const return_type = fn_ty.fnReturnType();773 if (!want_sret and return_type.hasCodeGenBits()) {
763 switch (return_type.zigTypeTag()) {774 try returns.append(try self.typeToValtype(return_type));
764 .Void, .NoReturn => {},
765 .Struct => return self.fail("TODO: Implement struct as return type for wasm", .{}),
766 else => try returns.append(try self.typeToValtype(return_type)),
767 }775 }
768776
769 return wasm.Type{777 return wasm.Type{
...@@ -785,6 +793,15 @@ pub fn genFunc(self: *Self) InnerError!Result {...@@ -785,6 +793,15 @@ pub fn genFunc(self: *Self) InnerError!Result {
785793
786 // Generate MIR for function body794 // Generate MIR for function body
787 try self.genBody(self.air.getMainBody());795 try self.genBody(self.air.getMainBody());
796 // In case we have a return value, but the last instruction is a noreturn (such as a while loop)
797 // we emit an unreachable instruction to tell the stack validator that part will never be reached.
798 if (func_type.returns.len != 0 and self.air.instructions.len > 0) {
799 const inst = @intCast(u32, self.air.instructions.len - 1);
800 if (self.air.typeOfIndex(inst).isNoReturn()) {
801 try self.addTag(.@"unreachable");
802 }
803 }
804
788 // End of function body805 // End of function body
789 try self.addTag(.end);806 try self.addTag(.end);
790807
...@@ -1074,6 +1091,15 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) InnerError!CallWValu...@@ -1074,6 +1091,15 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) InnerError!CallWValu
1074 .return_value = .none,1091 .return_value = .none,
1075 };1092 };
1076 errdefer self.gpa.free(result.args);1093 errdefer self.gpa.free(result.args);
1094 const ret_ty = fn_ty.fnReturnType();
1095 // Check if we store the result as a pointer to the stack rather than
1096 // by value
1097 if (isByRef(ret_ty)) {
1098 // the sret arg will be passed as first argument, therefore we
1099 // set the `return_value` before allocating locals for regular args.
1100 result.return_value = .{ .local = self.local_index };
1101 self.local_index += 1;
1102 }
1077 switch (cc) {1103 switch (cc) {
1078 .Naked => return result,1104 .Naked => return result,
1079 .Unspecified, .C => {1105 .Unspecified, .C => {
...@@ -1086,27 +1112,6 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) InnerError!CallWValu...@@ -1086,27 +1112,6 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) InnerError!CallWValu
1086 result.args[ty_index] = .{ .local = self.local_index };1112 result.args[ty_index] = .{ .local = self.local_index };
1087 self.local_index += 1;1113 self.local_index += 1;
1088 }1114 }
1089
1090 const ret_ty = fn_ty.fnReturnType();
1091 if (isByRef(ret_ty)) {
1092 result.return_value = try self.allocLocal(Type.initTag(.i32));
1093 }
1094
1095 // Check if we store the result as a pointer to the stack rather than
1096 // by value
1097 if (result.return_value != .none) {
1098 if (self.initial_stack_value == .none) try self.initializeStack();
1099 const offset = std.math.cast(u32, ret_ty.abiSize(self.target)) catch {
1100 return self.fail("Return type '{}' too big for stack frame", .{ret_ty});
1101 };
1102
1103 try self.moveStack(offset, result.return_value.local);
1104
1105 // We want to make sure the return value's stack value doesn't get overwritten,
1106 // so set initial stack value to current's position instead.
1107 try self.addLabel(.global_get, 0);
1108 try self.addLabel(.local_set, self.initial_stack_value.local);
1109 }
1110 },1115 },
1111 else => return self.fail("TODO implement function parameters for cc '{}' on wasm", .{cc}),1116 else => return self.fail("TODO implement function parameters for cc '{}' on wasm", .{cc}),
1112 }1117 }
...@@ -1165,10 +1170,16 @@ fn allocStack(self: *Self, ty: Type) !WValue {...@@ -1165,10 +1170,16 @@ fn allocStack(self: *Self, ty: Type) !WValue {
1165 assert(ty.hasCodeGenBits());1170 assert(ty.hasCodeGenBits());
11661171
1167 // calculate needed stack space1172 // calculate needed stack space
1168 const abi_size = std.math.cast(u32, ty.abiSize(self.target)) catch {1173 var abi_size = std.math.cast(u32, ty.abiSize(self.target)) catch {
1169 return self.fail("Given type '{}' too big to fit into stack frame", .{ty});1174 return self.fail("Given type '{}' too big to fit into stack frame", .{ty});
1170 };1175 };
11711176
1177 // We store slices as a struct with a pointer field and a length field
1178 // both being 'usize' size.
1179 if (ty.isSlice()) {
1180 abi_size = self.ptrSize() * 2;
1181 }
1182
1172 // allocate a local using wasm's pointer size1183 // allocate a local using wasm's pointer size
1173 const local = try self.allocLocal(Type.@"usize");1184 const local = try self.allocLocal(Type.@"usize");
1174 try self.moveStack(abi_size, local.local);1185 try self.moveStack(abi_size, local.local);
...@@ -1256,6 +1267,28 @@ fn isByRef(ty: Type) bool {...@@ -1256,6 +1267,28 @@ fn isByRef(ty: Type) bool {
1256 }1267 }
1257}1268}
12581269
1270/// Creates a new local for a pointer that points to memory with given offset.
1271/// This can be used to get a pointer to a struct field, error payload, etc.
1272fn buildPointerOffset(self: *Self, ptr_value: WValue, offset: u64) InnerError!WValue {
1273 // do not perform arithmetic when offset is 0.
1274 if (offset == 0) return ptr_value;
1275 const result_ptr = try self.allocLocal(Type.usize);
1276 try self.emitWValue(ptr_value);
1277 switch (self.target.cpu.arch.ptrBitWidth()) {
1278 32 => {
1279 try self.addImm32(@bitCast(i32, @intCast(u32, offset)));
1280 try self.addTag(.i32_add);
1281 },
1282 64 => {
1283 try self.addImm64(offset);
1284 try self.addTag(.i64_add);
1285 },
1286 else => unreachable,
1287 }
1288 try self.addLabel(.local_set, result_ptr.local);
1289 return result_ptr;
1290}
1291
1259fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {1292fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
1260 const air_tags = self.air.instructions.items(.tag);1293 const air_tags = self.air.instructions.items(.tag);
1261 return switch (air_tags[inst]) {1294 return switch (air_tags[inst]) {
...@@ -1296,16 +1329,17 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {...@@ -1296,16 +1329,17 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
1296 .is_err => self.airIsErr(inst, .i32_ne),1329 .is_err => self.airIsErr(inst, .i32_ne),
1297 .is_non_err => self.airIsErr(inst, .i32_eq),1330 .is_non_err => self.airIsErr(inst, .i32_eq),
12981331
1299 .is_null => self.airIsNull(inst, .i32_ne),1332 .is_null => self.airIsNull(inst, .i32_eq, .value),
1300 .is_non_null => self.airIsNull(inst, .i32_eq),1333 .is_non_null => self.airIsNull(inst, .i32_ne, .value),
1301 .is_null_ptr => self.airIsNull(inst, .i32_ne),1334 .is_null_ptr => self.airIsNull(inst, .i32_eq, .ptr),
1302 .is_non_null_ptr => self.airIsNull(inst, .i32_eq),1335 .is_non_null_ptr => self.airIsNull(inst, .i32_ne, .ptr),
13031336
1304 .load => self.airLoad(inst),1337 .load => self.airLoad(inst),
1305 .loop => self.airLoop(inst),1338 .loop => self.airLoop(inst),
1339 .memset => self.airMemset(inst),
1306 .not => self.airNot(inst),1340 .not => self.airNot(inst),
1307 .optional_payload => self.airOptionalPayload(inst),1341 .optional_payload => self.airOptionalPayload(inst),
1308 .optional_payload_ptr => self.airOptionalPayload(inst),1342 .optional_payload_ptr => self.airOptionalPayloadPtr(inst),
1309 .optional_payload_ptr_set => self.airOptionalPayloadPtrSet(inst),1343 .optional_payload_ptr_set => self.airOptionalPayloadPtrSet(inst),
1310 .ptr_add => self.airPtrBinOp(inst, .add),1344 .ptr_add => self.airPtrBinOp(inst, .add),
1311 .ptr_sub => self.airPtrBinOp(inst, .sub),1345 .ptr_sub => self.airPtrBinOp(inst, .sub),
...@@ -1315,17 +1349,21 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {...@@ -1315,17 +1349,21 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
1315 .ret => self.airRet(inst),1349 .ret => self.airRet(inst),
1316 .ret_ptr => self.airRetPtr(inst),1350 .ret_ptr => self.airRetPtr(inst),
1317 .ret_load => self.airRetLoad(inst),1351 .ret_load => self.airRetLoad(inst),
1352
1353 .slice => self.airSlice(inst),
1318 .slice_len => self.airSliceLen(inst),1354 .slice_len => self.airSliceLen(inst),
1319 .slice_elem_val => self.airSliceElemVal(inst),1355 .slice_elem_val => self.airSliceElemVal(inst),
1320 .slice_elem_ptr => self.airSliceElemPtr(inst),1356 .slice_elem_ptr => self.airSliceElemPtr(inst),
1321 .slice_ptr => self.airSlicePtr(inst),1357 .slice_ptr => self.airSlicePtr(inst),
1322 .store => self.airStore(inst),1358 .store => self.airStore(inst),
1359
1323 .struct_field_ptr => self.airStructFieldPtr(inst),1360 .struct_field_ptr => self.airStructFieldPtr(inst),
1324 .struct_field_ptr_index_0 => self.airStructFieldPtrIndex(inst, 0),1361 .struct_field_ptr_index_0 => self.airStructFieldPtrIndex(inst, 0),
1325 .struct_field_ptr_index_1 => self.airStructFieldPtrIndex(inst, 1),1362 .struct_field_ptr_index_1 => self.airStructFieldPtrIndex(inst, 1),
1326 .struct_field_ptr_index_2 => self.airStructFieldPtrIndex(inst, 2),1363 .struct_field_ptr_index_2 => self.airStructFieldPtrIndex(inst, 2),
1327 .struct_field_ptr_index_3 => self.airStructFieldPtrIndex(inst, 3),1364 .struct_field_ptr_index_3 => self.airStructFieldPtrIndex(inst, 3),
1328 .struct_field_val => self.airStructFieldVal(inst),1365 .struct_field_val => self.airStructFieldVal(inst),
1366
1329 .switch_br => self.airSwitchBr(inst),1367 .switch_br => self.airSwitchBr(inst),
1330 .trunc => self.airTrunc(inst),1368 .trunc => self.airTrunc(inst),
1331 .unreach => self.airUnreachable(inst),1369 .unreach => self.airUnreachable(inst),
...@@ -1353,7 +1391,6 @@ fn airRet(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -1353,7 +1391,6 @@ fn airRet(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1353 // to the stack instead1391 // to the stack instead
1354 if (self.return_value != .none) {1392 if (self.return_value != .none) {
1355 try self.store(self.return_value, operand, self.decl.ty.fnReturnType(), 0);1393 try self.store(self.return_value, operand, self.decl.ty.fnReturnType(), 0);
1356 try self.emitWValue(self.return_value);
1357 } else {1394 } else {
1358 try self.emitWValue(operand);1395 try self.emitWValue(operand);
1359 }1396 }
...@@ -1372,6 +1409,9 @@ fn airRetPtr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -1372,6 +1409,9 @@ fn airRetPtr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
13721409
1373 if (child_type.abiSize(self.target) == 0) return WValue{ .none = {} };1410 if (child_type.abiSize(self.target) == 0) return WValue{ .none = {} };
13741411
1412 if (isByRef(child_type)) {
1413 return self.return_value;
1414 }
1375 return self.allocStack(child_type);1415 return self.allocStack(child_type);
1376}1416}
13771417
...@@ -1381,9 +1421,7 @@ fn airRetLoad(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -1381,9 +1421,7 @@ fn airRetLoad(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1381 const ret_ty = self.air.typeOf(un_op).childType();1421 const ret_ty = self.air.typeOf(un_op).childType();
1382 if (!ret_ty.hasCodeGenBits()) return WValue.none;1422 if (!ret_ty.hasCodeGenBits()) return WValue.none;
13831423
1384 if (isByRef(ret_ty)) {1424 if (!isByRef(ret_ty)) {
1385 try self.emitWValue(operand);
1386 } else {
1387 const result = try self.load(operand, ret_ty, 0);1425 const result = try self.load(operand, ret_ty, 0);
1388 try self.emitWValue(result);1426 try self.emitWValue(result);
1389 }1427 }
...@@ -1404,6 +1442,8 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -1404,6 +1442,8 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1404 .Pointer => ty.childType(),1442 .Pointer => ty.childType(),
1405 else => unreachable,1443 else => unreachable,
1406 };1444 };
1445 const ret_ty = fn_ty.fnReturnType();
1446 const first_param_sret = isByRef(ret_ty);
14071447
1408 const target: ?*Decl = blk: {1448 const target: ?*Decl = blk: {
1409 const func_val = self.air.value(pl_op.operand) orelse break :blk null;1449 const func_val = self.air.value(pl_op.operand) orelse break :blk null;
...@@ -1416,6 +1456,12 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -1416,6 +1456,12 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1416 return self.fail("Expected a function, but instead found type '{s}'", .{func_val.tag()});1456 return self.fail("Expected a function, but instead found type '{s}'", .{func_val.tag()});
1417 };1457 };
14181458
1459 const sret = if (first_param_sret) blk: {
1460 const sret_local = try self.allocStack(ret_ty);
1461 try self.emitWValue(sret_local);
1462 break :blk sret_local;
1463 } else WValue{ .none = {} };
1464
1419 for (args) |arg| {1465 for (args) |arg| {
1420 const arg_ref = @intToEnum(Air.Inst.Ref, arg);1466 const arg_ref = @intToEnum(Air.Inst.Ref, arg);
1421 const arg_val = self.resolveInst(arg_ref);1467 const arg_val = self.resolveInst(arg_ref);
...@@ -1454,42 +1500,35 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -1454,42 +1500,35 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1454 try self.addLabel(.call_indirect, fn_type_index);1500 try self.addLabel(.call_indirect, fn_type_index);
1455 }1501 }
14561502
1457 const ret_ty = fn_ty.fnReturnType();1503 if (self.liveness.isUnused(inst) or !ret_ty.hasCodeGenBits()) {
1458 if (!ret_ty.hasCodeGenBits()) return WValue.none;1504 return WValue.none;
14591505 } else if (ret_ty.isNoReturn()) {
1460 // TODO: Implement this for all aggregate types1506 try self.addTag(.@"unreachable");
1461 if (ret_ty.isSlice()) {1507 return WValue.none;
1462 // first load the values onto the regular stack, before we move the stack pointer1508 } else if (first_param_sret) {
1463 // to prevent overwriting the return value.1509 return sret;
1464 const tmp = try self.allocLocal(ret_ty);1510 } else {
1465 try self.addLabel(.local_set, tmp.local);1511 const result_local = try self.allocLocal(ret_ty);
1466 const field_ty = Type.@"usize";1512 try self.addLabel(.local_set, result_local.local);
1467 const offset = @intCast(u32, field_ty.abiSize(self.target));1513 return result_local;
1468 const ptr_local = try self.load(tmp, field_ty, 0);
1469 const len_local = try self.load(tmp, field_ty, offset);
1470
1471 // As our values are now safe, we reserve space on the virtual stack and
1472 // store the values there.
1473 const result = try self.allocStack(ret_ty);
1474 try self.store(result, ptr_local, field_ty, 0);
1475 try self.store(result, len_local, field_ty, offset);
1476 return result;
1477 }1514 }
1478
1479 const result_local = try self.allocLocal(ret_ty);
1480 try self.addLabel(.local_set, result_local.local);
1481 return result_local;
1482}1515}
14831516
1484fn airAlloc(self: *Self, inst: Air.Inst.Index) InnerError!WValue {1517fn airAlloc(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1485 const child_type = self.air.typeOfIndex(inst).childType();1518 const pointee_type = self.air.typeOfIndex(inst).childType();
14861519
1487 // Initialize the stack1520 // Initialize the stack
1488 if (self.initial_stack_value == .none) {1521 if (self.initial_stack_value == .none) {
1489 try self.initializeStack();1522 try self.initializeStack();
1490 }1523 }
1491 if (child_type.abiSize(self.target) == 0) return WValue{ .none = {} };1524
1492 return self.allocStack(child_type);1525 if (!pointee_type.hasCodeGenBits()) {
1526 // when the pointee is zero-sized, we still want to create a pointer.
1527 // but instead use a default pointer type as storage.
1528 const zero_ptr = try self.allocStack(Type.usize);
1529 return zero_ptr;
1530 }
1531 return self.allocStack(pointee_type);
1493}1532}
14941533
1495fn airStore(self: *Self, inst: Air.Inst.Index) InnerError!WValue {1534fn airStore(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
...@@ -1516,6 +1555,8 @@ fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerErro...@@ -1516,6 +1555,8 @@ fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerErro
1516 const tag_ty = if (ty.zigTypeTag() == .ErrorUnion) ty.errorUnionSet() else Type.initTag(.u8);1555 const tag_ty = if (ty.zigTypeTag() == .ErrorUnion) ty.errorUnionSet() else Type.initTag(.u8);
1517 const payload_offset = if (ty.zigTypeTag() == .ErrorUnion)1556 const payload_offset = if (ty.zigTypeTag() == .ErrorUnion)
1518 @intCast(u32, tag_ty.abiSize(self.target))1557 @intCast(u32, tag_ty.abiSize(self.target))
1558 else if (ty.isPtrLikeOptional())
1559 @as(u32, 0)
1519 else1560 else
1520 @intCast(u32, ty.abiSize(self.target) - payload_ty.abiSize(self.target));1561 @intCast(u32, ty.abiSize(self.target) - payload_ty.abiSize(self.target));
15211562
...@@ -1528,6 +1569,10 @@ fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerErro...@@ -1528,6 +1569,10 @@ fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerErro
1528 try self.addLabel(.local_set, mem_local.local);1569 try self.addLabel(.local_set, mem_local.local);
1529 try self.store(lhs, mem_local, ty, 0);1570 try self.store(lhs, mem_local, ty, 0);
1530 return;1571 return;
1572 } else if (ty.isPtrLikeOptional()) {
1573 // set the address of rhs to lhs
1574 try self.store(lhs, rhs, Type.usize, 0);
1575 return;
1531 }1576 }
1532 // constant will contain both tag and payload,1577 // constant will contain both tag and payload,
1533 // so save those in 2 temporary locals before storing them1578 // so save those in 2 temporary locals before storing them
...@@ -1546,11 +1591,23 @@ fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerErro...@@ -1546,11 +1591,23 @@ fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerErro
1546 return;1591 return;
1547 },1592 },
1548 .local => {1593 .local => {
1594 // When the optional is pointer-like, we simply store the pointer
1595 // instead.
1596 if (ty.isPtrLikeOptional()) {
1597 try self.store(lhs, rhs, Type.usize, 0);
1598 return;
1599 }
1549 // Load values from `rhs` stack position and store in `lhs` instead1600 // Load values from `rhs` stack position and store in `lhs` instead
1550 const tag_local = try self.load(rhs, tag_ty, 0);1601 const tag_local = try self.load(rhs, tag_ty, 0);
1551 if (payload_ty.hasCodeGenBits()) {1602 if (payload_ty.hasCodeGenBits()) {
1552 const payload_local = try self.load(rhs, payload_ty, payload_offset);1603 if (isByRef(payload_ty)) {
1553 try self.store(lhs, payload_local, payload_ty, payload_offset);1604 const payload_ptr = try self.buildPointerOffset(rhs, payload_offset);
1605 const lhs_payload_ptr = try self.buildPointerOffset(lhs, payload_offset);
1606 try self.store(lhs_payload_ptr, payload_ptr, payload_ty, 0);
1607 } else {
1608 const payload_local = try self.load(rhs, payload_ty, payload_offset);
1609 try self.store(lhs, payload_local, payload_ty, payload_offset);
1610 }
1554 }1611 }
1555 return try self.store(lhs, tag_local, tag_ty, 0);1612 return try self.store(lhs, tag_local, tag_ty, 0);
1556 },1613 },
...@@ -1593,12 +1650,9 @@ fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerErro...@@ -1593,12 +1650,9 @@ fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerErro
1593 const len_offset = self.ptrSize();1650 const len_offset = self.ptrSize();
1594 if (val.castTag(.decl_ref)) |decl| {1651 if (val.castTag(.decl_ref)) |decl| {
1595 // for decl references we also need to retrieve the length and the original decl's pointer1652 // for decl references we also need to retrieve the length and the original decl's pointer
1596 try self.addMemArg(.i32_load, .{ .offset = 0, .alignment = Type.@"usize".abiAlignment(self.target) });1653 try self.addMemArg(.i32_load, .{ .offset = 0, .alignment = self.ptrSize() });
1597 try self.addLabel(.memory_address, decl.data.link.wasm.sym_index);1654 try self.addLabel(.memory_address, decl.data.link.wasm.sym_index);
1598 try self.addMemArg(1655 try self.addMemArg(.i32_load, .{ .offset = len_offset, .alignment = self.ptrSize() });
1599 .i32_load,
1600 .{ .offset = len_offset, .alignment = Type.@"usize".abiAlignment(self.target) },
1601 );
1602 }1656 }
1603 try self.addLabel(.local_set, len_local.local);1657 try self.addLabel(.local_set, len_local.local);
1604 try self.addLabel(.local_set, ptr_local.local);1658 try self.addLabel(.local_set, ptr_local.local);
...@@ -1630,7 +1684,6 @@ fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerErro...@@ -1630,7 +1684,6 @@ fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerErro
1630 .ErrorSet,1684 .ErrorSet,
1631 .Enum,1685 .Enum,
1632 .Bool,1686 .Bool,
1633 .ErrorUnion,
1634 => @intCast(u8, ty.abiSize(self.target)),1687 => @intCast(u8, ty.abiSize(self.target)),
1635 else => @as(u8, 4),1688 else => @as(u8, 4),
1636 };1689 };
...@@ -1670,7 +1723,9 @@ fn load(self: *Self, operand: WValue, ty: Type, offset: u32) InnerError!WValue {...@@ -1670,7 +1723,9 @@ fn load(self: *Self, operand: WValue, ty: Type, offset: u32) InnerError!WValue {
1670 // load local's value from memory by its stack position1723 // load local's value from memory by its stack position
1671 try self.emitWValue(operand);1724 try self.emitWValue(operand);
1672 // Build the opcode with the right bitsize1725 // Build the opcode with the right bitsize
1673 const signedness: std.builtin.Signedness = if (ty.isUnsignedInt() or ty.zigTypeTag() == .ErrorSet)1726 const signedness: std.builtin.Signedness = if (ty.isUnsignedInt() or
1727 ty.zigTypeTag() == .ErrorSet or
1728 ty.zigTypeTag() == .Bool)
1674 .unsigned1729 .unsigned
1675 else1730 else
1676 .signed;1731 .signed;
...@@ -1684,6 +1739,10 @@ fn load(self: *Self, operand: WValue, ty: Type, offset: u32) InnerError!WValue {...@@ -1684,6 +1739,10 @@ fn load(self: *Self, operand: WValue, ty: Type, offset: u32) InnerError!WValue {
1684 .Bool,1739 .Bool,
1685 .ErrorUnion,1740 .ErrorUnion,
1686 => @intCast(u8, ty.abiSize(self.target)),1741 => @intCast(u8, ty.abiSize(self.target)),
1742 .Optional => blk: {
1743 if (ty.isPtrLikeOptional()) break :blk @intCast(u8, self.ptrSize());
1744 break :blk @intCast(u8, ty.abiSize(self.target));
1745 },
1687 else => @as(u8, 4),1746 else => @as(u8, 4),
1688 };1747 };
16891748
...@@ -1828,7 +1887,7 @@ fn emitConstant(self: *Self, val: Value, ty: Type) InnerError!void {...@@ -1828,7 +1887,7 @@ fn emitConstant(self: *Self, val: Value, ty: Type) InnerError!void {
1828 }1887 }
1829 } else if (val.castTag(.int_u64)) |int_ptr| {1888 } else if (val.castTag(.int_u64)) |int_ptr| {
1830 try self.addImm32(@bitCast(i32, @intCast(u32, int_ptr.data)));1889 try self.addImm32(@bitCast(i32, @intCast(u32, int_ptr.data)));
1831 } else if (val.tag() == .zero) {1890 } else if (val.tag() == .zero or val.tag() == .null_value) {
1832 try self.addImm32(0);1891 try self.addImm32(0);
1833 } else if (val.tag() == .one) {1892 } else if (val.tag() == .one) {
1834 try self.addImm32(1);1893 try self.addImm32(1);
...@@ -1886,18 +1945,19 @@ fn emitConstant(self: *Self, val: Value, ty: Type) InnerError!void {...@@ -1886,18 +1945,19 @@ fn emitConstant(self: *Self, val: Value, ty: Type) InnerError!void {
1886 var buf: Type.Payload.ElemType = undefined;1945 var buf: Type.Payload.ElemType = undefined;
1887 const payload_type = ty.optionalChild(&buf);1946 const payload_type = ty.optionalChild(&buf);
1888 if (ty.isPtrLikeOptional()) {1947 if (ty.isPtrLikeOptional()) {
1889 return self.fail("Wasm TODO: emitConstant for optional pointer", .{});1948 try self.emitConstant(val, payload_type);
1949 return;
1890 }1950 }
18911951
1892 // When constant has value 'null', set is_null local to '1'1952 // When constant has value 'null', set is_null local to '1'
1893 // and payload to '0'1953 // and payload to '0'
1894 if (val.castTag(.opt_payload)) |payload| {1954 if (val.castTag(.opt_payload)) |payload| {
1895 try self.addImm32(0);1955 try self.addImm32(1);
1896 if (payload_type.hasCodeGenBits())1956 if (payload_type.hasCodeGenBits())
1897 try self.emitConstant(payload.data, payload_type);1957 try self.emitConstant(payload.data, payload_type);
1898 } else {1958 } else {
1899 // set null-tag1959 // set null-tag
1900 try self.addImm32(1);1960 try self.addImm32(0);
1901 // null-tag is set, so write a '0' const1961 // null-tag is set, so write a '0' const
1902 try self.addImm32(0);1962 try self.addImm32(0);
1903 }1963 }
...@@ -1908,11 +1968,16 @@ fn emitConstant(self: *Self, val: Value, ty: Type) InnerError!void {...@@ -1908,11 +1968,16 @@ fn emitConstant(self: *Self, val: Value, ty: Type) InnerError!void {
1908 const result = try self.allocStack(ty);1968 const result = try self.allocStack(ty);
19091969
1910 const fields = ty.structFields();1970 const fields = ty.structFields();
1971 var offset: u32 = 0;
1911 for (fields.values()) |field, index| {1972 for (fields.values()) |field, index| {
1973 if (isByRef(field.ty)) {
1974 return self.fail("TODO: emitConstant for struct field type {}\n", .{field.ty});
1975 }
1912 const tmp = try self.allocLocal(field.ty);1976 const tmp = try self.allocLocal(field.ty);
1913 try self.emitConstant(struct_data.data[index], field.ty);1977 try self.emitConstant(struct_data.data[index], field.ty);
1914 try self.addLabel(.local_set, tmp.local);1978 try self.addLabel(.local_set, tmp.local);
1915 try self.store(result, tmp, field.ty, field.offset);1979 try self.store(result, tmp, field.ty, offset);
1980 offset += @intCast(u32, field.ty.abiSize(self.target));
1916 }1981 }
1917 try self.addLabel(.local_get, result.local);1982 try self.addLabel(.local_get, result.local);
1918 },1983 },
...@@ -1936,10 +2001,15 @@ fn emitUndefined(self: *Self, ty: Type) InnerError!void {...@@ -1936,10 +2001,15 @@ fn emitUndefined(self: *Self, ty: Type) InnerError!void {
1936 // validator will not accept it due to out-of-bounds memory access);2001 // validator will not accept it due to out-of-bounds memory access);
1937 .Array => try self.addImm32(@bitCast(i32, @as(u32, 0xaa))),2002 .Array => try self.addImm32(@bitCast(i32, @as(u32, 0xaa))),
1938 .Struct => {2003 .Struct => {
1939 // TODO: Write 0xaa to each field2004 // TODO: Write 0xaa struct's memory
1940 const result = try self.allocStack(ty);2005 const result = try self.allocStack(ty);
1941 try self.addLabel(.local_get, result.local);2006 try self.addLabel(.local_get, result.local);
1942 },2007 },
2008 .Pointer => switch (self.ptrSize()) {
2009 4 => try self.addImm32(@bitCast(i32, @as(u32, 0xaaaaaaaa))),
2010 8 => try self.addImm64(0xaaaaaaaaaaaaaaaa),
2011 else => unreachable,
2012 },
1943 else => return self.fail("Wasm TODO: emitUndefined for type: {}\n", .{ty}),2013 else => return self.fail("Wasm TODO: emitUndefined for type: {}\n", .{ty}),
1944 }2014 }
1945}2015}
...@@ -2065,23 +2135,34 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -2065,23 +2135,34 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
2065}2135}
20662136
2067fn airCmp(self: *Self, inst: Air.Inst.Index, op: std.math.CompareOperator) InnerError!WValue {2137fn airCmp(self: *Self, inst: Air.Inst.Index, op: std.math.CompareOperator) InnerError!WValue {
2068 const data: Air.Inst.Data = self.air.instructions.items(.data)[inst];2138 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
2069 const lhs = self.resolveInst(data.bin_op.lhs);2139 const lhs = self.resolveInst(bin_op.lhs);
2070 const rhs = self.resolveInst(data.bin_op.rhs);2140 const rhs = self.resolveInst(bin_op.rhs);
2071 const lhs_ty = self.air.typeOf(data.bin_op.lhs);2141 const operand_ty = self.air.typeOf(bin_op.lhs);
20722142
2073 try self.emitWValue(lhs);2143 try self.emitWValue(lhs);
2074 try self.emitWValue(rhs);2144 try self.emitWValue(rhs);
20752145
2146 if (operand_ty.zigTypeTag() == .Optional and !operand_ty.isPtrLikeOptional()) {
2147 var buf: Type.Payload.ElemType = undefined;
2148 const payload_ty = operand_ty.optionalChild(&buf);
2149 if (payload_ty.hasCodeGenBits()) {
2150 // When we hit this case, we must check the value of optionals
2151 // that are not pointers. This means first checking against non-null for
2152 // both lhs and rhs, as well as checking the payload are matching of lhs and rhs
2153 return self.fail("TODO: Implement airCmp for comparing optionals", .{});
2154 }
2155 }
2156
2076 const signedness: std.builtin.Signedness = blk: {2157 const signedness: std.builtin.Signedness = blk: {
2077 // by default we tell the operand type is unsigned (i.e. bools and enum values)2158 // by default we tell the operand type is unsigned (i.e. bools and enum values)
2078 if (lhs_ty.zigTypeTag() != .Int) break :blk .unsigned;2159 if (operand_ty.zigTypeTag() != .Int) break :blk .unsigned;
20792160
2080 // incase of an actual integer, we emit the correct signedness2161 // incase of an actual integer, we emit the correct signedness
2081 break :blk lhs_ty.intInfo(self.target).signedness;2162 break :blk operand_ty.intInfo(self.target).signedness;
2082 };2163 };
2083 const opcode: wasm.Opcode = buildOpcode(.{2164 const opcode: wasm.Opcode = buildOpcode(.{
2084 .valtype1 = try self.typeToValtype(lhs_ty),2165 .valtype1 = try self.typeToValtype(operand_ty),
2085 .op = switch (op) {2166 .op = switch (op) {
2086 .lt => .lt,2167 .lt => .lt,
2087 .lte => .le,2168 .lte => .le,
...@@ -2132,7 +2213,7 @@ fn airNot(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -2132,7 +2213,7 @@ fn airNot(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
2132 try self.addTag(.i32_eq);2213 try self.addTag(.i32_eq);
21332214
2134 // save the result in the local2215 // save the result in the local
2135 const not_tmp = try self.allocLocal(self.air.getRefType(ty_op.ty));2216 const not_tmp = try self.allocLocal(Type.initTag(.i32));
2136 try self.addLabel(.local_set, not_tmp.local);2217 try self.addLabel(.local_set, not_tmp.local);
2137 return not_tmp;2218 return not_tmp;
2138}2219}
...@@ -2173,7 +2254,7 @@ fn airStructFieldPtr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -2173,7 +2254,7 @@ fn airStructFieldPtr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
2173 struct_ty.structFieldType(extra.data.field_index),2254 struct_ty.structFieldType(extra.data.field_index),
2174 });2255 });
2175 };2256 };
2176 return structFieldPtr(struct_ptr, offset);2257 return self.structFieldPtr(struct_ptr, offset);
2177}2258}
21782259
2179fn airStructFieldPtrIndex(self: *Self, inst: Air.Inst.Index, index: u32) InnerError!WValue {2260fn airStructFieldPtrIndex(self: *Self, inst: Air.Inst.Index, index: u32) InnerError!WValue {
...@@ -2186,10 +2267,10 @@ fn airStructFieldPtrIndex(self: *Self, inst: Air.Inst.Index, index: u32) InnerEr...@@ -2186,10 +2267,10 @@ fn airStructFieldPtrIndex(self: *Self, inst: Air.Inst.Index, index: u32) InnerEr
2186 field_ty,2267 field_ty,
2187 });2268 });
2188 };2269 };
2189 return structFieldPtr(struct_ptr, offset);2270 return self.structFieldPtr(struct_ptr, offset);
2190}2271}
21912272
2192fn structFieldPtr(struct_ptr: WValue, offset: u32) InnerError!WValue {2273fn structFieldPtr(self: *Self, struct_ptr: WValue, offset: u32) InnerError!WValue {
2193 var final_offset = offset;2274 var final_offset = offset;
2194 const local = switch (struct_ptr) {2275 const local = switch (struct_ptr) {
2195 .local => |local| local,2276 .local => |local| local,
...@@ -2199,7 +2280,7 @@ fn structFieldPtr(struct_ptr: WValue, offset: u32) InnerError!WValue {...@@ -2199,7 +2280,7 @@ fn structFieldPtr(struct_ptr: WValue, offset: u32) InnerError!WValue {
2199 },2280 },
2200 else => unreachable,2281 else => unreachable,
2201 };2282 };
2202 return WValue{ .local_with_offset = .{ .local = local, .offset = final_offset } };2283 return self.buildPointerOffset(.{ .local = local }, final_offset);
2203}2284}
22042285
2205fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) InnerError!WValue {2286fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
...@@ -2434,24 +2515,13 @@ fn airWrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -2434,24 +2515,13 @@ fn airWrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
2434 const offset = err_ty.errorUnionSet().abiSize(self.target);2515 const offset = err_ty.errorUnionSet().abiSize(self.target);
24352516
2436 const err_union = try self.allocStack(err_ty);2517 const err_union = try self.allocStack(err_ty);
2437 const to_store = switch (op_ty.zigTypeTag()) {2518 const payload_ptr = try self.buildPointerOffset(err_union, offset);
2438 // for those types we must load the pointer and then store2519 try self.store(payload_ptr, operand, op_ty, 0);
2439 // its value
2440 .Pointer, .Optional => blk: {
2441 if (!op_ty.isPtrLikeOptional()) {
2442 return self.fail("TODO: airWrapErrUnionPayload for optional type {}", .{op_ty});
2443 }
2444 break :blk try self.load(operand, op_ty, 0);
2445 },
2446 .Int => operand,
2447 else => return self.fail("TODO: airWrapErrUnionPayload for type {}", .{op_ty}),
2448 };
2449
2450 try self.store(err_union, to_store, op_ty, @intCast(u32, offset));
24512520
2452 // ensure we also write '0' to the error part, so any present stack value gets overwritten by it.2521 // ensure we also write '0' to the error part, so any present stack value gets overwritten by it.
2453 const tmp_local = try self.allocLocal(err_ty.errorUnionSet()); // locals are '0' by default.2522 try self.addLabel(.local_get, err_union.local);
2454 try self.store(err_union, tmp_local, err_ty.errorUnionSet(), 0);2523 try self.addImm32(0);
2524 try self.addMemArg(.i32_store16, .{ .offset = 0, .alignment = 2 });
24552525
2456 return err_union;2526 return err_union;
2457}2527}
...@@ -2499,64 +2569,140 @@ fn airIntcast(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -2499,64 +2569,140 @@ fn airIntcast(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
2499 return result;2569 return result;
2500}2570}
25012571
2502fn airIsNull(self: *Self, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!WValue {2572fn airIsNull(self: *Self, inst: Air.Inst.Index, opcode: wasm.Opcode, op_kind: enum { value, ptr }) InnerError!WValue {
2503 const un_op = self.air.instructions.items(.data)[inst].un_op;2573 const un_op = self.air.instructions.items(.data)[inst].un_op;
2504 const operand = self.resolveInst(un_op);2574 const operand = self.resolveInst(un_op);
25052575
2506 const op_ty = self.air.typeOf(un_op);2576 const op_ty = self.air.typeOf(un_op);
2577 const optional_ty = if (op_kind == .ptr) op_ty.childType() else op_ty;
2507 try self.emitWValue(operand);2578 try self.emitWValue(operand);
2508 if (!op_ty.isPtrLikeOptional()) {2579 if (!optional_ty.isPtrLikeOptional()) {
2509 try self.addMemArg(.i32_load8_u, .{ .offset = 0, .alignment = 1 });2580 var buf: Type.Payload.ElemType = undefined;
2581 const payload_ty = optional_ty.optionalChild(&buf);
2582 // When payload is zero-bits, we can treat operand as a value, rather than a
2583 // stack value
2584 if (payload_ty.hasCodeGenBits()) {
2585 try self.addMemArg(.i32_load8_u, .{ .offset = 0, .alignment = 1 });
2586 }
2510 }2587 }
25112588
2512 // Compare the error value with '0'2589 // Compare the null value with '0'
2513 try self.addImm32(0);2590 try self.addImm32(0);
2514 try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));2591 try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));
25152592
2516 const is_null_tmp = try self.allocLocal(Type.initTag(.u8));2593 const is_null_tmp = try self.allocLocal(Type.initTag(.i32));
2517 try self.addLabel(.local_set, is_null_tmp.local);2594 try self.addLabel(.local_set, is_null_tmp.local);
2518 return is_null_tmp;2595 return is_null_tmp;
2519}2596}
25202597
2521fn airOptionalPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue {2598fn airOptionalPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
2599 if (self.liveness.isUnused(inst)) return WValue{ .none = {} };
2522 const ty_op = self.air.instructions.items(.data)[inst].ty_op;2600 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
2523 const operand = self.resolveInst(ty_op.operand);2601 const operand = self.resolveInst(ty_op.operand);
2524 const opt_ty = self.air.typeOf(ty_op.operand);2602 const opt_ty = self.air.typeOf(ty_op.operand);
2603 const payload_ty = self.air.typeOfIndex(inst);
2604 if (!payload_ty.hasCodeGenBits()) return WValue{ .none = {} };
2605 if (opt_ty.isPtrLikeOptional()) return operand;
2606
2607 const offset = opt_ty.abiSize(self.target) - payload_ty.abiSize(self.target);
25252608
2526 // For pointers we simply return its stack address, rather than2609 if (isByRef(payload_ty)) {
2527 // loading its value2610 return self.buildPointerOffset(operand, offset);
2528 if (opt_ty.zigTypeTag() == .Pointer) {
2529 return WValue{ .local_with_offset = .{ .local = operand.local, .offset = 1 } };
2530 }2611 }
25312612
2532 if (opt_ty.isPtrLikeOptional()) return operand;2613 return self.load(operand, payload_ty, @intCast(u32, offset));
2614}
2615
2616fn airOptionalPayloadPtr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
2617 if (self.liveness.isUnused(inst)) return WValue{ .none = {} };
2618
2619 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
2620 const operand = self.resolveInst(ty_op.operand);
2621 const opt_ty = self.air.typeOf(ty_op.operand).childType();
25332622
2534 var buf: Type.Payload.ElemType = undefined;2623 var buf: Type.Payload.ElemType = undefined;
2535 const child_ty = opt_ty.optionalChild(&buf);2624 const payload_ty = opt_ty.optionalChild(&buf);
2536 const offset = opt_ty.abiSize(self.target) - child_ty.abiSize(self.target);2625 if (!payload_ty.hasCodeGenBits() or opt_ty.isPtrLikeOptional()) {
2626 return operand;
2627 }
25372628
2538 return self.load(operand, child_ty, @intCast(u32, offset));2629 const offset = opt_ty.abiSize(self.target) - payload_ty.abiSize(self.target);
2630 return self.buildPointerOffset(operand, offset);
2539}2631}
25402632
2541fn airOptionalPayloadPtrSet(self: *Self, inst: Air.Inst.Index) InnerError!WValue {2633fn airOptionalPayloadPtrSet(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
2542 const ty_op = self.air.instructions.items(.data)[inst].ty_op;2634 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
2543 const operand = self.resolveInst(ty_op.operand);2635 const operand = self.resolveInst(ty_op.operand);
2544 _ = operand;2636 const opt_ty = self.air.typeOf(ty_op.operand).childType();
2545 return self.fail("TODO - wasm codegen for optional_payload_ptr_set", .{});2637 var buf: Type.Payload.ElemType = undefined;
2638 const payload_ty = opt_ty.optionalChild(&buf);
2639 if (!payload_ty.hasCodeGenBits()) {
2640 return self.fail("TODO: Implement OptionalPayloadPtrSet for optional with zero-sized type {}", .{payload_ty});
2641 }
2642
2643 if (opt_ty.isPtrLikeOptional()) {
2644 return operand;
2645 }
2646
2647 const offset = std.math.cast(u32, opt_ty.abiSize(self.target) - payload_ty.abiSize(self.target)) catch {
2648 return self.fail("Optional type {} too big to fit into stack frame", .{opt_ty});
2649 };
2650
2651 try self.emitWValue(operand);
2652 try self.addImm32(1);
2653 try self.addMemArg(.i32_store8, .{ .offset = 0, .alignment = 1 });
2654
2655 return self.buildPointerOffset(operand, offset);
2546}2656}
25472657
2548fn airWrapOptional(self: *Self, inst: Air.Inst.Index) InnerError!WValue {2658fn airWrapOptional(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
2659 if (self.liveness.isUnused(inst)) return WValue{ .none = {} };
2660
2549 const ty_op = self.air.instructions.items(.data)[inst].ty_op;2661 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
2662 const payload_ty = self.air.typeOf(ty_op.operand);
2663 if (!payload_ty.hasCodeGenBits()) {
2664 const non_null_bit = try self.allocStack(Type.initTag(.u1));
2665 try self.addLabel(.local_get, non_null_bit.local);
2666 try self.addImm32(1);
2667 try self.addMemArg(.i32_store8, .{ .offset = 0, .alignment = 1 });
2668 return non_null_bit;
2669 }
2670
2550 const operand = self.resolveInst(ty_op.operand);2671 const operand = self.resolveInst(ty_op.operand);
2672 const op_ty = self.air.typeOfIndex(inst);
2673 if (op_ty.isPtrLikeOptional()) {
2674 return operand;
2675 }
2676 const offset = std.math.cast(u32, op_ty.abiSize(self.target) - payload_ty.abiSize(self.target)) catch {
2677 return self.fail("Optional type {} too big to fit into stack frame", .{op_ty});
2678 };
25512679
2552 const op_ty = self.air.typeOf(ty_op.operand);2680 // Create optional type, set the non-null bit, and store the operand inside the optional type
2553 const optional_ty = self.air.getRefType(ty_op.ty);2681 const result = try self.allocStack(op_ty);
2554 const offset = optional_ty.abiSize(self.target) - op_ty.abiSize(self.target);2682 try self.addLabel(.local_get, result.local);
2683 try self.addImm32(1);
2684 try self.addMemArg(.i32_store8, .{ .offset = 0, .alignment = 1 });
2685
2686 const payload_ptr = try self.buildPointerOffset(result, offset);
2687 try self.store(payload_ptr, operand, payload_ty, 0);
25552688
2556 return WValue{ .local_with_offset = .{2689 return result;
2557 .local = operand.local,2690}
2558 .offset = @intCast(u32, offset),2691
2559 } };2692fn airSlice(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
2693 if (self.liveness.isUnused(inst)) return WValue{ .none = {} };
2694
2695 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
2696 const bin_op = self.air.extraData(Air.Bin, ty_pl.payload).data;
2697 const lhs = self.resolveInst(bin_op.lhs);
2698 const rhs = self.resolveInst(bin_op.rhs);
2699 const slice_ty = self.air.typeOfIndex(inst);
2700
2701 const slice = try self.allocStack(slice_ty);
2702 try self.store(slice, lhs, Type.usize, 0);
2703 try self.store(slice, rhs, Type.usize, self.ptrSize());
2704
2705 return slice;
2560}2706}
25612707
2562fn airSliceLen(self: *Self, inst: Air.Inst.Index) InnerError!WValue {2708fn airSliceLen(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
...@@ -2809,3 +2955,71 @@ fn airPtrBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {...@@ -2809,3 +2955,71 @@ fn airPtrBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {
2809 try self.addLabel(.local_set, result.local);2955 try self.addLabel(.local_set, result.local);
2810 return result;2956 return result;
2811}2957}
2958
2959fn airMemset(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
2960 const pl_op = self.air.instructions.items(.data)[inst].pl_op;
2961 const bin_op = self.air.extraData(Air.Bin, pl_op.payload).data;
2962
2963 const ptr = self.resolveInst(pl_op.operand);
2964 const value = self.resolveInst(bin_op.lhs);
2965 const len = self.resolveInst(bin_op.rhs);
2966 try self.memSet(ptr, len, value);
2967
2968 return WValue.none;
2969}
2970
2971/// Sets a region of memory at `ptr` to the value of `value`
2972/// When the user has enabled the bulk_memory feature, we lower
2973/// this to wasm's memset instruction. When the feature is not present,
2974/// we implement it manually.
2975fn memSet(self: *Self, ptr: WValue, len: WValue, value: WValue) InnerError!void {
2976 // When bulk_memory is enabled, we lower it to wasm's memset instruction.
2977 // If not, we lower it ourselves
2978 if (std.Target.wasm.featureSetHas(self.target.cpu.features, .bulk_memory)) {
2979 try self.emitWValue(ptr);
2980 try self.emitWValue(value);
2981 try self.emitWValue(len);
2982 try self.addExtended(.memory_fill);
2983 return;
2984 }
2985
2986 // TODO: We should probably lower this to a call to compiler_rt
2987 // But for now, we implement it manually
2988 const offset = try self.allocLocal(Type.usize); // local for counter
2989 // outer block to jump to when loop is done
2990 try self.startBlock(.block, wasm.block_empty);
2991 try self.startBlock(.loop, wasm.block_empty);
2992 try self.emitWValue(offset);
2993 try self.emitWValue(len);
2994 switch (self.ptrSize()) {
2995 4 => try self.addTag(.i32_eq),
2996 8 => try self.addTag(.i64_eq),
2997 else => unreachable,
2998 }
2999 try self.addLabel(.br_if, 1); // jump out of loop into outer block (finished)
3000 try self.emitWValue(ptr);
3001 try self.emitWValue(offset);
3002 switch (self.ptrSize()) {
3003 4 => try self.addTag(.i32_add),
3004 8 => try self.addTag(.i64_add),
3005 else => unreachable,
3006 }
3007 try self.emitWValue(value);
3008 const mem_store_op: Mir.Inst.Tag = switch (self.ptrSize()) {
3009 4 => .i32_store8,
3010 8 => .i64_store8,
3011 else => unreachable,
3012 };
3013 try self.addMemArg(mem_store_op, .{ .offset = 0, .alignment = 1 });
3014 try self.emitWValue(offset);
3015 try self.addImm32(1);
3016 switch (self.ptrSize()) {
3017 4 => try self.addTag(.i32_add),
3018 8 => try self.addTag(.i64_add),
3019 else => unreachable,
3020 }
3021 try self.addLabel(.local_set, offset.local);
3022 try self.addLabel(.br, 0); // jump to start of loop
3023 try self.endBlock();
3024 try self.endBlock();
3025}
src/arch/wasm/Emit.zig+19
...@@ -161,6 +161,8 @@ pub fn emitMir(emit: *Emit) InnerError!void {...@@ -161,6 +161,8 @@ pub fn emitMir(emit: *Emit) InnerError!void {
161 .i64_extend8_s => try emit.emitTag(tag),161 .i64_extend8_s => try emit.emitTag(tag),
162 .i64_extend16_s => try emit.emitTag(tag),162 .i64_extend16_s => try emit.emitTag(tag),
163 .i64_extend32_s => try emit.emitTag(tag),163 .i64_extend32_s => try emit.emitTag(tag),
164
165 .extended => try emit.emitExtended(inst),
164 }166 }
165 }167 }
166}168}
...@@ -321,3 +323,20 @@ fn emitMemAddress(emit: *Emit, inst: Mir.Inst.Index) !void {...@@ -321,3 +323,20 @@ fn emitMemAddress(emit: *Emit, inst: Mir.Inst.Index) !void {
321 .relocation_type = .R_WASM_MEMORY_ADDR_LEB,323 .relocation_type = .R_WASM_MEMORY_ADDR_LEB,
322 });324 });
323}325}
326
327fn emitExtended(emit: *Emit, inst: Mir.Inst.Index) !void {
328 const opcode = emit.mir.instructions.items(.secondary)[inst];
329 switch (@intToEnum(std.wasm.PrefixedOpcode, opcode)) {
330 .memory_fill => try emit.emitMemFill(),
331 else => |tag| return emit.fail("TODO: Implement extension instruction: {s}\n", .{@tagName(tag)}),
332 }
333}
334
335fn emitMemFill(emit: *Emit) !void {
336 try emit.code.append(0xFC);
337 try emit.code.append(0x0B);
338 // When multi-memory proposal reaches phase 4, we
339 // can emit a different memory index here.
340 // For now we will always emit index 0.
341 try leb128.writeULEB128(emit.code.writer(), @as(u32, 0));
342}
src/arch/wasm/Mir.zig+8
...@@ -19,6 +19,9 @@ extra: []const u32,...@@ -19,6 +19,9 @@ extra: []const u32,
19pub const Inst = struct {19pub const Inst = struct {
20 /// The opcode that represents this instruction20 /// The opcode that represents this instruction
21 tag: Tag,21 tag: Tag,
22 /// This opcode will be set when `tag` represents an extended
23 /// instruction with prefix 0xFC, or a simd instruction with prefix 0xFD.
24 secondary: u8 = 0,
22 /// Data is determined by the set `tag`.25 /// Data is determined by the set `tag`.
23 /// For example, `data` will be an i32 for when `tag` is 'i32_const'.26 /// For example, `data` will be an i32 for when `tag` is 'i32_const'.
24 data: Data,27 data: Data,
...@@ -373,6 +376,11 @@ pub const Inst = struct {...@@ -373,6 +376,11 @@ pub const Inst = struct {
373 i64_extend16_s = 0xC3,376 i64_extend16_s = 0xC3,
374 /// Uses `tag`377 /// Uses `tag`
375 i64_extend32_s = 0xC4,378 i64_extend32_s = 0xC4,
379 /// The instruction consists of an extension opcode
380 /// set in `secondary`
381 ///
382 /// The `data` field depends on the extension instruction
383 extended = 0xFC,
376 /// Contains a symbol to a function pointer384 /// Contains a symbol to a function pointer
377 /// uses `label`385 /// uses `label`
378 ///386 ///
test/behavior.zig+8-8
...@@ -39,16 +39,23 @@ test {...@@ -39,16 +39,23 @@ test {
39 _ = @import("behavior/defer.zig");39 _ = @import("behavior/defer.zig");
40 _ = @import("behavior/enum.zig");40 _ = @import("behavior/enum.zig");
41 _ = @import("behavior/error.zig");41 _ = @import("behavior/error.zig");
42 _ = @import("behavior/generics.zig");
42 _ = @import("behavior/if.zig");43 _ = @import("behavior/if.zig");
43 _ = @import("behavior/import.zig");44 _ = @import("behavior/import.zig");
44 _ = @import("behavior/incomplete_struct_param_tld.zig");45 _ = @import("behavior/incomplete_struct_param_tld.zig");
45 _ = @import("behavior/inttoptr.zig");46 _ = @import("behavior/inttoptr.zig");
47 _ = @import("behavior/member_func.zig");
48 _ = @import("behavior/null.zig");
46 _ = @import("behavior/pointers.zig");49 _ = @import("behavior/pointers.zig");
47 _ = @import("behavior/ptrcast.zig");50 _ = @import("behavior/ptrcast.zig");
48 _ = @import("behavior/ref_var_in_if_after_if_2nd_switch_prong.zig");51 _ = @import("behavior/ref_var_in_if_after_if_2nd_switch_prong.zig");
52 _ = @import("behavior/struct.zig");
53 _ = @import("behavior/this.zig");
49 _ = @import("behavior/truncate.zig");54 _ = @import("behavior/truncate.zig");
50 _ = @import("behavior/usingnamespace.zig");
51 _ = @import("behavior/underscore.zig");55 _ = @import("behavior/underscore.zig");
56 _ = @import("behavior/usingnamespace.zig");
57 _ = @import("behavior/void.zig");
58 _ = @import("behavior/while.zig");
5259
53 if (!builtin.zig_is_stage2 or builtin.stage2_arch != .wasm32) {60 if (!builtin.zig_is_stage2 or builtin.stage2_arch != .wasm32) {
54 // Tests that pass for stage1, llvm backend, C backend61 // Tests that pass for stage1, llvm backend, C backend
...@@ -56,16 +63,9 @@ test {...@@ -56,16 +63,9 @@ test {
56 _ = @import("behavior/array.zig");63 _ = @import("behavior/array.zig");
57 _ = @import("behavior/cast.zig");64 _ = @import("behavior/cast.zig");
58 _ = @import("behavior/for.zig");65 _ = @import("behavior/for.zig");
59 _ = @import("behavior/generics.zig");
60 _ = @import("behavior/int128.zig");66 _ = @import("behavior/int128.zig");
61 _ = @import("behavior/member_func.zig");
62 _ = @import("behavior/null.zig");
63 _ = @import("behavior/optional.zig");67 _ = @import("behavior/optional.zig");
64 _ = @import("behavior/struct.zig");
65 _ = @import("behavior/this.zig");
66 _ = @import("behavior/translate_c_macros.zig");68 _ = @import("behavior/translate_c_macros.zig");
67 _ = @import("behavior/while.zig");
68 _ = @import("behavior/void.zig");
6969
70 if (builtin.object_format != .c) {70 if (builtin.object_format != .c) {
71 // Tests that pass for stage1 and the llvm backend.71 // Tests that pass for stage1 and the llvm backend.