| ... | @@ -979,10 +979,15 @@ pub const Context = struct { | ... | @@ -979,10 +979,15 @@ pub const Context = struct { |
| 979 | .valtype1 = try self.typeToValtype(ty), | 979 | .valtype1 = try self.typeToValtype(ty), |
| 980 | }); | 980 | }); |
| 981 | try writer.writeByte(wasm.opcode(opcode)); | 981 | try writer.writeByte(wasm.opcode(opcode)); |
| | 982 | const int_info = ty.intInfo(self.target); |
| 982 | // write constant | 983 | // write constant |
| 983 | switch (ty.intInfo(self.target).signedness) { | 984 | switch (int_info.signedness) { |
| 984 | .signed => try leb.writeILEB128(writer, value.toSignedInt()), | 985 | .signed => try leb.writeILEB128(writer, value.toSignedInt()), |
| 985 | .unsigned => try leb.writeILEB128(writer, value.toUnsignedInt()), | 986 | .unsigned => switch (int_info.bits) { |
| | 987 | 0...32 => try leb.writeILEB128(writer, @bitCast(i32, @intCast(u32, value.toUnsignedInt()))), |
| | 988 | 33...64 => try leb.writeILEB128(writer, @bitCast(i64, value.toUnsignedInt())), |
| | 989 | else => |bits| return self.fail("Wasm TODO: emitConstant for integer with {d} bits", .{bits}), |
| | 990 | }, |
| 986 | } | 991 | } |
| 987 | }, | 992 | }, |
| 988 | .Bool => { | 993 | .Bool => { |
| ... | @@ -1079,6 +1084,42 @@ pub const Context = struct { | ... | @@ -1079,6 +1084,42 @@ pub const Context = struct { |
| 1079 | } | 1084 | } |
| 1080 | } | 1085 | } |
| 1081 | | 1086 | |
| | 1087 | /// Returns a `Value` as a signed 32 bit value. |
| | 1088 | /// It's illegale to provide a value with a type that cannot be represented |
| | 1089 | /// as an integer value. |
| | 1090 | fn valueAsI32(self: Context, val: Value, ty: Type) i32 { |
| | 1091 | switch (ty.zigTypeTag()) { |
| | 1092 | .Enum => { |
| | 1093 | if (val.castTag(.enum_field_index)) |field_index| { |
| | 1094 | switch (ty.tag()) { |
| | 1095 | .enum_simple => return @bitCast(i32, field_index.data), |
| | 1096 | .enum_full, .enum_nonexhaustive => { |
| | 1097 | const enum_full = ty.cast(Type.Payload.EnumFull).?.data; |
| | 1098 | if (enum_full.values.count() != 0) { |
| | 1099 | const tag_val = enum_full.values.keys()[field_index.data]; |
| | 1100 | return self.valueAsI32(tag_val, enum_full.tag_ty); |
| | 1101 | } else return @bitCast(i32, field_index.data); |
| | 1102 | }, |
| | 1103 | else => unreachable, |
| | 1104 | } |
| | 1105 | } else { |
| | 1106 | var int_tag_buffer: Type.Payload.Bits = undefined; |
| | 1107 | const int_tag_ty = ty.intTagType(&int_tag_buffer); |
| | 1108 | return self.valueAsI32(val, int_tag_ty); |
| | 1109 | } |
| | 1110 | }, |
| | 1111 | .Int => switch (ty.intInfo(self.target).signedness) { |
| | 1112 | .signed => return @truncate(i32, val.toSignedInt()), |
| | 1113 | .unsigned => return @bitCast(i32, @truncate(u32, val.toUnsignedInt())), |
| | 1114 | }, |
| | 1115 | .ErrorSet => { |
| | 1116 | const error_index = self.global_error_set.get(val.getError().?).?; |
| | 1117 | return @bitCast(i32, error_index); |
| | 1118 | }, |
| | 1119 | else => unreachable, // Programmer called this function for an illegal type |
| | 1120 | } |
| | 1121 | } |
| | 1122 | |
| 1082 | fn airBlock(self: *Context, inst: Air.Inst.Index) InnerError!WValue { | 1123 | fn airBlock(self: *Context, inst: Air.Inst.Index) InnerError!WValue { |
| 1083 | const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; | 1124 | const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; |
| 1084 | const block_ty = try self.genBlockType(self.air.getRefType(ty_pl.ty)); | 1125 | const block_ty = try self.genBlockType(self.air.getRefType(ty_pl.ty)); |
| ... | @@ -1271,60 +1312,151 @@ pub const Context = struct { | ... | @@ -1271,60 +1312,151 @@ pub const Context = struct { |
| 1271 | } | 1312 | } |
| 1272 | | 1313 | |
| 1273 | fn airSwitchBr(self: *Context, inst: Air.Inst.Index) InnerError!WValue { | 1314 | fn airSwitchBr(self: *Context, inst: Air.Inst.Index) InnerError!WValue { |
| | 1315 | // result type is always 'noreturn' |
| | 1316 | const blocktype = wasm.block_empty; |
| 1274 | const pl_op = self.air.instructions.items(.data)[inst].pl_op; | 1317 | const pl_op = self.air.instructions.items(.data)[inst].pl_op; |
| 1275 | const extra = self.air.extraData(Air.SwitchBr, pl_op.payload); | | |
| 1276 | const cases = self.air.extra[extra.end..][0..extra.data.cases_len]; | | |
| 1277 | const else_body = self.air.extra[extra.end + cases.len ..][0..extra.data.else_body_len]; | | |
| 1278 | | | |
| 1279 | const target = self.resolveInst(pl_op.operand); | 1318 | const target = self.resolveInst(pl_op.operand); |
| 1280 | const target_ty = self.air.typeOf(pl_op.operand); | 1319 | const target_ty = self.air.typeOf(pl_op.operand); |
| 1281 | const valtype = try self.typeToValtype(target_ty); | 1320 | const switch_br = self.air.extraData(Air.SwitchBr, pl_op.payload); |
| 1282 | // result type is always 'noreturn' | 1321 | var extra_index: usize = switch_br.end; |
| 1283 | const blocktype = wasm.block_empty; | 1322 | var case_i: u32 = 0; |
| | 1323 | |
| | 1324 | // a list that maps each value with its value and body based on the order inside the list. |
| | 1325 | const CaseValue = struct { integer: i32, value: Value }; |
| | 1326 | var case_list = try std.ArrayList(struct { |
| | 1327 | values: []const CaseValue, |
| | 1328 | body: []const Air.Inst.Index, |
| | 1329 | }).initCapacity(self.gpa, switch_br.data.cases_len); |
| | 1330 | defer for (case_list.items) |case| { |
| | 1331 | self.gpa.free(case.values); |
| | 1332 | } else case_list.deinit(); |
| | 1333 | |
| | 1334 | var lowest: i32 = 0; |
| | 1335 | var highest: i32 = 0; |
| | 1336 | while (case_i < switch_br.data.cases_len) : (case_i += 1) { |
| | 1337 | const case = self.air.extraData(Air.SwitchBr.Case, extra_index); |
| | 1338 | const items = @bitCast([]const Air.Inst.Ref, self.air.extra[case.end..][0..case.data.items_len]); |
| | 1339 | const case_body = self.air.extra[case.end + items.len ..][0..case.data.body_len]; |
| | 1340 | extra_index = case.end + items.len + case_body.len; |
| | 1341 | const values = try self.gpa.alloc(CaseValue, items.len); |
| | 1342 | errdefer self.gpa.free(values); |
| | 1343 | |
| | 1344 | for (items) |ref, i| { |
| | 1345 | const item_val = self.air.value(ref).?; |
| | 1346 | const int_val = self.valueAsI32(item_val, target_ty); |
| | 1347 | if (int_val < lowest) { |
| | 1348 | lowest = int_val; |
| | 1349 | } |
| | 1350 | if (int_val > highest) { |
| | 1351 | highest = int_val; |
| | 1352 | } |
| | 1353 | values[i] = .{ .integer = int_val, .value = item_val }; |
| | 1354 | } |
| | 1355 | |
| | 1356 | case_list.appendAssumeCapacity(.{ .values = values, .body = case_body }); |
| | 1357 | try self.startBlock(.block, blocktype, null); |
| | 1358 | } |
| | 1359 | |
| | 1360 | // When the highest and lowest values are seperated by '50', |
| | 1361 | // we define it as sparse and use an if/else-chain, rather than a jump table. |
| | 1362 | // When the target is an integer size larger than u32, we have no way to use the value |
| | 1363 | // as an index, therefore we also use an if/else-chain for those cases. |
| | 1364 | // TODO: Benchmark this to find a proper value, LLVM seems to draw the line at '40~45'. |
| | 1365 | const is_sparse = highest - lowest > 50 or target_ty.bitSize(self.target) > 32; |
| | 1366 | |
| | 1367 | const else_body = self.air.extra[extra_index..][0..switch_br.data.else_body_len]; |
| | 1368 | const has_else_body = else_body.len != 0; |
| | 1369 | if (has_else_body) { |
| | 1370 | try self.startBlock(.block, blocktype, null); |
| | 1371 | } |
| | 1372 | |
| | 1373 | if (!is_sparse) { |
| | 1374 | // Generate the jump table 'br_table' when the prongs are not sparse. |
| | 1375 | // The value 'target' represents the index into the table. |
| | 1376 | // Each index in the table represents a label to the branch |
| | 1377 | // to jump to. |
| | 1378 | try self.startBlock(.block, blocktype, null); |
| | 1379 | try self.emitWValue(target); |
| | 1380 | if (lowest < 0) { |
| | 1381 | // since br_table works using indexes, starting from '0', we must ensure all values |
| | 1382 | // we put inside, are atleast 0. |
| | 1383 | try self.code.append(wasm.opcode(.i32_const)); |
| | 1384 | try leb.writeILEB128(self.code.writer(), lowest * -1); |
| | 1385 | try self.code.append(wasm.opcode(.i32_add)); |
| | 1386 | } |
| | 1387 | try self.code.append(wasm.opcode(.br_table)); |
| | 1388 | const depth = highest - lowest + @boolToInt(has_else_body); |
| | 1389 | try leb.writeILEB128(self.code.writer(), depth); |
| | 1390 | while (lowest <= highest) : (lowest += 1) { |
| | 1391 | // idx represents the branch we jump to |
| | 1392 | const idx = blk: { |
| | 1393 | for (case_list.items) |case, idx| { |
| | 1394 | for (case.values) |case_value| { |
| | 1395 | if (case_value.integer == lowest) break :blk @intCast(u32, idx); |
| | 1396 | } |
| | 1397 | } |
| | 1398 | break :blk if (has_else_body) case_i else unreachable; |
| | 1399 | }; |
| | 1400 | try leb.writeULEB128(self.code.writer(), idx); |
| | 1401 | } else if (has_else_body) { |
| | 1402 | try leb.writeULEB128(self.code.writer(), @as(u32, case_i)); // default branch |
| | 1403 | } |
| | 1404 | try self.endBlock(); |
| | 1405 | } |
| 1284 | | 1406 | |
| 1285 | _ = valtype; | 1407 | const signedness: std.builtin.Signedness = blk: { |
| 1286 | _ = blocktype; | 1408 | // by default we tell the operand type is unsigned (i.e. bools and enum values) |
| 1287 | _ = target; | 1409 | if (target_ty.zigTypeTag() != .Int) break :blk .unsigned; |
| 1288 | _ = else_body; | 1410 | |
| 1289 | return self.fail("TODO implement wasm codegen for switch", .{}); | 1411 | // incase of an actual integer, we emit the correct signedness |
| 1290 | //const signedness: std.builtin.Signedness = blk: { | 1412 | break :blk target_ty.intInfo(self.target).signedness; |
| 1291 | // // by default we tell the operand type is unsigned (i.e. bools and enum values) | 1413 | }; |
| 1292 | // if (target_ty.zigTypeTag() != .Int) break :blk .unsigned; | 1414 | |
| 1293 | | 1415 | for (case_list.items) |case| { |
| 1294 | // // incase of an actual integer, we emit the correct signedness | 1416 | // when sparse, we use if/else-chain, so emit conditional checks |
| 1295 | // break :blk target_ty.intInfo(self.target).signedness; | 1417 | if (is_sparse) { |
| 1296 | //}; | 1418 | // for single value prong we can emit a simple if |
| 1297 | //for (cases) |case_idx| { | 1419 | if (case.values.len == 1) { |
| 1298 | // const case = self.air.extraData(Air.SwitchBr.Case, case_idx); | 1420 | try self.emitWValue(target); |
| 1299 | // const case_body = self.air.extra[case.end..][0..case.data.body_len]; | 1421 | try self.emitConstant(case.values[0].value, target_ty); |
| 1300 | | 1422 | const opcode = buildOpcode(.{ |
| 1301 | // // create a block for each case, when the condition does not match we break out of it | 1423 | .valtype1 = try self.typeToValtype(target_ty), |
| 1302 | // try self.startBlock(.block, blocktype, null); | 1424 | .op = .ne, // not equal, because we want to jump out of this block if it does not match the condition. |
| 1303 | // try self.emitWValue(target); | 1425 | .signedness = signedness, |
| 1304 | | 1426 | }); |
| 1305 | // const val = self.air.value(case.data.item).?; | 1427 | try self.code.append(wasm.opcode(opcode)); |
| 1306 | // try self.emitConstant(val, target_ty); | 1428 | try self.code.append(wasm.opcode(.br_if)); |
| 1307 | // const opcode = buildOpcode(.{ | 1429 | try leb.writeULEB128(self.code.writer(), @as(u32, 0)); |
| 1308 | // .valtype1 = valtype, | 1430 | } else { |
| 1309 | // .op = .ne, // not equal because we jump out the block if it does not match the condition | 1431 | // in multi-value prongs we must check if any prongs match the target value. |
| 1310 | // .signedness = signedness, | 1432 | try self.startBlock(.block, blocktype, null); |
| 1311 | // }); | 1433 | for (case.values) |value| { |
| 1312 | // try self.code.append(wasm.opcode(opcode)); | 1434 | try self.emitWValue(target); |
| 1313 | // try self.code.append(wasm.opcode(.br_if)); | 1435 | try self.emitConstant(value.value, target_ty); |
| 1314 | // try leb.writeULEB128(self.code.writer(), @as(u32, 0)); | 1436 | const opcode = buildOpcode(.{ |
| 1315 | | 1437 | .valtype1 = try self.typeToValtype(target_ty), |
| 1316 | // // emit our block code | 1438 | .op = .eq, |
| 1317 | // try self.genBody(case_body); | 1439 | .signedness = signedness, |
| 1318 | | 1440 | }); |
| 1319 | // // end the block we created earlier | 1441 | try self.code.append(wasm.opcode(opcode)); |
| 1320 | // try self.endBlock(); | 1442 | try self.code.append(wasm.opcode(.br_if)); |
| 1321 | //} | 1443 | try leb.writeULEB128(self.code.writer(), @as(u32, 0)); |
| 1322 | | 1444 | } |
| 1323 | //// finally, emit the else case if it exists. Here we will not have to | 1445 | // value did not match any of the prong values |
| 1324 | //// check for a condition, so also no need to emit a block. | 1446 | try self.code.append(wasm.opcode(.br)); |
| 1325 | //try self.genBody(else_body); | 1447 | try leb.writeULEB128(self.code.writer(), @as(u32, 1)); |
| 1326 | | 1448 | try self.endBlock(); |
| 1327 | //return .none; | 1449 | } |
| | 1450 | } |
| | 1451 | try self.genBody(case.body); |
| | 1452 | try self.endBlock(); |
| | 1453 | } |
| | 1454 | |
| | 1455 | if (has_else_body) { |
| | 1456 | try self.genBody(else_body); |
| | 1457 | try self.endBlock(); |
| | 1458 | } |
| | 1459 | return .none; |
| 1328 | } | 1460 | } |
| 1329 | | 1461 | |
| 1330 | fn airIsErr(self: *Context, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!WValue { | 1462 | fn airIsErr(self: *Context, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!WValue { |