@amritk/nish-x86_64-linux 0.32.0 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -506,17 +506,19 @@ const http1StartRequest = (p: Http1Parser): void => {
506
506
  }
507
507
 
508
508
  /**
509
- * Whether any of the eight bytes of `x` is below 32 or is 127: a control
510
- * byte or DEL, which `http1TakeLine` stops at. Two of the bit tricks of
511
- * "Bit Twiddling Hacks" (S. E. Anderson, public domain): a byte below `n`
512
- * leaves its high bit set in `(x - n * ones) & ~x`, for `n` up to 128, and
513
- * `127` is the byte that is zero in `x ^ 127 * ones`. A byte from 128 up
514
- * has its high bit set in `x`, so `~x` clears it: obs-text never matches.
515
- * The answer is exact for the word, though not for which byte. Each
516
- * subtraction is masked to 64 bits because a `u64` is a BigInt under Node,
517
- * which does not wrap; natively the mask is a no-op.
509
+ * The bytes of `x` below 32 or equal to 127 — a control byte or DEL, which
510
+ * `http1TakeLine` stops at — as their high bits: zero when there is none,
511
+ * and else the lowest set bit is the first such byte's. Two of the bit
512
+ * tricks of "Bit Twiddling Hacks" (S. E. Anderson, public domain): a byte
513
+ * below `n` leaves its high bit set in `(x - n * ones) & ~x`, for `n` up to
514
+ * 128, and `127` is the byte that is zero in `x ^ 127 * ones`. A byte from
515
+ * 128 up has its high bit set in `x`, so `~x` clears it: obs-text never
516
+ * matches. A borrow only runs up from a byte that matched, so the bits above
517
+ * the first match may be false and the lowest is exact, in each mask and so
518
+ * in their union. Each subtraction is masked to 64 bits because a `u64` is a
519
+ * BigInt under Node, which does not wrap; natively the mask is a no-op.
518
520
  */
519
- const http1WordHasControl = (x: u64): boolean => {
521
+ const http1ControlBits = (x: u64): u64 => {
520
522
  const ones32: u64 = toU64(16843009)
521
523
  const ones: u64 = (ones32 << toU64(32)) | ones32
522
524
  const low32: u64 = 0xffffffff
@@ -525,7 +527,18 @@ const http1WordHasControl = (x: u64): boolean => {
525
527
  const below: u64 = (x - ones * toU64(32)) & all & ~x & high
526
528
  const y: u64 = x ^ (ones * toU64(127))
527
529
  const del: u64 = (y - ones) & all & ~y & high
528
- return (below | del) !== toU64(0)
530
+ return below | del
531
+ }
532
+
533
+ /** Which byte of a word `bits`'s lowest set bit is in, 0 to 7; `bits` is not zero. */
534
+ const http1FirstByte = (bits: u64): i32 => {
535
+ const mask32: u64 = 0xffffffff
536
+ const lo: u32 = toU32(bits & mask32)
537
+ if (lo !== toU32(0)) {
538
+ return (toI32(31) - toI32(Math.clz32(lo & (toU32(0) - lo)))) >> toI32(3)
539
+ }
540
+ const hi: u32 = toU32(bits >> toU64(32))
541
+ return toI32(4) + ((toI32(31) - toI32(Math.clz32(hi & (toU32(0) - hi)))) >> toI32(3))
529
542
  }
530
543
 
531
544
  /**
@@ -585,22 +598,39 @@ const http1TakeLine = (p: Http1Parser, limit: i32, overStatus: i32): i32 => {
585
598
  }
586
599
  while (i < stop) {
587
600
  // Most bytes are printable: sixteen at a time while a whole vector is,
588
- // then one at a time, with no test in either loop but the bytes' and the
589
- // bound's.
590
- while (i >= 0 && i <= stop - 16 && i <= toI32(buf.length) - 16) {
601
+ // and the vector that is not answers where its first control byte is.
602
+ // The bytes after a control byte, and a tail shorter than a vector, go
603
+ // one at a time.
604
+ let found: boolean = false
605
+ while (!found && i >= 0 && i <= stop - 16 && i <= toI32(buf.length) - 16) {
591
606
  const v: u8x16 = loadU8x16(buf, i)
592
- if (http1WordHasControl(http1Lanes(v, 0)) || http1WordHasControl(http1Lanes(v, 8))) {
593
- break
607
+ const low: u64 = http1ControlBits(http1Lanes(v, 0))
608
+ if (low !== toU64(0)) {
609
+ i += http1FirstByte(low)
610
+ found = true
611
+ } else {
612
+ const high: u64 = http1ControlBits(http1Lanes(v, 8))
613
+ if (high !== toU64(0)) {
614
+ i += 8 + http1FirstByte(high)
615
+ found = true
616
+ } else {
617
+ i += 16
618
+ }
594
619
  }
595
- i += 16
596
620
  }
597
- while (i >= 0 && i < stop && i < toI32(buf.length) && buf[i] >= 32 && buf[i] !== 127) {
621
+ while (!found && i >= 0 && i < stop && i < toI32(buf.length) && buf[i] >= 32 && buf[i] !== 127) {
598
622
  i += 1
599
623
  }
600
624
  if (i < 0 || i >= stop || i >= toI32(buf.length)) {
601
625
  break
602
626
  }
603
- const c: i32 = toI32(buf[i])
627
+ let c: i32 = toI32(buf[i])
628
+ if (c === 13 && i + 1 < stop && i + 1 < toI32(buf.length) && buf[i + 1] === 10) {
629
+ // CRLF, the end of almost every line: its LF is the next byte, so it
630
+ // is taken here rather than by another trip through the scan.
631
+ i += 1
632
+ c = 10
633
+ }
604
634
  if (c === 10) {
605
635
  if (i === p.start || buf[i - 1] !== 13) {
606
636
  return http1Fail(p, 400, "a line ended in a bare LF")
@@ -636,9 +666,11 @@ const http1TakeLine = (p: Http1Parser, limit: i32, overStatus: i32): i32 => {
636
666
  * `c - 64` and `c - 96` of each: `!` `#`-`'` `*` `+` `-` `.` and the digits
637
667
  * in the first (0x03ff6cfa); the upper-case letters, `^` and `_` in the
638
668
  * second (0xc7fffffe); `` ` ``, the lower-case letters, `|` and `~` in the
639
- * third (0x57ffffff). No byte below 33 or above 126 is a tchar. One shift
640
- * and mask replaces a chain of fourteen comparisons on the parser's hottest
641
- * path (`bench/techempower`).
669
+ * third (0x57ffffff). No byte below 33 or above 126 is a tchar.
670
+ * `http1IsTokenByte` reads them as two 64-bit masks, one for the bytes 0 to
671
+ * 63 and one for 64 to 127, so the test is a select and a shift with no
672
+ * branch on the byte: a name or method scan is the parser's hottest loop
673
+ * after the line scan (`bench/http1-parse`).
642
674
  */
643
675
  const HTTP1_TCHAR_32: i32 = 67071226
644
676
  const HTTP1_TCHAR_64: i32 = -939524098
@@ -646,16 +678,10 @@ const HTTP1_TCHAR_96: i32 = 1476395007
646
678
 
647
679
  /** Whether `c` is a tchar of RFC 9110 §5.6.2, the bytes a token is made of. */
648
680
  export const http1IsTokenByte = (c: i32): boolean => {
649
- if (c < 32 || c > 127) {
650
- return false
651
- }
652
- let mask: i32 = HTTP1_TCHAR_96
653
- if (c < 64) {
654
- mask = HTTP1_TCHAR_32
655
- } else if (c < 96) {
656
- mask = HTTP1_TCHAR_64
657
- }
658
- return ((mask >> (c & 31)) & 1) !== 0
681
+ const low: u64 = toU64(toU32(HTTP1_TCHAR_32)) << toU64(32)
682
+ const high: u64 = (toU64(toU32(HTTP1_TCHAR_96)) << toU64(32)) | toU64(toU32(HTTP1_TCHAR_64))
683
+ const word: u64 = c < 64 ? low : high
684
+ return c >= 0 && c < 128 && ((word >> toU64(c & 63)) & toU64(1)) !== toU64(0)
659
685
  }
660
686
 
661
687
  /** Whether `c` may appear in a field value (RFC 9110 §5.5): HTAB, SP, VCHAR or obs-text. */
@@ -978,9 +1004,30 @@ const http1FieldLine = (p: Http1Parser, keep: boolean): i32 => {
978
1004
  if (a >= 0 && a < b && (buf[a] === 32 || buf[a] === 9)) {
979
1005
  return http1Fail(p, 400, "a field line that starts with whitespace (obs-fold)")
980
1006
  }
1007
+ // A kept line is copied into the head first, and its name is read there,
1008
+ // each upper-case letter lowered as the scan passes it: one walk over the
1009
+ // name, not a check of `buf` and then a second walk of the copy. The copy
1010
+ // of a line refused below is never read, since a refusal ends the parse.
1011
+ let at: i32 = 0
981
1012
  let i: i32 = a
982
- while (i >= 0 && i < b && i < toI32(buf.length) && http1IsTokenByte(toI32(buf[i]))) {
983
- i += 1
1013
+ if (keep) {
1014
+ at = http1HeadAppend(p, a, b)
1015
+ const head: u8[] = p.head
1016
+ let k: i32 = at
1017
+ const stop: i32 = at + (b - a)
1018
+ while (k >= 0 && k < stop && k < toI32(head.length)) {
1019
+ const c: i32 = toI32(head[k])
1020
+ if (!http1IsTokenByte(c)) {
1021
+ break
1022
+ }
1023
+ head[k] = toU8(c >= 65 && c <= 90 ? c + 32 : c)
1024
+ k += 1
1025
+ }
1026
+ i = a + (k - at)
1027
+ } else {
1028
+ while (i >= 0 && i < b && i < toI32(buf.length) && http1IsTokenByte(toI32(buf[i]))) {
1029
+ i += 1
1030
+ }
984
1031
  }
985
1032
  if (i === a || i >= b || buf[i] !== 58) {
986
1033
  return http1Fail(p, 400, "a malformed field name")
@@ -1005,13 +1052,6 @@ const http1FieldLine = (p: Http1Parser, keep: boolean): i32 => {
1005
1052
  while (valueEnd > valueStart && valueEnd - 1 >= 0 && http1IsOws(toI32(buf[valueEnd - 1]))) {
1006
1053
  valueEnd -= 1
1007
1054
  }
1008
- const at: i32 = http1HeadAppend(p, a, b)
1009
- const head: u8[] = p.head
1010
- for (let k: i32 = at; k < at + (nameEnd - a) && k < toI32(head.length); k += 1) {
1011
- if (k >= 0 && head[k] >= 65 && head[k] <= 90) {
1012
- head[k] = head[k] + 32
1013
- }
1014
- }
1015
1055
  const base: i32 = 4 * p.headerCount
1016
1056
  http1SpanSet(p, base + 3, at + (valueEnd - a))
1017
1057
  // The last of the four is set first, so the array has room for them all.
@@ -525,28 +525,19 @@ export class Http1OutputPool {
525
525
  * `output[outputStart .. outputEnd)` for its carrier to send.
526
526
  */
527
527
  export class Http1Connection {
528
- // References, then 64-bit fields, then 32-bit ones, then flags: the order
529
- // the layout packs without padding.
530
- config: Http1Config
531
- /**
532
- * The `Date` every final response carries. The connection makes its own;
533
- * `Http1Server` and `Http1TlsServer` put theirs here, so that every slot of a
534
- * pool shares one and the pool formats at most once a second. A program may
535
- * put another, to share it or to pin its clock.
536
- */
537
- date: HttpDate
528
+ // What a request reads and writes comes first, so that `inputRoom`, `feed`,
529
+ // `next`, `respond`, `write`, `end`, `consume`, `release` and `park` touch
530
+ // the front of the object: the parser, the output and its pool, the clocks,
531
+ // the state and the flags. What a WebSocket, an error or a configured cap
532
+ // needs follows. Within each part the layout packs as widest first does:
533
+ // the narrow fields of the front end on a 4-byte boundary and the cold
534
+ // 32-bit fields bring the references behind them to an 8-byte one.
538
535
  /**
539
536
  * The request parser: its head (`methodIs`, `targetIs`, `header`, spans) is
540
537
  * the current request's from its `H1_REQUEST` until its response is done,
541
538
  * `H1_END` included, however much of a pipelined request is fed behind it.
542
539
  */
543
540
  parser: Http1Parser
544
- /**
545
- * The frame decoder: made when the slot first switches to WebSocket, so a
546
- * slot that only ever speaks HTTP never holds its buffers, then kept for
547
- * the slot's later peers.
548
- */
549
- ws: WsDecoder | null
550
541
  /**
551
542
  * Bytes to send are `output[outputStart .. outputEnd)`. Empty until the
552
543
  * connection first needs room to write: then its own buffer, made once, or
@@ -560,27 +551,17 @@ export class Http1Connection {
560
551
  /** Where the parser's input and head buffers go while the slot rests, with `Http1Config.idleInput`; null otherwise. */
561
552
  inputs: Http1OutputPool | null
562
553
  heads: Http1OutputPool | null
563
- /** `H1_BODY`'s and `H1_WS_MESSAGE`'s bytes. */
564
- data: u8[]
565
- /** A ping's payload, kept until its pong fits: empty until the first ping. */
566
- pong: u8[]
567
- /** No fields, for the responses the connection writes itself: made once. */
568
- noFields: u8[][]
554
+ /**
555
+ * The `Date` every final response carries. The connection makes its own;
556
+ * `Http1Server` and `Http1TlsServer` put theirs here, so that every slot of a
557
+ * pool shares one and the pool formats at most once a second. A program may
558
+ * put another, to share it or to pin its clock.
559
+ */
560
+ date: HttpDate
569
561
  /** When the carrier last moved a byte for the connection, in `monotonicNanos`. */
570
562
  lastActive: i64 = 0
571
563
  /** When the head being read began to arrive, in `monotonicNanos`; meaningful while `headTimed`. */
572
564
  headSince: i64 = 0
573
- outputStart: i32 = 0
574
- outputEnd: i32 = 0
575
- dataStart: i32 = 0
576
- dataLength: i32 = 0
577
- /** `H1_WS_MESSAGE`'s opcode: text or binary. */
578
- opcode: i32 = 0
579
- /** `H1_ERROR`'s HTTP status or WebSocket close code, or `H1_WS_CLOSE`'s close code. */
580
- status: i32 = 0
581
- /** The most input the connection buffers, unread: the cap `inputRoom` holds it to. */
582
- bufferLimit: i32 = 0
583
- pongLength: i32 = 0
584
565
  state: i32 = 0
585
566
  response: i32 = 0
586
567
  /**
@@ -590,12 +571,23 @@ export class Http1Connection {
590
571
  responseFraming: i32 = 0
591
572
  /** A response with a length: the bytes it still owes. */
592
573
  responseLeft: i32 = 0
593
- /** The response carries no body (HEAD, 204, 304): what is written is dropped. */
594
- responseEmpty: boolean = false
574
+ outputStart: i32 = 0
575
+ outputEnd: i32 = 0
576
+ /**
577
+ * `output.length`, kept beside the offsets so that `acquire` and `release`
578
+ * learn whether the connection holds a buffer without reading an array:
579
+ * the empty one is out of cache by the time a resting connection's next
580
+ * request comes. Only the connection assigns `output`, and with it this.
581
+ */
582
+ outputCap: i32 = 0
583
+ /** The most input the connection buffers, unread: the cap `inputRoom` holds it to. */
584
+ bufferLimit: i32 = 0
595
585
  /** A request's head has been read and its exchange is not over. */
596
586
  requestOpen: boolean = false
597
587
  /** The request's body has been read to its end. */
598
588
  requestEnded: boolean = false
589
+ /** The response carries no body (HEAD, 204, 304): what is written is dropped. */
590
+ responseEmpty: boolean = false
599
591
  /** The connection closes once this exchange is over. */
600
592
  closeAfter: boolean = false
601
593
  /** The program accepted the request's WebSocket handshake. */
@@ -607,6 +599,13 @@ export class Http1Connection {
607
599
  inputEnded: boolean = false
608
600
  /** A head has begun to arrive and is not read whole yet: `headSince` is its deadline's start. */
609
601
  headTimed: boolean = false
602
+ dataStart: i32 = 0
603
+ dataLength: i32 = 0
604
+ /** `H1_WS_MESSAGE`'s opcode: text or binary. */
605
+ opcode: i32 = 0
606
+ /** `H1_ERROR`'s HTTP status or WebSocket close code, or `H1_WS_CLOSE`'s close code. */
607
+ status: i32 = 0
608
+ pongLength: i32 = 0
610
609
  /**
611
610
  * The TCP maximum segment size the program sets for this connection, for
612
611
  * `respondEncoded` under `HttpCompressor.segmentMode`; 0, the default,
@@ -616,6 +615,19 @@ export class Http1Connection {
616
615
  mss: i32 = 0
617
616
  /** The MSS `Http1Server.accept` read from this connection's socket (`netMss`), or 0 when it reports none. */
618
617
  socketMss: i32 = 0
618
+ config: Http1Config
619
+ /**
620
+ * The frame decoder: made when the slot first switches to WebSocket, so a
621
+ * slot that only ever speaks HTTP never holds its buffers, then kept for
622
+ * the slot's later peers.
623
+ */
624
+ ws: WsDecoder | null
625
+ /** `H1_BODY`'s and `H1_WS_MESSAGE`'s bytes. */
626
+ data: u8[]
627
+ /** A ping's payload, kept until its pong fits: empty until the first ping. */
628
+ pong: u8[]
629
+ /** No fields, for the responses the connection writes itself: made once. */
630
+ noFields: u8[][]
619
631
 
620
632
  /** A connection under `config`. A cap outside what the connection can work with panics. */
621
633
  constructor(config: Http1Config) {
@@ -759,10 +771,15 @@ export class Http1Connection {
759
771
  if (this.writeHeld || this.inputEnded || this.state === H1_S_CLOSING || this.state === H1_S_FAILED) {
760
772
  return 0
761
773
  }
762
- const ws: WsDecoder | null = this.ws
763
- const held: i32 =
764
- this.state === H1_S_WEBSOCKET && ws !== null ? ws.end - ws.start : this.parser.buffered()
765
- const room: i32 = this.inputLimit() - held
774
+ let room: i32 = 0
775
+ if (this.state === H1_S_WEBSOCKET) {
776
+ // Only a WebSocket reads the decoder and the configured message cap,
777
+ // so a request's check stays on the connection's front fields.
778
+ const ws: WsDecoder | null = this.ws
779
+ room = this.inputLimit() - (ws !== null ? ws.end - ws.start : this.parser.buffered())
780
+ } else {
781
+ room = this.bufferLimit - this.parser.buffered()
782
+ }
766
783
  return room > 0 ? room : 0
767
784
  }
768
785
 
@@ -809,11 +826,13 @@ export class Http1Connection {
809
826
  * the pool has no free buffer, or for a connection's own, once.
810
827
  */
811
828
  acquire(): void {
812
- if (toI32(this.output.length) > 0) {
829
+ if (this.outputCap > 0) {
813
830
  return
814
831
  }
815
832
  const pool: Http1OutputPool | null = this.pool
816
- this.output = pool !== null ? pool.take() : new Array<u8>(this.config.outputSize)
833
+ const output: u8[] = pool !== null ? pool.take() : new Array<u8>(this.config.outputSize)
834
+ this.output = output
835
+ this.outputCap = toI32(output.length)
817
836
  this.outputStart = 0
818
837
  this.outputEnd = 0
819
838
  }
@@ -825,16 +844,12 @@ export class Http1Connection {
825
844
  */
826
845
  release(): void {
827
846
  const pool: Http1OutputPool | null = this.pool
828
- if (
829
- pool === null ||
830
- toI32(this.output.length) === 0 ||
831
- this.outputEnd > this.outputStart ||
832
- this.writeHeld
833
- ) {
847
+ if (pool === null || this.outputCap === 0 || this.outputEnd > this.outputStart || this.writeHeld) {
834
848
  return
835
849
  }
836
850
  pool.give(this.output)
837
851
  this.output = this.noOutput
852
+ this.outputCap = 0
838
853
  this.outputStart = 0
839
854
  this.outputEnd = 0
840
855
  }