Skip to content

VM AOT does not recognize 32-bit rotate, so SHA-256 runs 1.7x faster under dart2js than native #64389

Open

Description

@krll-kov

The code below is a complete SHA-256. It checks the NIST abc vector, then hashes 64 MB and prints MB/s and the digest. The digest is the same on all three targets and matches sha256sum of the same bytes (52c5b272...f74736). The dart2js build on node is 1.7x faster than the AOT build.

import 'dart:typed_data';

const _k = [
  0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, //
  0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
  0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
  0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
  0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
  0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
  0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
  0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
  0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
  0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
  0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
];

int rotr(int x, int n) => ((x >>> n) | (x << (32 - n))) & 0xffffffff;

void compress(Uint32List h, Uint32List w, Uint8List d) {
  for (var at = 0; at + 64 <= d.length; at += 64) {
    for (var i = 0; i < 16; i++) {
      final o = at + 4 * i;
      w[i] = (d[o] << 24) | (d[o + 1] << 16) | (d[o + 2] << 8) | d[o + 3];
    }
    for (var i = 16; i < 64; i++) {
      final x = w[i - 15], y = w[i - 2];
      final s0 = rotr(x, 7) ^ rotr(x, 18) ^ (x >>> 3);
      final s1 = rotr(y, 17) ^ rotr(y, 19) ^ (y >>> 10);
      w[i] = w[i - 16] + s0 + w[i - 7] + s1;
    }
    var a = h[0], b = h[1], c = h[2], e = h[4], f = h[5], g = h[6];
    var dd = h[3], hh = h[7];
    for (var i = 0; i < 64; i++) {
      final s1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
      final t1 = (hh + s1 + ((e & f) ^ (~e & g)) + _k[i] + w[i]) & 0xffffffff;
      final s0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
      final t2 = s0 + ((a & b) ^ (a & c) ^ (b & c));
      hh = g;
      g = f;
      f = e;
      e = (dd + t1) & 0xffffffff;
      dd = c;
      c = b;
      b = a;
      a = (t1 + t2) & 0xffffffff;
    }
    h[0] += a;
    h[1] += b;
    h[2] += c;
    h[3] += dd;
    h[4] += e;
    h[5] += f;
    h[6] += g;
    h[7] += hh;
  }
}

String sha256(Uint8List message) {
  final length = message.length;
  final whole = length & ~63;
  final padded = Uint8List(length - whole < 56 ? 64 : 128);
  padded.setRange(0, length - whole, message, whole);
  padded[length - whole] = 0x80;
  var bits = length * 8;
  for (var i = padded.length - 1; i >= padded.length - 8; i--) {
    padded[i] = bits % 256;
    bits ~/= 256;
  }
  final h = Uint32List.fromList([
    0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, //
    0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
  ]);
  final w = Uint32List(64);
  compress(h, w, Uint8List.sublistView(message, 0, whole));
  compress(h, w, padded);
  return h.map((v) => v.toRadixString(16).padLeft(8, '0')).join();
}

void main() {
  const abc =
      'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad';
  if (sha256(Uint8List.fromList('abc'.codeUnits)) != abc) {
    throw StateError('NIST vector failed');
  }
  final data = Uint8List(64 << 20);
  for (var i = 0; i < data.length; i++) {
    data[i] = (i * 31 + (i >> 11)) & 0xff;
  }
  for (var pass = 0; pass < 10; pass++) {
    final sw = Stopwatch()..start();
    final digest = sha256(data);
    final s = sw.elapsedMicroseconds / 1e6;
    print('${(data.length / 1e6 / s).toStringAsFixed(0)} MB/s  $digest');
  }
}

Run:

dart compile exe sha_rotate.dart -o sha_vm && ./sha_vm
dart compile js -O2 sha_rotate.dart -o sha.js && node sha.js
dart compile wasm sha_rotate.dart -o sha.wasm && node run_wasm.mjs

run_wasm.mjs:

import { readFile } from 'node:fs/promises';
import { compile } from './sha.mjs';
const bytes = await readFile(new URL('./sha.wasm', import.meta.url));
const app = await compile(bytes);
const inst = await app.instantiate({});
inst.invokeMain();

Results, 10 passes each, steady state after warm-up:

target MB/s
dart compile exe (AOT) 182-184
dart compile js -O2, node 308-315
dart compile wasm, node 187-190

Dart2js emits (x >>> n | x << (32 - n)) >>> 0, and V8 TurboFan turns it into Word32Ror in its EarlyOptimization phase. On arm64 it selects ror or folds the rotate into eor w, w, w, ror #n, with no separate shifts left for the rotates.

The AOT code for compress has no ror. Each rotr compiles to lsr + lsl + orr. That is 2 extra instructions per rotate, 1152 per 64-byte block (576 rotates).

dart compile aot-snapshot sha_rotate.dart -o sha.aot --verbose \
  --extra-gen-snapshot-options=--disassemble-optimized \
  --extra-gen-snapshot-options=--print-flow-graph-filter=compress

It is not arm64 only. With --target-os=linux --target-arch=x64 added to the same command, compress has no ror/rol either: 10 shrl + 10 shll + 10 orl for the 10 rotates in the source. I could not time x64 here. In runtime/vm/compiler/backend there is no rotate operation at all, so neither backend can emit one.

Could the VM compiler recognize ((x >>> n) | (x << (32 - n))) & 0xffffffff with a constant n, on a value known to fit in 32 bits, and emit a rotate?

Related: #6486 (rotate in the core library), #64345 (Int32x4.rotateRight).

Dart SDK 3.13.4 (stable) macos_arm64, macbook M4 Max, node v26.8.1

For comparison in C: a portable SHA-256 (clang -O2, no SHA instructions, 128 ror in the block function) hashes 100 MB at 634 MB/s. The same code with each rotate forced into lsr + lsl + orr (empty asm barriers between the shifts, 0 ror left) runs at 448 MB/s, 1.42x slower, with the same digest.

