| ... | ... | @@ -43,6 +43,10 @@ const WValue = union(enum) { |
| 43 | 43 | imm32: u32, |
| 44 | 44 | /// An immediate 64bit value |
| 45 | 45 | imm64: u64, |
| 46 | /// Index into the list of simd128 immediates. This `WValue` is |
| 47 | /// only possible in very rare cases, therefore it would be |
| 48 | /// a waste of memory to store the value in a 128 bit integer. |
| 49 | imm128: u32, |
| 46 | 50 | /// A constant 32bit float value |
| 47 | 51 | float32: f32, |
| 48 | 52 | /// A constant 64bit float value |
| ... | ... | @@ -116,6 +120,7 @@ const WValue = union(enum) { |
| 116 | 120 | .i64 => gen.free_locals_i64.append(gen.gpa, local_value) catch return, |
| 117 | 121 | .f32 => gen.free_locals_f32.append(gen.gpa, local_value) catch return, |
| 118 | 122 | .f64 => gen.free_locals_f64.append(gen.gpa, local_value) catch return, |
| 123 | .v128 => gen.free_locals_v128.append(gen.gpa, local_value) catch return, |
| 119 | 124 | } |
| 120 | 125 | value.* = undefined; |
| 121 | 126 | } |
| ... | ... | @@ -258,18 +263,18 @@ fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode { |
| 258 | 263 | 8 => switch (args.valtype1.?) { |
| 259 | 264 | .i32 => if (args.signedness.? == .signed) return .i32_load8_s else return .i32_load8_u, |
| 260 | 265 | .i64 => if (args.signedness.? == .signed) return .i64_load8_s else return .i64_load8_u, |
| 261 | | .f32, .f64 => unreachable, |
| 266 | .f32, .f64, .v128 => unreachable, |
| 262 | 267 | }, |
| 263 | 268 | 16 => switch (args.valtype1.?) { |
| 264 | 269 | .i32 => if (args.signedness.? == .signed) return .i32_load16_s else return .i32_load16_u, |
| 265 | 270 | .i64 => if (args.signedness.? == .signed) return .i64_load16_s else return .i64_load16_u, |
| 266 | | .f32, .f64 => unreachable, |
| 271 | .f32, .f64, .v128 => unreachable, |
| 267 | 272 | }, |
| 268 | 273 | 32 => switch (args.valtype1.?) { |
| 269 | 274 | .i64 => if (args.signedness.? == .signed) return .i64_load32_s else return .i64_load32_u, |
| 270 | 275 | .i32 => return .i32_load, |
| 271 | 276 | .f32 => return .f32_load, |
| 272 | | .f64 => unreachable, |
| 277 | .f64, .v128 => unreachable, |
| 273 | 278 | }, |
| 274 | 279 | 64 => switch (args.valtype1.?) { |
| 275 | 280 | .i64 => return .i64_load, |
| ... | ... | @@ -282,24 +287,25 @@ fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode { |
| 282 | 287 | .i64 => return .i64_load, |
| 283 | 288 | .f32 => return .f32_load, |
| 284 | 289 | .f64 => return .f64_load, |
| 290 | .v128 => unreachable, // handled independently |
| 285 | 291 | }, |
| 286 | 292 | .store => if (args.width) |width| { |
| 287 | 293 | switch (width) { |
| 288 | 294 | 8 => switch (args.valtype1.?) { |
| 289 | 295 | .i32 => return .i32_store8, |
| 290 | 296 | .i64 => return .i64_store8, |
| 291 | | .f32, .f64 => unreachable, |
| 297 | .f32, .f64, .v128 => unreachable, |
| 292 | 298 | }, |
| 293 | 299 | 16 => switch (args.valtype1.?) { |
| 294 | 300 | .i32 => return .i32_store16, |
| 295 | 301 | .i64 => return .i64_store16, |
| 296 | | .f32, .f64 => unreachable, |
| 302 | .f32, .f64, .v128 => unreachable, |
| 297 | 303 | }, |
| 298 | 304 | 32 => switch (args.valtype1.?) { |
| 299 | 305 | .i64 => return .i64_store32, |
| 300 | 306 | .i32 => return .i32_store, |
| 301 | 307 | .f32 => return .f32_store, |
| 302 | | .f64 => unreachable, |
| 308 | .f64, .v128 => unreachable, |
| 303 | 309 | }, |
| 304 | 310 | 64 => switch (args.valtype1.?) { |
| 305 | 311 | .i64 => return .i64_store, |
| ... | ... | @@ -314,6 +320,7 @@ fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode { |
| 314 | 320 | .i64 => return .i64_store, |
| 315 | 321 | .f32 => return .f32_store, |
| 316 | 322 | .f64 => return .f64_store, |
| 323 | .v128 => unreachable, // handled independently |
| 317 | 324 | } |
| 318 | 325 | }, |
| 319 | 326 | |
| ... | ... | @@ -325,24 +332,27 @@ fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode { |
| 325 | 332 | .i64 => return .i64_const, |
| 326 | 333 | .f32 => return .f32_const, |
| 327 | 334 | .f64 => return .f64_const, |
| 335 | .v128 => unreachable, // handled independently |
| 328 | 336 | }, |
| 329 | 337 | |
| 330 | 338 | .eqz => switch (args.valtype1.?) { |
| 331 | 339 | .i32 => return .i32_eqz, |
| 332 | 340 | .i64 => return .i64_eqz, |
| 333 | | .f32, .f64 => unreachable, |
| 341 | .f32, .f64, .v128 => unreachable, |
| 334 | 342 | }, |
| 335 | 343 | .eq => switch (args.valtype1.?) { |
| 336 | 344 | .i32 => return .i32_eq, |
| 337 | 345 | .i64 => return .i64_eq, |
| 338 | 346 | .f32 => return .f32_eq, |
| 339 | 347 | .f64 => return .f64_eq, |
| 348 | .v128 => unreachable, // handled independently |
| 340 | 349 | }, |
| 341 | 350 | .ne => switch (args.valtype1.?) { |
| 342 | 351 | .i32 => return .i32_ne, |
| 343 | 352 | .i64 => return .i64_ne, |
| 344 | 353 | .f32 => return .f32_ne, |
| 345 | 354 | .f64 => return .f64_ne, |
| 355 | .v128 => unreachable, // handled independently |
| 346 | 356 | }, |
| 347 | 357 | |
| 348 | 358 | .lt => switch (args.valtype1.?) { |
| ... | ... | @@ -350,40 +360,47 @@ fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode { |
| 350 | 360 | .i64 => if (args.signedness.? == .signed) return .i64_lt_s else return .i64_lt_u, |
| 351 | 361 | .f32 => return .f32_lt, |
| 352 | 362 | .f64 => return .f64_lt, |
| 363 | .v128 => unreachable, // handled independently |
| 353 | 364 | }, |
| 354 | 365 | .gt => switch (args.valtype1.?) { |
| 355 | 366 | .i32 => if (args.signedness.? == .signed) return .i32_gt_s else return .i32_gt_u, |
| 356 | 367 | .i64 => if (args.signedness.? == .signed) return .i64_gt_s else return .i64_gt_u, |
| 357 | 368 | .f32 => return .f32_gt, |
| 358 | 369 | .f64 => return .f64_gt, |
| 370 | .v128 => unreachable, // handled independently |
| 359 | 371 | }, |
| 360 | 372 | .le => switch (args.valtype1.?) { |
| 361 | 373 | .i32 => if (args.signedness.? == .signed) return .i32_le_s else return .i32_le_u, |
| 362 | 374 | .i64 => if (args.signedness.? == .signed) return .i64_le_s else return .i64_le_u, |
| 363 | 375 | .f32 => return .f32_le, |
| 364 | 376 | .f64 => return .f64_le, |
| 377 | .v128 => unreachable, // handled independently |
| 365 | 378 | }, |
| 366 | 379 | .ge => switch (args.valtype1.?) { |
| 367 | 380 | .i32 => if (args.signedness.? == .signed) return .i32_ge_s else return .i32_ge_u, |
| 368 | 381 | .i64 => if (args.signedness.? == .signed) return .i64_ge_s else return .i64_ge_u, |
| 369 | 382 | .f32 => return .f32_ge, |
| 370 | 383 | .f64 => return .f64_ge, |
| 384 | .v128 => unreachable, // handled independently |
| 371 | 385 | }, |
| 372 | 386 | |
| 373 | 387 | .clz => switch (args.valtype1.?) { |
| 374 | 388 | .i32 => return .i32_clz, |
| 375 | 389 | .i64 => return .i64_clz, |
| 376 | 390 | .f32, .f64 => unreachable, |
| 391 | .v128 => unreachable, // handled independently |
| 377 | 392 | }, |
| 378 | 393 | .ctz => switch (args.valtype1.?) { |
| 379 | 394 | .i32 => return .i32_ctz, |
| 380 | 395 | .i64 => return .i64_ctz, |
| 381 | 396 | .f32, .f64 => unreachable, |
| 397 | .v128 => unreachable, // handled independently |
| 382 | 398 | }, |
| 383 | 399 | .popcnt => switch (args.valtype1.?) { |
| 384 | 400 | .i32 => return .i32_popcnt, |
| 385 | 401 | .i64 => return .i64_popcnt, |
| 386 | 402 | .f32, .f64 => unreachable, |
| 403 | .v128 => unreachable, // handled independently |
| 387 | 404 | }, |
| 388 | 405 | |
| 389 | 406 | .add => switch (args.valtype1.?) { |
| ... | ... | @@ -391,18 +408,21 @@ fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode { |
| 391 | 408 | .i64 => return .i64_add, |
| 392 | 409 | .f32 => return .f32_add, |
| 393 | 410 | .f64 => return .f64_add, |
| 411 | .v128 => unreachable, // handled independently |
| 394 | 412 | }, |
| 395 | 413 | .sub => switch (args.valtype1.?) { |
| 396 | 414 | .i32 => return .i32_sub, |
| 397 | 415 | .i64 => return .i64_sub, |
| 398 | 416 | .f32 => return .f32_sub, |
| 399 | 417 | .f64 => return .f64_sub, |
| 418 | .v128 => unreachable, // handled independently |
| 400 | 419 | }, |
| 401 | 420 | .mul => switch (args.valtype1.?) { |
| 402 | 421 | .i32 => return .i32_mul, |
| 403 | 422 | .i64 => return .i64_mul, |
| 404 | 423 | .f32 => return .f32_mul, |
| 405 | 424 | .f64 => return .f64_mul, |
| 425 | .v128 => unreachable, // handled independently |
| 406 | 426 | }, |
| 407 | 427 | |
| 408 | 428 | .div => switch (args.valtype1.?) { |
| ... | ... | @@ -410,71 +430,84 @@ fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode { |
| 410 | 430 | .i64 => if (args.signedness.? == .signed) return .i64_div_s else return .i64_div_u, |
| 411 | 431 | .f32 => return .f32_div, |
| 412 | 432 | .f64 => return .f64_div, |
| 433 | .v128 => unreachable, // handled independently |
| 413 | 434 | }, |
| 414 | 435 | .rem => switch (args.valtype1.?) { |
| 415 | 436 | .i32 => if (args.signedness.? == .signed) return .i32_rem_s else return .i32_rem_u, |
| 416 | 437 | .i64 => if (args.signedness.? == .signed) return .i64_rem_s else return .i64_rem_u, |
| 417 | 438 | .f32, .f64 => unreachable, |
| 439 | .v128 => unreachable, // handled independently |
| 418 | 440 | }, |
| 419 | 441 | |
| 420 | 442 | .@"and" => switch (args.valtype1.?) { |
| 421 | 443 | .i32 => return .i32_and, |
| 422 | 444 | .i64 => return .i64_and, |
| 423 | 445 | .f32, .f64 => unreachable, |
| 446 | .v128 => unreachable, // handled independently |
| 424 | 447 | }, |
| 425 | 448 | .@"or" => switch (args.valtype1.?) { |
| 426 | 449 | .i32 => return .i32_or, |
| 427 | 450 | .i64 => return .i64_or, |
| 428 | 451 | .f32, .f64 => unreachable, |
| 452 | .v128 => unreachable, // handled independently |
| 429 | 453 | }, |
| 430 | 454 | .xor => switch (args.valtype1.?) { |
| 431 | 455 | .i32 => return .i32_xor, |
| 432 | 456 | .i64 => return .i64_xor, |
| 433 | 457 | .f32, .f64 => unreachable, |
| 458 | .v128 => unreachable, // handled independently |
| 434 | 459 | }, |
| 435 | 460 | |
| 436 | 461 | .shl => switch (args.valtype1.?) { |
| 437 | 462 | .i32 => return .i32_shl, |
| 438 | 463 | .i64 => return .i64_shl, |
| 439 | 464 | .f32, .f64 => unreachable, |
| 465 | .v128 => unreachable, // handled independently |
| 440 | 466 | }, |
| 441 | 467 | .shr => switch (args.valtype1.?) { |
| 442 | 468 | .i32 => if (args.signedness.? == .signed) return .i32_shr_s else return .i32_shr_u, |
| 443 | 469 | .i64 => if (args.signedness.? == .signed) return .i64_shr_s else return .i64_shr_u, |
| 444 | 470 | .f32, .f64 => unreachable, |
| 471 | .v128 => unreachable, // handled independently |
| 445 | 472 | }, |
| 446 | 473 | .rotl => switch (args.valtype1.?) { |
| 447 | 474 | .i32 => return .i32_rotl, |
| 448 | 475 | .i64 => return .i64_rotl, |
| 449 | 476 | .f32, .f64 => unreachable, |
| 477 | .v128 => unreachable, // handled independently |
| 450 | 478 | }, |
| 451 | 479 | .rotr => switch (args.valtype1.?) { |
| 452 | 480 | .i32 => return .i32_rotr, |
| 453 | 481 | .i64 => return .i64_rotr, |
| 454 | 482 | .f32, .f64 => unreachable, |
| 483 | .v128 => unreachable, // handled independently |
| 455 | 484 | }, |
| 456 | 485 | |
| 457 | 486 | .abs => switch (args.valtype1.?) { |
| 458 | 487 | .i32, .i64 => unreachable, |
| 459 | 488 | .f32 => return .f32_abs, |
| 460 | 489 | .f64 => return .f64_abs, |
| 490 | .v128 => unreachable, // handled independently |
| 461 | 491 | }, |
| 462 | 492 | .neg => switch (args.valtype1.?) { |
| 463 | 493 | .i32, .i64 => unreachable, |
| 464 | 494 | .f32 => return .f32_neg, |
| 465 | 495 | .f64 => return .f64_neg, |
| 496 | .v128 => unreachable, // handled independently |
| 466 | 497 | }, |
| 467 | 498 | .ceil => switch (args.valtype1.?) { |
| 468 | 499 | .i64 => unreachable, |
| 469 | 500 | .i32 => return .f32_ceil, // when valtype is f16, we store it in i32. |
| 470 | 501 | .f32 => return .f32_ceil, |
| 471 | 502 | .f64 => return .f64_ceil, |
| 503 | .v128 => unreachable, // handled independently |
| 472 | 504 | }, |
| 473 | 505 | .floor => switch (args.valtype1.?) { |
| 474 | 506 | .i64 => unreachable, |
| 475 | 507 | .i32 => return .f32_floor, // when valtype is f16, we store it in i32. |
| 476 | 508 | .f32 => return .f32_floor, |
| 477 | 509 | .f64 => return .f64_floor, |
| 510 | .v128 => unreachable, // handled independently |
| 478 | 511 | }, |
| 479 | 512 | .trunc => switch (args.valtype1.?) { |
| 480 | 513 | .i32 => if (args.valtype2) |valty| switch (valty) { |
| ... | ... | @@ -482,40 +515,48 @@ fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode { |
| 482 | 515 | .i64 => unreachable, |
| 483 | 516 | .f32 => if (args.signedness.? == .signed) return .i32_trunc_f32_s else return .i32_trunc_f32_u, |
| 484 | 517 | .f64 => if (args.signedness.? == .signed) return .i32_trunc_f64_s else return .i32_trunc_f64_u, |
| 518 | .v128 => unreachable, // handled independently |
| 485 | 519 | } else return .f32_trunc, // when no valtype2, it's an f16 instead which is stored in an i32. |
| 486 | 520 | .i64 => switch (args.valtype2.?) { |
| 487 | 521 | .i32 => unreachable, |
| 488 | 522 | .i64 => unreachable, |
| 489 | 523 | .f32 => if (args.signedness.? == .signed) return .i64_trunc_f32_s else return .i64_trunc_f32_u, |
| 490 | 524 | .f64 => if (args.signedness.? == .signed) return .i64_trunc_f64_s else return .i64_trunc_f64_u, |
| 525 | .v128 => unreachable, // handled independently |
| 491 | 526 | }, |
| 492 | 527 | .f32 => return .f32_trunc, |
| 493 | 528 | .f64 => return .f64_trunc, |
| 529 | .v128 => unreachable, // handled independently |
| 494 | 530 | }, |
| 495 | 531 | .nearest => switch (args.valtype1.?) { |
| 496 | 532 | .i32, .i64 => unreachable, |
| 497 | 533 | .f32 => return .f32_nearest, |
| 498 | 534 | .f64 => return .f64_nearest, |
| 535 | .v128 => unreachable, // handled independently |
| 499 | 536 | }, |
| 500 | 537 | .sqrt => switch (args.valtype1.?) { |
| 501 | 538 | .i32, .i64 => unreachable, |
| 502 | 539 | .f32 => return .f32_sqrt, |
| 503 | 540 | .f64 => return .f64_sqrt, |
| 541 | .v128 => unreachable, // handled independently |
| 504 | 542 | }, |
| 505 | 543 | .min => switch (args.valtype1.?) { |
| 506 | 544 | .i32, .i64 => unreachable, |
| 507 | 545 | .f32 => return .f32_min, |
| 508 | 546 | .f64 => return .f64_min, |
| 547 | .v128 => unreachable, // handled independently |
| 509 | 548 | }, |
| 510 | 549 | .max => switch (args.valtype1.?) { |
| 511 | 550 | .i32, .i64 => unreachable, |
| 512 | 551 | .f32 => return .f32_max, |
| 513 | 552 | .f64 => return .f64_max, |
| 553 | .v128 => unreachable, // handled independently |
| 514 | 554 | }, |
| 515 | 555 | .copysign => switch (args.valtype1.?) { |
| 516 | 556 | .i32, .i64 => unreachable, |
| 517 | 557 | .f32 => return .f32_copysign, |
| 518 | 558 | .f64 => return .f64_copysign, |
| 559 | .v128 => unreachable, // handled independently |
| 519 | 560 | }, |
| 520 | 561 | |
| 521 | 562 | .wrap => switch (args.valtype1.?) { |
| ... | ... | @@ -523,8 +564,10 @@ fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode { |
| 523 | 564 | .i32 => unreachable, |
| 524 | 565 | .i64 => return .i32_wrap_i64, |
| 525 | 566 | .f32, .f64 => unreachable, |
| 567 | .v128 => unreachable, // handled independently |
| 526 | 568 | }, |
| 527 | 569 | .i64, .f32, .f64 => unreachable, |
| 570 | .v128 => unreachable, // handled independently |
| 528 | 571 | }, |
| 529 | 572 | .convert => switch (args.valtype1.?) { |
| 530 | 573 | .i32, .i64 => unreachable, |
| ... | ... | @@ -532,12 +575,15 @@ fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode { |
| 532 | 575 | .i32 => if (args.signedness.? == .signed) return .f32_convert_i32_s else return .f32_convert_i32_u, |
| 533 | 576 | .i64 => if (args.signedness.? == .signed) return .f32_convert_i64_s else return .f32_convert_i64_u, |
| 534 | 577 | .f32, .f64 => unreachable, |
| 578 | .v128 => unreachable, // handled independently |
| 535 | 579 | }, |
| 536 | 580 | .f64 => switch (args.valtype2.?) { |
| 537 | 581 | .i32 => if (args.signedness.? == .signed) return .f64_convert_i32_s else return .f64_convert_i32_u, |
| 538 | 582 | .i64 => if (args.signedness.? == .signed) return .f64_convert_i64_s else return .f64_convert_i64_u, |
| 539 | 583 | .f32, .f64 => unreachable, |
| 584 | .v128 => unreachable, // handled independently |
| 540 | 585 | }, |
| 586 | .v128 => unreachable, // handled independently |
| 541 | 587 | }, |
| 542 | 588 | .demote => if (args.valtype1.? == .f32 and args.valtype2.? == .f64) return .f32_demote_f64 else unreachable, |
| 543 | 589 | .promote => if (args.valtype1.? == .f64 and args.valtype2.? == .f32) return .f64_promote_f32 else unreachable, |
| ... | ... | @@ -546,6 +592,7 @@ fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode { |
| 546 | 592 | .i64 => if (args.valtype2.? == .f64) return .i64_reinterpret_f64 else unreachable, |
| 547 | 593 | .f32 => if (args.valtype2.? == .i32) return .f32_reinterpret_i32 else unreachable, |
| 548 | 594 | .f64 => if (args.valtype2.? == .i64) return .f64_reinterpret_i64 else unreachable, |
| 595 | .v128 => unreachable, // handled independently |
| 549 | 596 | }, |
| 550 | 597 | .extend => switch (args.valtype1.?) { |
| 551 | 598 | .i32 => switch (args.width.?) { |
| ... | ... | @@ -560,6 +607,7 @@ fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode { |
| 560 | 607 | else => unreachable, |
| 561 | 608 | }, |
| 562 | 609 | .f32, .f64 => unreachable, |
| 610 | .v128 => unreachable, // handled independently |
| 563 | 611 | }, |
| 564 | 612 | } |
| 565 | 613 | } |
| ... | ... | @@ -629,6 +677,10 @@ err_msg: *Module.ErrorMsg, |
| 629 | 677 | /// List of all locals' types generated throughout this declaration |
| 630 | 678 | /// used to emit locals count at start of 'code' section. |
| 631 | 679 | locals: std.ArrayListUnmanaged(u8), |
| 680 | /// List of simd128 immediates. Each value is stored as an array of bytes. |
| 681 | /// This list will only be populated for 128bit-simd values when the target features |
| 682 | /// are enabled also. |
| 683 | simd_immediates: std.ArrayListUnmanaged([16]u8) = .{}, |
| 632 | 684 | /// The Target we're emitting (used to call intInfo) |
| 633 | 685 | target: std.Target, |
| 634 | 686 | /// Represents the wasm binary file that is being linked. |
| ... | ... | @@ -665,14 +717,17 @@ stack_alignment: u32 = 16, |
| 665 | 717 | /// It is illegal to store a non-i32 valtype in this list. |
| 666 | 718 | free_locals_i32: std.ArrayListUnmanaged(u32) = .{}, |
| 667 | 719 | /// A list of indexes which represents a local of valtype `i64`. |
| 668 | | /// It is illegal to store a non-i32 valtype in this list. |
| 720 | /// It is illegal to store a non-i64 valtype in this list. |
| 669 | 721 | free_locals_i64: std.ArrayListUnmanaged(u32) = .{}, |
| 670 | 722 | /// A list of indexes which represents a local of valtype `f32`. |
| 671 | | /// It is illegal to store a non-i32 valtype in this list. |
| 723 | /// It is illegal to store a non-f32 valtype in this list. |
| 672 | 724 | free_locals_f32: std.ArrayListUnmanaged(u32) = .{}, |
| 673 | 725 | /// A list of indexes which represents a local of valtype `f64`. |
| 674 | | /// It is illegal to store a non-i32 valtype in this list. |
| 726 | /// It is illegal to store a non-f64 valtype in this list. |
| 675 | 727 | free_locals_f64: std.ArrayListUnmanaged(u32) = .{}, |
| 728 | /// A list of indexes which represents a local of valtype `v127`. |
| 729 | /// It is illegal to store a non-v128 valtype in this list. |
| 730 | free_locals_v128: std.ArrayListUnmanaged(u32) = .{}, |
| 676 | 731 | |
| 677 | 732 | /// When in debug mode, this tracks if no `finishAir` was missed. |
| 678 | 733 | /// Forgetting to call `finishAir` will cause the result to not be |
| ... | ... | @@ -699,12 +754,14 @@ pub fn deinit(func: *CodeGen) void { |
| 699 | 754 | func.branches.deinit(func.gpa); |
| 700 | 755 | func.blocks.deinit(func.gpa); |
| 701 | 756 | func.locals.deinit(func.gpa); |
| 757 | func.simd_immediates.deinit(func.gpa); |
| 702 | 758 | func.mir_instructions.deinit(func.gpa); |
| 703 | 759 | func.mir_extra.deinit(func.gpa); |
| 704 | 760 | func.free_locals_i32.deinit(func.gpa); |
| 705 | 761 | func.free_locals_i64.deinit(func.gpa); |
| 706 | 762 | func.free_locals_f32.deinit(func.gpa); |
| 707 | 763 | func.free_locals_f64.deinit(func.gpa); |
| 764 | func.free_locals_v128.deinit(func.gpa); |
| 708 | 765 | func.* = undefined; |
| 709 | 766 | } |
| 710 | 767 | |
| ... | ... | @@ -867,6 +924,17 @@ fn addImm64(func: *CodeGen, imm: u64) error{OutOfMemory}!void { |
| 867 | 924 | try func.addInst(.{ .tag = .i64_const, .data = .{ .payload = extra_index } }); |
| 868 | 925 | } |
| 869 | 926 | |
| 927 | /// Accepts the index into the list of 128bit-immediates |
| 928 | fn addImm128(func: *CodeGen, index: u32) error{OutOfMemory}!void { |
| 929 | const simd_values = func.simd_immediates.items[index]; |
| 930 | const extra_index = @intCast(u32, func.mir_extra.items.len); |
| 931 | // tag + 128bit value |
| 932 | try func.mir_extra.ensureUnusedCapacity(func.gpa, 5); |
| 933 | func.mir_extra.appendAssumeCapacity(std.wasm.simdOpcode(.v128_const)); |
| 934 | func.mir_extra.appendSliceAssumeCapacity(@alignCast(4, mem.bytesAsSlice(u32, &simd_values))); |
| 935 | try func.addInst(.{ .tag = .simd, .data = .{ .payload = extra_index } }); |
| 936 | } |
| 937 | |
| 870 | 938 | fn addFloat64(func: *CodeGen, float: f64) error{OutOfMemory}!void { |
| 871 | 939 | const extra_index = try func.addExtra(Mir.Float64.fromFloat64(float)); |
| 872 | 940 | try func.addInst(.{ .tag = .f64_const, .data = .{ .payload = extra_index } }); |
| ... | ... | @@ -924,6 +992,10 @@ fn typeToValtype(ty: Type, target: std.Target) wasm.Valtype { |
| 924 | 992 | }, |
| 925 | 993 | else => wasm.Valtype.i32, |
| 926 | 994 | }, |
| 995 | .Vector => switch (determineSimdStoreStrategy(ty, target)) { |
| 996 | .direct => wasm.Valtype.v128, |
| 997 | .unrolled => wasm.Valtype.i32, |
| 998 | }, |
| 927 | 999 | else => wasm.Valtype.i32, // all represented as reference/immediate |
| 928 | 1000 | }; |
| 929 | 1001 | } |
| ... | ... | @@ -950,6 +1022,7 @@ fn emitWValue(func: *CodeGen, value: WValue) InnerError!void { |
| 950 | 1022 | .local => |idx| try func.addLabel(.local_get, idx.value), |
| 951 | 1023 | .imm32 => |val| try func.addImm32(@bitCast(i32, val)), |
| 952 | 1024 | .imm64 => |val| try func.addImm64(val), |
| 1025 | .imm128 => |val| try func.addImm128(val), |
| 953 | 1026 | .float32 => |val| try func.addInst(.{ .tag = .f32_const, .data = .{ .float32 = val } }), |
| 954 | 1027 | .float64 => |val| try func.addFloat64(val), |
| 955 | 1028 | .memory => |ptr| { |
| ... | ... | @@ -1016,6 +1089,10 @@ fn allocLocal(func: *CodeGen, ty: Type) InnerError!WValue { |
| 1016 | 1089 | log.debug("reusing local ({d}) of type {}\n", .{ index, valtype }); |
| 1017 | 1090 | return WValue{ .local = .{ .value = index, .references = 1 } }; |
| 1018 | 1091 | }, |
| 1092 | .v128 => if (func.free_locals_v128.popOrNull()) |index| { |
| 1093 | log.debug("reusing local ({d}) of type {}\n", .{ index, valtype }); |
| 1094 | return WValue{ .local = .{ .value = index, .references = 1 } }; |
| 1095 | }, |
| 1019 | 1096 | } |
| 1020 | 1097 | log.debug("new local of type {}\n", .{valtype}); |
| 1021 | 1098 | // no local was free to be re-used, so allocate a new local instead |
| ... | ... | @@ -1098,7 +1175,6 @@ pub fn generate( |
| 1098 | 1175 | .gpa = bin_file.allocator, |
| 1099 | 1176 | .air = air, |
| 1100 | 1177 | .liveness = liveness, |
| 1101 | | // .values = .{}, |
| 1102 | 1178 | .code = code, |
| 1103 | 1179 | .decl_index = func.owner_decl, |
| 1104 | 1180 | .decl = bin_file.options.module.?.declPtr(func.owner_decl), |
| ... | ... | @@ -1481,9 +1557,9 @@ fn memcpy(func: *CodeGen, dst: WValue, src: WValue, len: WValue) !void { |
| 1481 | 1557 | .imm64 => |val| val, |
| 1482 | 1558 | else => unreachable, |
| 1483 | 1559 | }; |
| 1484 | | // if the size (length) is more than 1024 bytes, we use a runtime loop instead to prevent |
| 1560 | // if the size (length) is more than 32 bytes, we use a runtime loop instead to prevent |
| 1485 | 1561 | // binary size bloat. |
| 1486 | | if (length > 1024) break :blk; |
| 1562 | if (length > 32) break :blk; |
| 1487 | 1563 | var offset: u32 = 0; |
| 1488 | 1564 | const lhs_base = dst.offset(); |
| 1489 | 1565 | const rhs_base = src.offset(); |
| ... | ... | @@ -1612,7 +1688,6 @@ fn isByRef(ty: Type, target: std.Target) bool { |
| 1612 | 1688 | => return false, |
| 1613 | 1689 | |
| 1614 | 1690 | .Array, |
| 1615 | | .Vector, |
| 1616 | 1691 | .Frame, |
| 1617 | 1692 | .Union, |
| 1618 | 1693 | => return ty.hasRuntimeBitsIgnoreComptime(), |
| ... | ... | @@ -1625,6 +1700,7 @@ fn isByRef(ty: Type, target: std.Target) bool { |
| 1625 | 1700 | } |
| 1626 | 1701 | return ty.hasRuntimeBitsIgnoreComptime(); |
| 1627 | 1702 | }, |
| 1703 | .Vector => return determineSimdStoreStrategy(ty, target) == .unrolled, |
| 1628 | 1704 | .Int => return ty.intInfo(target).bits > 64, |
| 1629 | 1705 | .Float => return ty.floatBits(target) > 64, |
| 1630 | 1706 | .ErrorUnion => { |
| ... | ... | @@ -1647,6 +1723,26 @@ fn isByRef(ty: Type, target: std.Target) bool { |
| 1647 | 1723 | } |
| 1648 | 1724 | } |
| 1649 | 1725 | |
| 1726 | const SimdStoreStrategy = enum { |
| 1727 | direct, |
| 1728 | unrolled, |
| 1729 | }; |
| 1730 | |
| 1731 | /// For a given vector type, returns the `SimdStoreStrategy`. |
| 1732 | /// This means when a given type is 128 bits and either the simd128 or relaxed-simd |
| 1733 | /// features are enabled, the function will return `.direct`. This would allow to store |
| 1734 | /// it using a instruction, rather than an unrolled version. |
| 1735 | fn determineSimdStoreStrategy(ty: Type, target: std.Target) SimdStoreStrategy { |
| 1736 | std.debug.assert(ty.zigTypeTag() == .Vector); |
| 1737 | if (ty.bitSize(target) != 128) return .unrolled; |
| 1738 | const hasFeature = std.Target.wasm.featureSetHas; |
| 1739 | const features = target.cpu.features; |
| 1740 | if (hasFeature(features, .relaxed_simd) or hasFeature(features, .simd128)) { |
| 1741 | return .direct; |
| 1742 | } |
| 1743 | return .unrolled; |
| 1744 | } |
| 1745 | |
| 1650 | 1746 | /// Creates a new local for a pointer that points to memory with given offset. |
| 1651 | 1747 | /// This can be used to get a pointer to a struct field, error payload, etc. |
| 1652 | 1748 | /// By providing `modify` as action, it will modify the given `ptr_value` instead of making a new |
| ... | ... | @@ -2187,10 +2283,29 @@ fn store(func: *CodeGen, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerE |
| 2187 | 2283 | const len = @intCast(u32, ty.abiSize(func.target)); |
| 2188 | 2284 | return func.memcpy(lhs, rhs, .{ .imm32 = len }); |
| 2189 | 2285 | }, |
| 2190 | | .Struct, .Array, .Union, .Vector => if (isByRef(ty, func.target)) { |
| 2286 | .Struct, .Array, .Union => if (isByRef(ty, func.target)) { |
| 2191 | 2287 | const len = @intCast(u32, ty.abiSize(func.target)); |
| 2192 | 2288 | return func.memcpy(lhs, rhs, .{ .imm32 = len }); |
| 2193 | 2289 | }, |
| 2290 | .Vector => switch (determineSimdStoreStrategy(ty, func.target)) { |
| 2291 | .unrolled => { |
| 2292 | const len = @intCast(u32, ty.abiSize(func.target)); |
| 2293 | return func.memcpy(lhs, rhs, .{ .imm32 = len }); |
| 2294 | }, |
| 2295 | .direct => { |
| 2296 | try func.emitWValue(lhs); |
| 2297 | try func.lowerToStack(rhs); |
| 2298 | // TODO: Add helper functions for simd opcodes |
| 2299 | const extra_index = @intCast(u32, func.mir_extra.items.len); |
| 2300 | // stores as := opcode, offset, alignment (opcode::memarg) |
| 2301 | try func.mir_extra.appendSlice(func.gpa, &[_]u32{ |
| 2302 | std.wasm.simdOpcode(.v128_store), |
| 2303 | offset + lhs.offset(), |
| 2304 | ty.abiAlignment(func.target), |
| 2305 | }); |
| 2306 | return func.addInst(.{ .tag = .simd, .data = .{ .payload = extra_index } }); |
| 2307 | }, |
| 2308 | }, |
| 2194 | 2309 | .Pointer => { |
| 2195 | 2310 | if (ty.isSlice()) { |
| 2196 | 2311 | // store pointer first |
| ... | ... | @@ -2289,6 +2404,19 @@ fn load(func: *CodeGen, operand: WValue, ty: Type, offset: u32) InnerError!WValu |
| 2289 | 2404 | // load local's value from memory by its stack position |
| 2290 | 2405 | try func.emitWValue(operand); |
| 2291 | 2406 | |
| 2407 | if (ty.zigTypeTag() == .Vector) { |
| 2408 | // TODO: Add helper functions for simd opcodes |
| 2409 | const extra_index = @intCast(u32, func.mir_extra.items.len); |
| 2410 | // stores as := opcode, offset, alignment (opcode::memarg) |
| 2411 | try func.mir_extra.appendSlice(func.gpa, &[_]u32{ |
| 2412 | std.wasm.simdOpcode(.v128_load), |
| 2413 | offset + operand.offset(), |
| 2414 | ty.abiAlignment(func.target), |
| 2415 | }); |
| 2416 | try func.addInst(.{ .tag = .simd, .data = .{ .payload = extra_index } }); |
| 2417 | return WValue{ .stack = {} }; |
| 2418 | } |
| 2419 | |
| 2292 | 2420 | const abi_size = @intCast(u8, ty.abiSize(func.target)); |
| 2293 | 2421 | const opcode = buildOpcode(.{ |
| 2294 | 2422 | .valtype1 = typeToValtype(ty, func.target), |
| ... | ... | @@ -2766,10 +2894,24 @@ fn lowerConstant(func: *CodeGen, arg_val: Value, ty: Type) InnerError!WValue { |
| 2766 | 2894 | const int_val = Value.initPayload(&payload.base); |
| 2767 | 2895 | return func.lowerConstant(int_val, struct_obj.backing_int_ty); |
| 2768 | 2896 | }, |
| 2897 | .Vector => { |
| 2898 | assert(determineSimdStoreStrategy(ty, target) == .direct); |
| 2899 | var buf: [16]u8 = undefined; |
| 2900 | val.writeToMemory(ty, func.bin_file.base.options.module.?, &buf); |
| 2901 | return func.storeSimdImmd(buf); |
| 2902 | }, |
| 2769 | 2903 | else => |zig_type| return func.fail("Wasm TODO: LowerConstant for zigTypeTag {}", .{zig_type}), |
| 2770 | 2904 | } |
| 2771 | 2905 | } |
| 2772 | 2906 | |
| 2907 | /// Stores the value as a 128bit-immediate value by storing it inside |
| 2908 | /// the list and returning the index into this list as `WValue`. |
| 2909 | fn storeSimdImmd(func: *CodeGen, value: [16]u8) !WValue { |
| 2910 | const index = @intCast(u32, func.simd_immediates.items.len); |
| 2911 | try func.simd_immediates.append(func.gpa, value); |
| 2912 | return WValue{ .imm128 = index }; |
| 2913 | } |
| 2914 | |
| 2773 | 2915 | fn emitUndefined(func: *CodeGen, ty: Type) InnerError!WValue { |
| 2774 | 2916 | switch (ty.zigTypeTag()) { |
| 2775 | 2917 | .Bool, .ErrorSet => return WValue{ .imm32 = 0xaaaaaaaa }, |
| ... | ... | @@ -4288,9 +4430,71 @@ fn airIntToFloat(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 4288 | 4430 | fn airSplat(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 4289 | 4431 | const ty_op = func.air.instructions.items(.data)[inst].ty_op; |
| 4290 | 4432 | const operand = try func.resolveInst(ty_op.operand); |
| 4433 | const ty = func.air.typeOfIndex(inst); |
| 4434 | const elem_ty = ty.childType(); |
| 4435 | |
| 4436 | if (determineSimdStoreStrategy(ty, func.target) == .direct) blk: { |
| 4437 | switch (operand) { |
| 4438 | // when the operand lives in the linear memory section, we can directly |
| 4439 | // load and splat the value at once. Meaning we do not first have to load |
| 4440 | // the scalar value onto the stack. |
| 4441 | .stack_offset, .memory, .memory_offset => { |
| 4442 | const opcode = switch (elem_ty.bitSize(func.target)) { |
| 4443 | 8 => std.wasm.simdOpcode(.v128_load8_splat), |
| 4444 | 16 => std.wasm.simdOpcode(.v128_load16_splat), |
| 4445 | 32 => std.wasm.simdOpcode(.v128_load32_splat), |
| 4446 | 64 => std.wasm.simdOpcode(.v128_load64_splat), |
| 4447 | else => break :blk, // Cannot make use of simd-instructions |
| 4448 | }; |
| 4449 | const result = try func.allocLocal(ty); |
| 4450 | try func.emitWValue(operand); |
| 4451 | // TODO: Add helper functions for simd opcodes |
| 4452 | const extra_index = @intCast(u32, func.mir_extra.items.len); |
| 4453 | // stores as := opcode, offset, alignment (opcode::memarg) |
| 4454 | try func.mir_extra.appendSlice(func.gpa, &[_]u32{ |
| 4455 | opcode, |
| 4456 | operand.offset(), |
| 4457 | elem_ty.abiAlignment(func.target), |
| 4458 | }); |
| 4459 | try func.addInst(.{ .tag = .simd, .data = .{ .payload = extra_index } }); |
| 4460 | try func.addLabel(.local_set, result.local.value); |
| 4461 | return func.finishAir(inst, result, &.{ty_op.operand}); |
| 4462 | }, |
| 4463 | .local => { |
| 4464 | const opcode = switch (elem_ty.bitSize(func.target)) { |
| 4465 | 8 => std.wasm.simdOpcode(.i8x16_splat), |
| 4466 | 16 => std.wasm.simdOpcode(.i16x8_splat), |
| 4467 | 32 => if (elem_ty.isInt()) std.wasm.simdOpcode(.i32x4_splat) else std.wasm.simdOpcode(.f32x4_splat), |
| 4468 | 64 => if (elem_ty.isInt()) std.wasm.simdOpcode(.i64x2_splat) else std.wasm.simdOpcode(.f64x2_splat), |
| 4469 | else => break :blk, // Cannot make use of simd-instructions |
| 4470 | }; |
| 4471 | const result = try func.allocLocal(ty); |
| 4472 | try func.emitWValue(operand); |
| 4473 | const extra_index = @intCast(u32, func.mir_extra.items.len); |
| 4474 | try func.mir_extra.append(func.gpa, opcode); |
| 4475 | try func.addInst(.{ .tag = .simd, .data = .{ .payload = extra_index } }); |
| 4476 | try func.addLabel(.local_set, result.local.value); |
| 4477 | return func.finishAir(inst, result, &.{ty_op.operand}); |
| 4478 | }, |
| 4479 | else => unreachable, |
| 4480 | } |
| 4481 | } |
| 4482 | const elem_size = elem_ty.bitSize(func.target); |
| 4483 | const vector_len = @intCast(usize, ty.vectorLen()); |
| 4484 | if ((!std.math.isPowerOfTwo(elem_size) or elem_size % 8 != 0) and vector_len > 1) { |
| 4485 | return func.fail("TODO: WebAssembly `@splat` for arbitrary element bitsize {d}", .{elem_size}); |
| 4486 | } |
| 4291 | 4487 | |
| 4292 | | _ = operand; |
| 4293 | | return func.fail("TODO: Implement wasm airSplat", .{}); |
| 4488 | const result = try func.allocStack(ty); |
| 4489 | const elem_byte_size = @intCast(u32, elem_ty.abiSize(func.target)); |
| 4490 | var index: usize = 0; |
| 4491 | var offset: u32 = 0; |
| 4492 | while (index < vector_len) : (index += 1) { |
| 4493 | try func.store(result, operand, elem_ty, offset); |
| 4494 | offset += elem_byte_size; |
| 4495 | } |
| 4496 | |
| 4497 | return func.finishAir(inst, result, &.{ty_op.operand}); |
| 4294 | 4498 | } |
| 4295 | 4499 | |
| 4296 | 4500 | fn airSelect(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |