|
|
Microsoft Windows NT Build 511 (DDK SDK) 11-01-1993
// TITLE("Large Integer Arithmetic")
//++
//
// Copyright (c) 1990 Microsoft Corporation
// Copyright (c) 1993 Digital Equipment Corporation
//
// Module Name:
//
// largeint.s
//
// Abstract:
//
// This module implements routines for performing extended integer
// arithmetic.
//
// Author:
//
// David N. Cutler (davec) 18-Apr-1990
//
// Environment:
//
// Any mode.
//
// Revision History:
//
// Thomas Van Baak (tvb) 9-May-1992
//
// Adapted for Alpha AXP.
//
//--
#include "ksalpha.h"
//
// Alpha AXP Implementation Notes:
//
// The LargeInteger functions defined below implement a set of portable
// 64-bit integer arithmetic operations for x86, Mips, and Alpha systems
// using the LARGE_INTEGER data type. Code using LARGE_INTEGER variables
// and calling these functions will be portable across all NT platforms.
// This is the recommended approach to 64-bit arithmetic on NT.
//
// However, if performance is more important than portability, then for
// Alpha systems, the native 64-bit integer data types may be used instead
// of the LARGE_INTEGER type and the LargeInteger functions. The Alpha C
// compilers support a __int64 data type (renamed LONGLONG in the system
// header files). All C integer arithmetic operators may be used with
// these quadword types. This eliminates the need for, and the overhead
// of, any of the portable LargeInteger functions.
//
// In general, a LARGE_INTEGER cannot simply be converted to a LONGLONG
// because of explicit references in application code to the 32-bit LowPart
// and HighPart members of the LARGE_INTEGER structure.
//
// The performance difference between using the portable LARGE_INTEGER
// types with LargeInteger functions and the Alpha LONGLONG types and
// operations is often not significant enough to warrant modifying otherwise
// portable code. In addition, future compiler optimization and inlining may
// actually result in identical performance between portable LARGE_INTEGER
// code and Alpha specific LONGLONG code. Therefore it is recommended to
// keep NT source code portable.
//
// The Alpha source for the large integer functions below differs from the
// Mips version since for Alpha a 64-bit argument or return value is passed
// through a 64-bit integer register. In addition, most operations are
// implemented with a single instruction. The division routines below,
// however, like Mips, use a simple shift/subtract algorithm which is
// considerably slower than the algorithm used by Alpha runtime division
// routines.
//
SBTTL("Convert Long to Large Integer")
//++
//
// LARGE_INTEGER
// ConvertLongToLargeInteger (
// IN LONG SignedInteger
// )
//
// Routine Description:
//
// This function converts a signed integer to a signed large integer
// and returns the result.
//
// Arguments:
//
// SignedInteger (a0) - Supplies the value to convert.
//
// Return Value:
//
// The large integer result is returned as the function value in v0.
//
//--
LEAF_ENTRY(ConvertLongToLargeInteger)
addl a0, 0, v0 // ensure canonical (signed long) form
ret zero, (ra) // return
.end ConvertLongToLargeInteger
SBTTL("Convert Ulong to Large Integer")
//++
//
// LARGE_INTEGER
// ConvertUlongToLargeInteger (
// IN ULONG UnsignedInteger
// )
//
// Routine Description:
//
// This function converts an unsigned integer to a signed large
// integer and returns the result.
//
// Arguments:
//
// UnsignedInteger (a0) - Supplies the value to convert.
//
// Return Value:
//
// The large integer result is returned as the function value in v0.
//
//--
LEAF_ENTRY(ConvertUlongToLargeInteger)
zap a0, 0xf0, v0 // convert canonical ULONG to quadword
ret zero, (ra) // return
.end ConvertUlongToLargeInteger
SBTTL("Enlarged Signed Integer Multiply")
//++
//
// LARGE_INTEGER
// EnlargedIntegerMultiply (
// IN LONG Multiplicand,
// IN LONG Multiplier
// )
//
// Routine Description:
//
// This function multiplies a signed integer by a signed integer and
// returns a signed large integer result.
//
// N.B. An overflow is not possible.
//
// Arguments:
//
// Multiplicand (a0) - Supplies the multiplicand value.
//
// Multiplier (a1) - Supplies the multiplier value.
//
// Return Value:
//
// The large integer result is returned as the function value in v0.
//
//--
LEAF_ENTRY(EnlargedIntegerMultiply)
addl a0, 0, a0 // ensure canonical (signed long) form
addl a1, 0, a1 // ensure canonical (signed long) form
mulq a0, a1, v0 // multiply signed both quadwords
ret zero, (ra) // return
.end EnlargedIntegerMultiply
SBTTL("Enlarged Unsigned Divide")
//++
//
// ULONG
// EnlargedUnsignedDivide (
// IN ULARGE_INTEGER Dividend,
// IN ULONG Divisor,
// IN OUT PULONG Remainder OPTIONAL
// )
//
// Routine Description:
//
// This function divides an unsigned large integer by an unsigned long
// and returns the resultant quotient and optionally the remainder.
//
// N.B. An overflow or divide by zero exception is possible.
//
// Arguments:
//
// Dividend (a0) - Supplies the unsigned 64-bit dividend value.
//
// Divisor (a1) - Supplies the unsigned 32-bit divisor value.
//
// Remainder (a2) - Supplies an optional pointer to a variable that
// receives the unsigned 32-bit remainder.
//
// Return Value:
//
// The unsigned long integer quotient is returned as the function value.
//
//--
LEAF_ENTRY(EnlargedUnsignedDivide)
//
// Check for division by zero.
//
zap a1, 0xf0, a1 // convert ULONG divisor to quadword
beq a1, 30f // trap if divisor is zero
//
// Check for overflow. If the divisor is less than the upper half of the
// dividend the quotient would be wider than 32 bits.
//
srl a0, 32, t0 // get upper longword of dividend
cmpule a1, t0, t1 // is divisor <= upper dividend?
bne t1, 40f // if ne[true], then overflow trap
//
// Perform the shift/subtract loop 8 times and 4 bits per loop.
//
// t0 - Temp used for 0/1 results of compares.
// t1 - High 64-bits of 128-bit (t1, a0) dividend.
// t2 - Loop counter.
//
ldiq t2, 32/4 // set iteration count
srl a0, 32, t1 // get top 32 bits of carry-out
sll a0, 32, a0 // preshift first 32 bits left
10: cmplt a0, 0, t0 // predict low-dividend shift carry-out
addq a0, a0, a0 // shift low-dividend left
addq t1, t1, t1 // shift high-dividend left
bis t1, t0, t1 // merge in carry-out of low-dividend
cmpule a1, t1, t0 // if dividend >= divisor,
addq a0, t0, a0 // then set quotient bit
subq t1, a1, t0 // subtract divisor from dividend,
cmovlbs a0, t0, t1 // if dividend >= divisor
cmplt a0, 0, t0 // predict low-dividend shift carry-out
addq a0, a0, a0 // shift low-dividend left
addq t1, t1, t1 // shift high-dividend left
bis t1, t0, t1 // merge in carry-out of low-dividend
cmpule a1, t1, t0 // if dividend >= divisor,
addq a0, t0, a0 // then set quotient bit
subq t1, a1, t0 // subtract divisor from dividend,
cmovlbs a0, t0, t1 // if dividend >= divisor
cmplt a0, 0, t0 // predict low-dividend shift carry-out
addq a0, a0, a0 // shift low-dividend left
addq t1, t1, t1 // shift high-dividend left
bis t1, t0, t1 // merge in carry-out of low-dividend
cmpule a1, t1, t0 // if dividend >= divisor,
addq a0, t0, a0 // then set quotient bit
subq t1, a1, t0 // subtract divisor from dividend,
cmovlbs a0, t0, t1 // if dividend >= divisor
cmplt a0, 0, t0 // predict low-dividend shift carry-out
addq a0, a0, a0 // shift low-dividend left
addq t1, t1, t1 // shift high-dividend left
bis t1, t0, t1 // merge in carry-out of low-dividend
cmpule a1, t1, t0 // if dividend >= divisor,
addq a0, t0, a0 // then set quotient bit
subq t1, a1, t0 // subtract divisor from dividend,
cmovlbs a0, t0, t1 // if dividend >= divisor
subq t2, 1, t2 // any more iterations?
bne t2, 10b //
//
// Finished with remainder value in t1 and quotient value in a0.
//
addl a0, 0, v0 // set longword quotient return value
beq a2, 20f // skip optional remainder store
stl t1, 0(a2) // store longword remainder
20: ret zero, (ra) // return
//
// Generate an exception for divide by zero and return a zero quotient if the
// caller continues execution.
//
30: ldil a0, GENTRAP_INTEGER_DIVIDE_BY_ZERO
GENERATE_TRAP
ldil v0, 0 // return zero quotient
ret zero, (ra) // return
//
// Generate an exception for overflow.
//
40: ldiq a0, 0x8000000000000000 //
subqv zero, a0, v0 // negate in order to overflow
trapb // wait for trap to occur
ret zero, (ra) // return
.end EnlargedUnsignedDivide
SBTTL("Enlarged Unsigned Integer Multiply")
//++
//
// LARGE_INTEGER
// EnlargedUnsignedMultiply (
// IN ULONG Multiplicand,
// IN ULONG Multiplier
// )
//
// Routine Description:
//
// This function multiplies an unsigned integer by an unsigned integer
// and returns a signed large integer result.
//
// Arguments:
//
// Multiplicand (a0) - Supplies the multiplicand value.
//
// Multiplier (a1) - Supplies the multiplier value.
//
// Return Value:
//
// The large integer result is returned as the function value in v0.
//
//--
LEAF_ENTRY(EnlargedUnsignedMultiply)
zap a0, 0xf0, a0 // convert canonical ULONG to quadword
zap a1, 0xf0, a1 // convert canonical ULONG to quadword
mulq a0, a1, v0 // multiply signed both quadwords
ret zero, (ra) // return
.end EnlargedUnsignedMultiply
SBTTL("Extended Integer Multiply")
//++
//
// LARGE_INTEGER
// ExtendedIntegerMultiply (
// IN LARGE_INTEGER Multiplicand,
// IN LONG Multiplier
// )
//
// Routine Description:
//
// This function multiplies a signed large integer by a signed integer and
// returns the signed large integer result.
//
// N.B. An overflow is possible, but no exception is generated.
//
// Arguments:
//
// Multiplicand (a0) - Supplies the multiplicand value.
//
// Multiplier (a1) - Supplies the multiplier value.
//
// Return Value:
//
// The large integer result is returned as the function value in v0.
//
//--
LEAF_ENTRY(ExtendedIntegerMultiply)
addl a1, 0, a1 // ensure canonical (signed long) form
mulq a0, a1, v0 // multiply signed both quadwords
ret zero, (ra) // return
.end ExtendedIntegerMultiply
SBTTL("Extended Large Integer Divide")
//++
//
// LARGE_INTEGER
// ExtendedLargeIntegerDivide (
// IN LARGE_INTEGER Dividend,
// IN ULONG Divisor,
// IN OUT PULONG Remainder OPTIONAL
// )
//
// Routine Description:
//
// This function divides an unsigned large integer by an unsigned long
// and returns the quadword quotient and optionally the long remainder.
//
// N.B. A divide by zero exception is possible.
//
// Arguments:
//
// Dividend (a0) - Supplies the dividend value.
//
// Divisor (a1) - Supplies the divisor value.
//
// Remainder (a2) - Supplies an optional pointer to a variable that
// receives the remainder.
//
// Return Value:
//
// The large integer result is returned as the function value in v0.
//
//--
LEAF_ENTRY(ExtendedLargeIntegerDivide)
//
// Check for division by zero.
//
zap a1, 0xf0, a1 // convert canonical ULONG to quadword
beq a1, 30f // trap if divisor is zero
//
// Perform the shift/subtract loop 16 times and 4 bits per loop.
//
// t0 - Temp used for 0/1 results of compares.
// t1 - High 64-bits of 128-bit (t1, a0) dividend.
// t2 - Loop counter.
//
ldiq t2, 64/4 // set iteration count
ldiq t1, 0 // zero-extend dividend to 128 bits
10: cmplt a0, 0, t0 // predict low-dividend shift carry-out
addq a0, a0, a0 // shift low-dividend left
addq t1, t1, t1 // shift high-dividend left
bis t1, t0, t1 // merge in carry-out of low-dividend
cmpule a1, t1, t0 // if dividend >= divisor,
addq a0, t0, a0 // then set quotient bit
subq t1, a1, t0 // subtract divisor from dividend,
cmovlbs a0, t0, t1 // if dividend >= divisor
cmplt a0, 0, t0 // predict low-dividend shift carry-out
addq a0, a0, a0 // shift low-dividend left
addq t1, t1, t1 // shift high-dividend left
bis t1, t0, t1 // merge in carry-out of low-dividend
cmpule a1, t1, t0 // if dividend >= divisor,
addq a0, t0, a0 // then set quotient bit
subq t1, a1, t0 // subtract divisor from dividend,
cmovlbs a0, t0, t1 // if dividend >= divisor
cmplt a0, 0, t0 // predict low-dividend shift carry-out
addq a0, a0, a0 // shift low-dividend left
addq t1, t1, t1 // shift high-dividend left
bis t1, t0, t1 // merge in carry-out of low-dividend
cmpule a1, t1, t0 // if dividend >= divisor,
addq a0, t0, a0 // then set quotient bit
subq t1, a1, t0 // subtract divisor from dividend,
cmovlbs a0, t0, t1 // if dividend >= divisor
cmplt a0, 0, t0 // predict low-dividend shift carry-out
addq a0, a0, a0 // shift low-dividend left
addq t1, t1, t1 // shift high-dividend left
bis t1, t0, t1 // merge in carry-out of low-dividend
cmpule a1, t1, t0 // if dividend >= divisor,
addq a0, t0, a0 // then set quotient bit
subq t1, a1, t0 // subtract divisor from dividend,
cmovlbs a0, t0, t1 // if dividend >= divisor
subq t2, 1, t2 // any more iterations?
bne t2, 10b //
//
// Finished with remainder value in t1 and quotient value in a0.
//
mov a0, v0 // set quadword quotient return value
beq a2, 20f // skip optional remainder store
stl t1, 0(a2) // store longword remainder
20: ret zero, (ra) // return
//
// Generate an exception for divide by zero.
//
30: ldil a0, GENTRAP_INTEGER_DIVIDE_BY_ZERO
GENERATE_TRAP
br zero, 30b // in case they continue
.end ExtendedLargeIntegerDivide
SBTTL("Extended Magic Divide")
//++
//
// LARGE_INTEGER
// ExtendedMagicDivide (
// IN LARGE_INTEGER Dividend,
// IN LARGE_INTEGER MagicDivisor,
// IN CCHAR ShiftCount
// )
//
// Routine Description:
//
// This function divides a signed large integer by an unsigned large integer
// and returns the signed large integer result. The division is performed
// using reciprocal multiplication of a signed large integer value by an
// unsigned large integer fraction which represents the most significant
// 64-bits of the reciprocal divisor rounded up in its least significant bit
// and normalized with respect to bit 63. A shift count is also provided
// which is used to truncate the fractional bits from the result value.
//
// Arguments:
//
// Dividend (a0) - Supplies the dividend value.
//
// MagicDivisor (a1) - Supplies the magic divisor value which
// is a 64-bit multiplicative reciprocal.
//
// Shiftcount (a2) - Supplies the right shift adjustment value.
//
// Return Value:
//
// The large integer result is returned as the function value in v0.
//
//--
LEAF_ENTRY(ExtendedMagicDivide)
//
// Make the dividend positive for the reciprocal multiplication to work.
//
negq a0, t0 // negate dividend
cmovgt a0, a0, t0 // get absolute value in t0
//
// Multiply both quadword arguments together and take only the upper 64 bits of
// the resulting 128 bit product. This can be done using the umulh instruction.
//
// Division of a dividend by a constant divisor through reciprocal
// multiplication works because a/b is equivalent to (a*x)/(b*x) for any
// value of x. Now if b is a constant, some "magic" integer x can be chosen,
// based on the value of b, so that (b*x) is very close to a large power of
// two (e.g., 2^64). Then a/b = (a*x)/(b*x) = (a*x)/(2^64) = (a*x)>>64. This
// effectively turns the problem of division by a constant into multiplication
// by a constant which is a much faster operation.
//
umulh t0, a1, t1 // multiply high both quadword arguments
sra t1, a2, t1 // shift result right by requested amount
//
// Make the result negative if the dividend was negative.
//
negq t1, t0 // negate result
cmovgt a0, t1, t0 // restore sign of dividend
mov t0, v0 // set quadword result return value
ret zero, (ra) // return
.end ExtendedMagicDivide
SBTTL("Large Integer Add")
//++
//
// LARGE_INTEGER
// LargeIntegerAdd (
// IN LARGE_INTEGER Addend1,
// IN LARGE_INTEGER Addend2
// )
//
// Routine Description:
//
// This function adds a signed large integer to a signed large integer and
// returns the signed large integer result.
//
// N.B. An overflow is possible, but no exception is generated.
//
// Arguments:
//
// Addend1 (a0) - Supplies the first addend value.
//
// Addend2 (a1) - Supplies the second addend value.
//
// Return Value:
//
// The large integer result is returned as the function value in v0.
//
//--
LEAF_ENTRY(LargeIntegerAdd)
addq a0, a1, v0 // add both quadword arguments
ret zero, (ra) // return
.end LargeIntegerAdd
SBTTL("Large Integer Arithmetic Shift Right")
//++
//
// LARGE_INTEGER
// LargeIntegerArithmeticShift (
// IN LARGE_INTEGER LargeInteger,
// IN CCHAR ShiftCount
// )
//
// Routine Description:
//
// This function shifts a signed large integer right by an unsigned integer
// modulo 64 and returns the shifted signed large integer result.
//
// Arguments:
//
// LargeInteger (a0) - Supplies the large integer to be shifted.
//
// ShiftCount (a1) - Supplies the right shift count.
//
// Return Value:
//
// The large integer result is returned as the function value in v0.
//
//--
LEAF_ENTRY(LargeIntegerArithmeticShift)
sra a0, a1, v0 // shift the quadword right/arithmetic
ret zero, (ra) // return
.end LargeIntegerArithmeticShift
SBTTL("Large Integer Divide")
//++
//
// LARGE_INTEGER
// LargeIntegerDivide (
// IN LARGE_INTEGER Dividend,
// IN LARGE_INTEGER Divisor,
// IN OUT PLARGE_INTEGER Remainder OPTIONAL
// )
//
// Routine Description:
//
// This function divides an unsigned large integer by an unsigned large
// integer and returns the quadword quotient and optionally the quadword
// remainder.
//
// N.B. A divide by zero exception is possible.
//
// Arguments:
//
// Dividend (a0) - Supplies the dividend value.
//
// Divisor (a1) - Supplies the divisor value.
//
// Remainder (a2) - Supplies an optional pointer to a variable that
// receives the remainder.
//
// Return Value:
//
// The large integer result is returned as the function value in v0.
//
//--
LEAF_ENTRY(LargeIntegerDivide)
//
// Check for division by zero.
//
beq a1, 30f // trap if divisor is zero
//
// Perform the shift/subtract loop 16 times and 4 bits per loop.
//
// t0 - Temp used for 0/1 results of compares.
// t1 - High 64-bits of 128-bit (t1, a0) dividend.
// t2 - Loop counter.
//
ldiq t2, 64/4 // set iteration count
ldiq t1, 0 // zero-extend dividend to 128 bits
10: cmplt a0, 0, t0 // predict low-dividend shift carry-out
addq a0, a0, a0 // shift low-dividend left
addq t1, t1, t1 // shift high-dividend left
bis t1, t0, t1 // merge in carry-out of low-dividend
cmpule a1, t1, t0 // if dividend >= divisor,
addq a0, t0, a0 // then set quotient bit
subq t1, a1, t0 // subtract divisor from dividend,
cmovlbs a0, t0, t1 // if dividend >= divisor
cmplt a0, 0, t0 // predict low-dividend shift carry-out
addq a0, a0, a0 // shift low-dividend left
addq t1, t1, t1 // shift high-dividend left
bis t1, t0, t1 // merge in carry-out of low-dividend
cmpule a1, t1, t0 // if dividend >= divisor,
addq a0, t0, a0 // then set quotient bit
subq t1, a1, t0 // subtract divisor from dividend,
cmovlbs a0, t0, t1 // if dividend >= divisor
cmplt a0, 0, t0 // predict low-dividend shift carry-out
addq a0, a0, a0 // shift low-dividend left
addq t1, t1, t1 // shift high-dividend left
bis t1, t0, t1 // merge in carry-out of low-dividend
cmpule a1, t1, t0 // if dividend >= divisor,
addq a0, t0, a0 // then set quotient bit
subq t1, a1, t0 // subtract divisor from dividend,
cmovlbs a0, t0, t1 // if dividend >= divisor
cmplt a0, 0, t0 // predict low-dividend shift carry-out
addq a0, a0, a0 // shift low-dividend left
addq t1, t1, t1 // shift high-dividend left
bis t1, t0, t1 // merge in carry-out of low-dividend
cmpule a1, t1, t0 // if dividend >= divisor,
addq a0, t0, a0 // then set quotient bit
subq t1, a1, t0 // subtract divisor from dividend,
cmovlbs a0, t0, t1 // if dividend >= divisor
subq t2, 1, t2 // any more iterations?
bne t2, 10b //
//
// Finished with remainder value in t1 and quotient value in a0.
//
mov a0, v0 // set quadword quotient return value
beq a2, 20f // skip optional remainder store
stq t1, 0(a2) // store quadword remainder
20: ret zero, (ra) // return
//
// Generate an exception for divide by zero.
//
30: ldil a0, GENTRAP_INTEGER_DIVIDE_BY_ZERO
GENERATE_TRAP
br zero, 30b // in case they continue
.end LargeIntegerDivide
SBTTL("Large Integer Negate")
//++
//
// LARGE_INTEGER
// LargeIntegerNegate (
// IN LARGE_INTEGER Subtrahend
// )
//
// Routine Description:
//
// This function negates a signed large integer and returns the signed
// large integer result.
//
// N.B. An overflow is possible, but no exception is generated.
//
// Arguments:
//
// Subtrahend (a0) - Supplies the subtrahend value.
//
// Return Value:
//
// The large integer result is returned as the function value in v0.
//
//--
LEAF_ENTRY(LargeIntegerNegate)
subq zero, a0, v0 // negate the quadword argument
ret zero, (ra) // return
.end LargeIntegerNegate
SBTTL("Large Integer Shift Left")
//++
//
// LARGE_INTEGER
// LargeIntegerShiftLeft (
// IN LARGE_INTEGER LargeInteger,
// IN CCHAR ShiftCount
// )
//
// Routine Description:
//
// This function shifts a signed large integer left by an unsigned integer
// modulo 64 and returns the shifted signed large integer result.
//
// Arguments:
//
// LargeInteger (a0) - Supplies the large integer to be shifted.
//
// ShiftCount (a1) - Supplies the left shift count.
//
// Return Value:
//
// The large integer result is returned as the function value in v0.
//
//--
LEAF_ENTRY(LargeIntegerShiftLeft)
sll a0, a1, v0 // shift the quadword argument left
ret zero, (ra) // return
.end LargeIntegerShiftLeft
SBTTL("Large Integer Logical Shift Right")
//++
//
// LARGE_INTEGER
// LargeIntegerShiftRight (
// IN LARGE_INTEGER LargeInteger,
// IN CCHAR ShiftCount
// )
//
// Routine Description:
//
// This function shifts an unsigned large integer right by an unsigned
// integer modulo 64 and returns the shifted unsigned large integer result.
//
// Arguments:
//
// LargeInteger (a0) - Supplies the large integer to be shifted.
//
// ShiftCount (a1) - Supplies the right shift count.
//
// Return Value:
//
// The large integer result is returned as the function value in v0.
//
//--
LEAF_ENTRY(LargeIntegerShiftRight)
srl a0, a1, v0 // shift the quadword right/logical
ret zero, (ra) // return
.end LargeIntegerShiftRight
SBTTL("Large Integer Subtract")
//++
//
// LARGE_INTEGER
// LargeIntegerSubtract (
// IN LARGE_INTEGER Minuend,
// IN LARGE_INTEGER Subtrahend
// )
//
// Routine Description:
//
// This function subtracts a signed large integer from a signed large
// integer and returns the signed large integer result.
//
// N.B. An overflow is possible, but no exception is generated.
//
// Arguments:
//
// Minuend (a0) - Supplies the minuend value.
//
// Subtrahend (a1) - Supplies the subtrahend value.
//
// Return Value:
//
// The large integer result is returned as the function value in v0.
//
//--
LEAF_ENTRY(LargeIntegerSubtract)
subq a0, a1, v0 // subtract the quadword arguments
ret zero, (ra) // return
.end LargeIntegerSubtract
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.