Activity

  1. modulovalue commented on Sep 25, 2026

    @modulovalue
    Contributor

    no SHA instructions

    I don't think that's a fair comparison then.

    There are two angles here:

    • If you want to see a fast SHA-256, adding a rotate right is not really important. Adding dedicated SHA intrinsics would be more helpful.

    • If you want to show a reason for why rotate right is useful, this is a bad example because it doesn't make a big difference when it comes to reaching an optimal SHA-256 implementation.

    So your argument is kind of telling me that we should probably not invest time in having a scalar rotate right as it wouldn't make much of a difference in reaching an optimal SHA-256 implementation.

    Optimal SHA-256 vs our package:crypto SHA-256:

    Image

    Side note: SHA-256 with SHA intrinsics reaches up to 3 GB/s on my machine (M5 Max) and 1 GB/s without.

  2. krll-kov commented on Sep 26, 2026

    @krll-kov
    Author

    no SHA instructions

    I don't think that's a fair comparison then.

    There are two angles here:

    * If you want to see a fast SHA-256, adding a rotate right is not really important. Adding dedicated SHA intrinsics would be more helpful.
    
    * If you want to show a reason for why rotate right is useful, this is a bad example because it doesn't make a big difference when it comes to reaching an optimal SHA-256 implementation.
    

    So your argument is kind of telling me that we should probably not invest time in having a scalar rotate right as it wouldn't make much of a difference in reaching an optimal SHA-256 implementation.

    Optimal SHA-256 vs our package:crypto SHA-256:
    Image

    Side note: SHA-256 with SHA intrinsics reaches up to 3 GB/s on my machine (M5 Max) and 1 GB/s without.

    Thanks for testing this!

    The Dart comparison in the issue is the same source on two compilers, neither using SHA instructions, so it seems fair: 182-184 MB/s AOT against 308-315 MB/s under dart2js.

    The C code below is a control that changes one thing. It is the same loop as the Dart code, and clang emits 10 ror in compress, one for each rotate in the source, where Dart AOT emits lsr + lsl + orr. With -DNO_ROR each rotate is forced into those three instructions: X MB/s against Y MB/s on enwik8, Z x. Not all of the dart2js 1.7x is the rotate, since V8 also computes in Word32 without the & 0xffffffff masks, but Z x is the rotate alone.

    X = 455 MB/s
    Y = 336 MB/s
    Z = 1.36x
    Measured on enwik8 (100 MB), M4 Max, clang O2: 10 alternating runs of both builds, 5 passes each, best pass of each build; the top 8 passes of each build agree within 1 MB/s.

    Both builds give the same digest as shasum -a 256. The 1 GB/s you get without SHA instructions is scalar code built on ror and eor ..., ror #n, so rotate is part of reaching that number, not beside it. SHA intrinsics would help SHA-256 only. xxHash, ChaCha20, BLAKE2/3, SipHash and Murmur have no dedicated instructions and depend on the same pattern that LLVM, GCC and V8 already recognize.

    #include <stdint.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <time.h>
    
    static const uint32_t k[64] = {
        0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
        0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
        0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
        0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
        0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
        0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
        0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
        0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
        0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
        0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
        0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
    };
    
    #ifdef NO_ROR
    // Empty asm keeps both shifts, so clang cannot fuse them into ror
    static inline uint32_t rotr(uint32_t x, int n) {
      uint32_t lo = x >> n, hi = x << (32 - n);
      __asm__("" : "+r"(lo));
      __asm__("" : "+r"(hi));
      return lo | hi;
    }
    #else
    static inline uint32_t rotr(uint32_t x, int n) {
      return (x >> n) | (x << (32 - n));
    }
    #endif
    
    static void compress(uint32_t h[8], const uint8_t *d, size_t length) {
      uint32_t w[64];
      for (size_t at = 0; at + 64 <= length; at += 64) {
        for (int i = 0; i < 16; i++) {
          const uint8_t *p = d + at + 4 * i;
          w[i] = (uint32_t)p[0] << 24 | (uint32_t)p[1] << 16 |
                 (uint32_t)p[2] << 8 | p[3];
        }
        for (int i = 16; i < 64; i++) {
          uint32_t x = w[i - 15], y = w[i - 2];
          uint32_t s0 = rotr(x, 7) ^ rotr(x, 18) ^ (x >> 3);
          uint32_t s1 = rotr(y, 17) ^ rotr(y, 19) ^ (y >> 10);
          w[i] = w[i - 16] + s0 + w[i - 7] + s1;
        }
        uint32_t a = h[0], b = h[1], c = h[2], dd = h[3];
        uint32_t e = h[4], f = h[5], g = h[6], hh = h[7];
        for (int i = 0; i < 64; i++) {
          uint32_t s1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
          uint32_t t1 = hh + s1 + ((e & f) ^ (~e & g)) + k[i] + w[i];
          uint32_t s0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
          uint32_t t2 = s0 + ((a & b) ^ (a & c) ^ (b & c));
          hh = g;
          g = f;
          f = e;
          e = dd + t1;
          dd = c;
          c = b;
          b = a;
          a = t1 + t2;
        }
        h[0] += a;
        h[1] += b;
        h[2] += c;
        h[3] += dd;
        h[4] += e;
        h[5] += f;
        h[6] += g;
        h[7] += hh;
      }
    }
    
    static void sha256(const uint8_t *message, size_t length, uint32_t h[8]) {
      static const uint32_t init[8] = {
          0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
          0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
      };
      memcpy(h, init, sizeof(init));
      size_t whole = length & ~(size_t)63;
      compress(h, message, whole);
    
      uint8_t padded[128] = {0};
      size_t tail = length - whole;
      memcpy(padded, message + whole, tail);
      padded[tail] = 0x80;
      size_t padded_length = tail < 56 ? 64 : 128;
      uint64_t bits = (uint64_t)length * 8;
      for (int i = 0; i < 8; i++) {
        padded[padded_length - 1 - i] = (uint8_t)(bits >> (8 * i));
      }
      compress(h, padded, padded_length);
    }
    
    int main(int argc, char **argv) {
      FILE *file = fopen(argv[1], "rb");
      fseek(file, 0, SEEK_END);
      size_t length = (size_t)ftell(file);
      fseek(file, 0, SEEK_SET);
      uint8_t *data = malloc(length);
      fread(data, 1, length, file);
      fclose(file);
    
      uint32_t h[8];
      for (int pass = 0; pass < 5; pass++) {
        struct timespec t0, t1;
        clock_gettime(CLOCK_MONOTONIC, &t0);
        sha256(data, length, h);
        clock_gettime(CLOCK_MONOTONIC, &t1);
        double s = (t1.tv_sec - t0.tv_sec) + (t1.tv_nsec - t0.tv_nsec) / 1e9;
        printf("%.0f MB/s  ", length / 1e6 / s);
        for (int i = 0; i < 8; i++) printf("%08x", h[i]);
        printf("\n");
      }
      return 0;
    }
    
    clang -O2 sha256.c -o sha_ror
    clang -O2 -DNO_ROR sha256.c -o sha_noror
    ./sha_ror enwik8 && ./sha_noror enwik8

    Previous C numbers in the issue body came from an unrolled version (8 rounds per iteration, 16-word rolling schedule, 128 ror in the block function): 634 against 448 MB/s, 1.42x. The code above is a plain loop that matches the Dart source, 10 ror, and shows the same effect at 1.36x.

  3. modulovalue commented on Sep 26, 2026

    @modulovalue
    Contributor

    I'm on your side, rotate right would make things faster, definitely, but let's assume we had a rotate right over integers, then you might want a rotate left, then you might want some other permutation of bytes, a bit reverse and so on. If we add them to the integer interface we end up with dozens of methods that make the life of everyone harder. (If we don't add them to the integer interface, then where? A general purpose bit operation class with statics? What platforms do we support those on? Is this work that's worthwhile?)

    I can think of two kinds of people who would benefit from those operations:

    • People who optimize things for fun, which is not a group of people we should optimize APIs for. The downside of having those operations is complexity and a bigger API surface.
    • People who want a faster SHA-256. But even if we had a rotate right, those people would be 3x/4x/5x away from an optimal SHA-256. So everyone who really needs a fast SHA-256 would be forced to use an FFI based SHA-256 anyway.

    Consider for example a use case that needs POW for whatever reason, you're doing a Bitcoin style protection scheme where you need to find a SHA-256 with a specific suffix. Those are use cases where performance matters MOST and none of them would start using SHA-256 if we added a rotate right. Most other people who need SHA-256 are creating a checksum of small binary blobs and they wouldn't notice a faster SHA-256.

    You might be suggesting this because we recently landed oneBitCount and trailingZeroBitCount. Those are immensely useful and allow us to move very close to an optimal implementation without leaving Dart (bitarray iteration and cardinality, succinct representations of trees, and many SIMD algorithms where we need CTZ for extracting the position of a bit in a signmask). But rotate right would not benefit many people and in cases where it really matters, I claim, it will be superseded by SIMD anyways.

    To summarize, I think SHA-256 is not a good use case to show why rotate right matters. I think a scalar rotate right matters, just not enough that it's worth working on at the moment.

  4. krll-kov commented on Sep 26, 2026

    @krll-kov
    Author

    I'm on your side, rotate right would make things faster, definitely, but let's assume we had a rotate right over integers, then you might want a rotate left, then you might want some other permutation of bytes, a bit reverse and so on. If we add them to the integer interface we end up with dozens of methods that make the life of everyone harder. (If we don't add them to the integer interface, then where? A general purpose bit operation class with statics? What platforms do we support those on? Is this work that's worthwhile?)

    I can think of two kinds of people who would benefit from those operations:

    * People who optimize things for fun, which is not a group of people we should optimize APIs for. The downside of having those operations is complexity and a bigger API surface.
    
    * People who want a faster SHA-256. But even if we had a rotate right, those people would be 3x/4x/5x away from an optimal SHA-256. So everyone who really needs a fast SHA-256 would be forced to use an FFI based SHA-256 anyway.
    

    Consider for example a use case that needs POW for whatever reason, you're doing a Bitcoin style protection scheme where you need to find a SHA-256 with a specific suffix. Those are use cases where performance matters MOST and none of them would start using SHA-256 if we added a rotate right. Most other people who need SHA-256 are creating a checksum of small binary blobs and they wouldn't notice a faster SHA-256.

    You might be suggesting this because we recently landed oneBitCount and trailingZeroBitCount. Those are immensely useful and allow us to move very close to an optimal implementation without leaving Dart (bitarray iteration and cardinality, succinct representations of trees, and many SIMD algorithms where we need CTZ for extracting the position of a bit in a signmask). But rotate right would not benefit many people and in cases where it really matters, I claim, it will be superseded by SIMD anyways.

    To summarize, I think SHA-256 is not a good use case to show why rotate right matters. I think a scalar rotate right matters, just not enough that it's worth working on at the moment.

    I agree that demands do grow with each feature approval, and you're right overall. My point is that the language doesn't stand still, and operations like this are needed in many algorithms, not only SHA-256.

    Yes, native code can be tens of times faster thanks to assembly and NEON. But people don't expect native speed from Dart. They just want the existing implementations not to be tens of times slower. Being 1.5-2x slower than native is perfectly acceptable for cross-platform code that is much easier to write than C.

    Your argument about FFI is valid, but people don't want to use it: it has platform limitations, all those DLLs add a lot of weight, and it means maintaining endless bindings. That's exactly what makes Dart simple: everything can be implemented in plain Dart without any DLLs. It's just missing a few optimizations that the most widely used algorithms depend on.

    Also, I'm not asking for a new method on int. The idea is that the compiler recognizes the pattern people already write, (x >>> n) | (x << (32 - n)) with a constant n, and emits a single rotate instruction, the way V8 and LLVM already do. Nothing is added to the API, so rotate left, bit reverse or a separate class of static helpers are not part of this.

    Mentioned trailingZeroBitCount is also great, i myself already used it in zstd decoder/encoder and got a free speed increase with minor code rewrite

  5. modulovalue commented on Sep 26, 2026

    @modulovalue
    Contributor

    But people don't expect native speed from Dart.

    I do.

    Being 1.5-2x slower than native is perfectly acceptable for cross-platform code

    It's not. That's not the standard I'd like Dart to have. That's not acceptable to me.

    The idea is that the compiler recognizes the pattern people already write, (x >>> n) | (x << (32 - n)) with a constant n

    The Dart team and people like me, an external contributor, have to focus on things that matter and we need to implement them in ways that is scalable and makes sense. Adding a ton of peephole optimizations is dead weight if nobody is going to benefit much from them. I also would like to point you to the full employment theorem. There's an endless amount of optimization due to Gödel so the fact that an inefficiency is discovered is not really valuable in itself. The question is who is going to benefit from it.

    There's a much more scalable approach to adding such peephole optimizations that we don't support yet (mainly because it's the state of the art and an active area of research): if we use tree automata (which in that area are also known as e-graphs) for specifying such patterns it would reduce down to a few lines of code where certain patterns could be optimally transduced into more efficient patterns. That's when adding such peephole optimizations would become much more valuable because we could also be taking algebraics into consideration #64286. We are unfortunately not there yet.

  6. modulovalue commented on Sep 26, 2026

    @modulovalue
    Contributor
    Image

    Off topic, note to self: A heavily unrolled SHA-256 doesn't pass the code size limits and doesn't get optimized on JIT which is the same problem I've seen on huge generated lexers. We could probably raise those limits as I've done some work on eliminating obvious inefficiencies there. I'll try to investigate that once I look into the lexer use case again.

  7. krll-kov commented on Sep 26, 2026

    @krll-kov
    Author

    But people don't expect native speed from Dart.

    I do.

    Being 1.5-2x slower than native is perfectly acceptable for cross-platform code

    It's not. That's not the standard I'd like Dart to have. That's not acceptable to me.

    The idea is that the compiler recognizes the pattern people already write, (x >>> n) | (x << (32 - n)) with a constant n

    The Dart team and people like me, an external contributor, have to focus on things that matter and we need to implement them in ways that is scalable and makes sense. Adding a ton of peephole optimizations is dead weight if nobody is going to benefit much from them. I also would like to point you to the full employment theorem. There's an endless amount of optimization due to Gödel so the fact that an inefficiency is discovered is not really valuable in itself. The question is who is going to benefit from it.

    There's a much more scalable approach to adding such peephole optimizations that we don't support yet (mainly because it's the state of the art and an active area of research): if we use tree automata (which in that area are also known as e-graphs) for specifying such patterns it would reduce down to a few lines of code where certain patterns could be optimally transduced into more efficient patterns. That's when adding such peephole optimizations would become much more valuable because we could also be taking algebraics into consideration #64286. We are unfortunately not there yet.

    Fair enough, if native speed is the goal, even better.

    But you keep treating this as part of ton of peephole optimizations. It's one pattern, and LLVM, GCC and V8 already recognize it. Dart AOT is the outlier here: the same Dart source runs 1.7x faster under dart2js, and in the C control the rotate alone is worth 1.36x.

    As for who benefits: most hashes and ciphers written in plain Dart, such as SHA-1/2/3, MD5, BLAKE2/3, ChaCha20, Salsa20, xxHash and SipHash, and KDFs built on them like PBKDF2, scrypt and Argon2, where the hash runs thousands of times per call, all use this pattern, same for package:crypto, pointycastle, cryptography. Code that already writes rotates this way, including the masked 32-bit forms, would get faster without changes. I never mentioned that something new in INT api should be added, it's not the place for such change.

    E-graphs sound like the right long term design, and I'd be glad to see them. Until then, a single rotate pattern seems like a small change with a broad effect.

    I'd also love to see 1:1 with native in the long term, but I'm not sure how to get there without turning a 300-line algorithm into 3-4k lines of manually unrolled loops and similar tricks. I've spent a lot of time reading the assembly Dart generates, and there are many places where it does work that isn't needed, many of them are tiny ones, but each tiny change looks like an unneeded one since it's just a few %, nobody for some reason thinks about cumulative effect which, taken together, yields not just a couple of percent, but steady growth.

  8. added
    area-vmUse area-vm for VM related issues, including code coverage, and the AOT and JIT backends.
    on Sep 29, 2026
  9. added
    P3A lower priority bug or feature request
    type-enhancementA request for a change that isn't a bug
    on Sep 30, 2026
  10. biggs0125 commented on Sep 30, 2026

    @biggs0125

    This is a nice to have. Someone on the Dart team may choose to add the logic to recognize this pattern but this is not really at the top of our queue right now.

  11. biggs0125 commented on Sep 30, 2026

    @biggs0125

    @lrhn Is this something that would be worth adding as an explicit API in somewhere like 'dart:math' (or int directly)?

  12. lrhn commented on Sep 30, 2026

    @lrhn
    Member

    If it's a 32-bit operation, I'd prefer to put it in dart:typed_data in some way. That's where 32-bit numbers live.

    Maybe an extension type Uint32(int _) with multiple 32-bit operations.

    But then the question becomes why there isn't a type for every number kind er have a typed data list for. And whether it should be a real type, not just an extension type, so it doesn't have to box intermediate results as anint

    Just having a single 32-bit operation somewhere feels ad-hoc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3A lower priority bug or feature requestarea-vmUse area-vm for VM related issues, including code coverage, and the AOT and JIT backends.triagedIssue has been triaged by sub teamtype-enhancementA request for a change that isn't a bug

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions