Skip to content

Commit 220f9c4

Browse files
committed
fix: use exact f32 oracle and compact float formatting buffers
1 parent 660bd25 commit 220f9c4

18 files changed

Lines changed: 267 additions & 87 deletions

‎std/assembly/util/dtoa.ts‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { CharCode, POWERS10 } from "./string";
2-
import { DIGITS, MAX_DOUBLE_LENGTH } from "./number";
2+
import { DIGITS } from "./number";
33

44
// Fixed-point log significands shared by the dec-exp / binary-exp estimates below.
55
@lazy @inline const LOG10_2_SIGNIFICAND = 0x4D105; // ~(log10(2) * 2**20)
@@ -136,7 +136,7 @@ import { DIGITS, MAX_DOUBLE_LENGTH } from "./number";
136136
}
137137

138138
// floor(log10(2**bin_exp)). (The f64 path only ever needs the regular form; the
139-
// irregular 3/4 variant lives in ftoa.ts's own copy.)
139+
// irregular 3/4 variant lives in the f32 section below.)
140140
// @ts-ignore: decorator
141141
@inline export function toDecExponent(binExp: i32): i32 {
142142
return (binExp * LOG10_2_SIGNIFICAND) >> LOG10_2_EXP;
@@ -784,6 +784,8 @@ function toBcd8(value: u64): i32 {
784784
return formatDecodedDouble(buf, bits, binExp, binSig, dotZero);
785785
}
786786

787+
/** Low-level shortest-decimal string without an integer `.0` suffix.
788+
* Use util/number.dtoa for AssemblyScript string-conversion conventions. */
787789
export function dtoa(value: f64): string {
788790
let bits = reinterpret<u64>(value);
789791
let exp = <i32>((bits << 1) >> 53);
@@ -1058,6 +1060,8 @@ export function dtoa_buffered(buffer: usize, value: f64): u32 {
10581060
return writeExpNotationFloat(buf, start, decExp, hasLastDigit, hasExtraDigit);
10591061
}
10601062

1063+
/** f32 counterpart of dtoa, also without an integer `.0` suffix.
1064+
* Use util/number.dtoa<f32> for AssemblyScript string conversions. */
10611065
export function ftoa(value: f32): string {
10621066
let bits = reinterpret<u32>(value);
10631067
let exp = <i32>((bits << 1) >> 24);

‎std/assembly/util/number.ts‎

Lines changed: 45 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ import { idof } from "../builtins";
44
import { CharCode } from "./string";
55
import * as floatFormat from "./dtoa";
66

7-
// >= 32 code units (64 bytes) per value
7+
// 28 code units (56 bytes) per value, including full digit-block stores.
88
// @ts-ignore: decorator
99
@inline
10-
export const MAX_DOUBLE_LENGTH = 32;
10+
export const MAX_DOUBLE_LENGTH = 28;
1111

1212
/*
1313
Lookup table for pairwise char codes in range [0-99]
@@ -418,50 +418,53 @@ export function dtoa<T extends number>(value: T): String {
418418
if (isNaN(value)) return "NaN";
419419
return select<String>("-Infinity", "Infinity", value < 0);
420420
}
421-
// Exact integers in this range can be written into their final allocation.
422-
// The f32 limit matters: larger integral f32 values can have a shorter
423-
// decimal representation than their exact integer value.
424-
let exactIntegerRange = false;
425-
if (isFloat<T>()) {
426-
if (sizeof<T>() == 4) {
427-
let bits = reinterpret<u32>(<f32>value) & 0x7fffffff;
428-
exactIntegerRange = bits >= 0x3f800000 && bits <= 0x4b800000;
429-
} else {
430-
let bits = reinterpret<u64>(<f64>value) & 0x7fffffffffffffff;
431-
exactIntegerRange = bits >= 0x3ff0000000000000 && bits <= 0x41cdcd6500000000;
432-
}
433-
}
434-
if (exactIntegerRange) {
435-
let integer = <i32>value;
436-
if (value == <T>integer) {
437-
let negative = integer < 0;
438-
let magnitude = <u32>(negative ? -integer : integer);
439-
let digits = decimalCount32(magnitude);
440-
let result = changetype<String>(__new(<usize>(digits + u32(negative) + 2) << 1, idof<String>()));
441-
let out = changetype<usize>(result);
442-
if (negative) {
443-
store<u16>(out, CharCode.MINUS);
444-
out += 2;
421+
// Avoid the specialized integer writer when optimizing for code size.
422+
if (ASC_SHRINK_LEVEL == 0) {
423+
// Exact integers in this range can be written into their final allocation.
424+
// The f32 limit matters: larger integral f32 values can have a shorter
425+
// decimal representation than their exact integer value.
426+
let exactIntegerRange = false;
427+
if (isFloat<T>()) {
428+
if (sizeof<T>() == 4) {
429+
let bits = reinterpret<u32>(<f32>value) & 0x7fffffff;
430+
exactIntegerRange = bits >= 0x3f800000 && bits <= 0x4b800000;
431+
} else {
432+
let bits = reinterpret<u64>(<f64>value) & 0x7fffffffffffffff;
433+
exactIntegerRange = bits >= 0x3ff0000000000000 && bits <= 0x41cdcd6500000000;
445434
}
446-
if (magnitude < 10) {
447-
store<u16>(out, CharCode._0 + magnitude);
448-
} else if (magnitude < 100) {
449-
store<u32>(out, load<u32>(DIGITS + (<usize>magnitude << 2)));
450-
} else if (magnitude < 10000) {
451-
let high = magnitude / 100;
452-
let low = magnitude - high * 100;
453-
if (magnitude < 1000) {
454-
store<u16>(out, CharCode._0 + high);
455-
store<u32>(out + 2, load<u32>(DIGITS + (<usize>low << 2)));
435+
}
436+
if (exactIntegerRange) {
437+
let integer = <i32>value;
438+
if (value == <T>integer) {
439+
let negative = integer < 0;
440+
let magnitude = <u32>(negative ? -integer : integer);
441+
let digits = decimalCount32(magnitude);
442+
let result = changetype<String>(__new(<usize>(digits + u32(negative) + 2) << 1, idof<String>()));
443+
let out = changetype<usize>(result);
444+
if (negative) {
445+
store<u16>(out, CharCode.MINUS);
446+
out += 2;
447+
}
448+
if (magnitude < 10) {
449+
store<u16>(out, CharCode._0 + magnitude);
450+
} else if (magnitude < 100) {
451+
store<u32>(out, load<u32>(DIGITS + (<usize>magnitude << 2)));
452+
} else if (magnitude < 10000) {
453+
let high = magnitude / 100;
454+
let low = magnitude - high * 100;
455+
if (magnitude < 1000) {
456+
store<u16>(out, CharCode._0 + high);
457+
store<u32>(out + 2, load<u32>(DIGITS + (<usize>low << 2)));
458+
} else {
459+
store<u32>(out, load<u32>(DIGITS + (<usize>high << 2)));
460+
store<u32>(out + 4, load<u32>(DIGITS + (<usize>low << 2)));
461+
}
456462
} else {
457-
store<u32>(out, load<u32>(DIGITS + (<usize>high << 2)));
458-
store<u32>(out + 4, load<u32>(DIGITS + (<usize>low << 2)));
463+
utoa32_dec_core(out, magnitude, digits);
459464
}
460-
} else {
461-
utoa32_dec_core(out, magnitude, digits);
465+
store<u32>(out + (<usize>digits << 1), <u32>CharCode.DOT | (<u32>CharCode._0 << 16));
466+
return result;
462467
}
463-
store<u32>(out + (<usize>digits << 1), <u32>CharCode.DOT | (<u32>CharCode._0 << 16));
464-
return result;
465468
}
466469
}
467470
let len: u32;

‎tests/compiler/issues/2873.debug.wat‎

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
(type $18 (func (param f32) (result i32)))
2121
(type $19 (func (param f64) (result i32)))
2222
(import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32)))
23+
(global $~lib/native/ASC_SHRINK_LEVEL i32 (i32.const 0))
2324
(global $~lib/rt/itcms/total (mut i32) (i32.const 0))
2425
(global $~lib/rt/itcms/threshold (mut i32) (i32.const 0))
2526
(global $~lib/rt/itcms/state (mut i32) (i32.const 0))
@@ -34,7 +35,6 @@
3435
(global $~lib/rt/itcms/fromSpace (mut i32) (i32.const 0))
3536
(global $~lib/rt/tlsf/ROOT (mut i32) (i32.const 0))
3637
(global $~lib/native/ASC_LOW_MEMORY_LIMIT i32 (i32.const 0))
37-
(global $~lib/native/ASC_SHRINK_LEVEL i32 (i32.const 0))
3838
(global $~lib/util/dtoa/FLOAT_SIGNIFICAND_SIZE i32 (i32.const 23))
3939
(global $~lib/util/dtoa/FLOAT_SIGNIFICAND_MASK i32 (i32.const 8388607))
4040
(global $~lib/native/ASC_FEATURE_SIMD i32 (i32.const 0))
@@ -9903,12 +9903,12 @@
99039903
local.get $10
99049904
call $~lib/string/String#get:length
99059905
local.set $sepLen
9906-
i32.const 32
9906+
i32.const 28
99079907
local.get $sepLen
99089908
i32.add
99099909
local.get $lastIndex
99109910
i32.mul
9911-
i32.const 32
9911+
i32.const 28
99129912
i32.add
99139913
local.set $estLen
99149914
global.get $~lib/memory/__stack_pointer
@@ -10123,12 +10123,12 @@
1012310123
local.get $10
1012410124
call $~lib/string/String#get:length
1012510125
local.set $sepLen
10126-
i32.const 32
10126+
i32.const 28
1012710127
local.get $sepLen
1012810128
i32.add
1012910129
local.get $lastIndex
1013010130
i32.mul
10131-
i32.const 32
10131+
i32.const 28
1013210132
i32.add
1013310133
local.set $estLen
1013410134
global.get $~lib/memory/__stack_pointer
@@ -10533,6 +10533,10 @@
1053310533
return
1053410534
end
1053510535
i32.const 0
10536+
i32.const 0
10537+
i32.eq
10538+
drop
10539+
i32.const 0
1053610540
local.set $exactIntegerRange
1053710541
i32.const 1
1053810542
drop
@@ -10826,6 +10830,10 @@
1082610830
return
1082710831
end
1082810832
i32.const 0
10833+
i32.const 0
10834+
i32.eq
10835+
drop
10836+
i32.const 0
1082910837
local.set $exactIntegerRange
1083010838
i32.const 1
1083110839
drop

‎tests/compiler/issues/2873.release.wat‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6238,11 +6238,11 @@
62386238
i32.const 1
62396239
i32.shr_u
62406240
local.tee $4
6241-
i32.const 32
6241+
i32.const 28
62426242
i32.add
62436243
local.get $3
62446244
i32.mul
6245-
i32.const 32
6245+
i32.const 28
62466246
i32.add
62476247
local.tee $5
62486248
i32.const 1
@@ -6432,11 +6432,11 @@
64326432
i32.const 1
64336433
i32.shr_u
64346434
local.tee $4
6435-
i32.const 32
6435+
i32.const 28
64366436
i32.add
64376437
local.get $3
64386438
i32.mul
6439-
i32.const 32
6439+
i32.const 28
64406440
i32.add
64416441
local.tee $5
64426442
i32.const 1

‎tests/compiler/number.debug.wat‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8807,6 +8807,10 @@
88078807
return
88088808
end
88098809
i32.const 0
8810+
i32.const 0
8811+
i32.eq
8812+
drop
8813+
i32.const 0
88108814
local.set $exactIntegerRange
88118815
i32.const 1
88128816
drop

‎tests/compiler/resolve-binary.debug.wat‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10772,6 +10772,10 @@
1077210772
return
1077310773
end
1077410774
i32.const 0
10775+
i32.const 0
10776+
i32.eq
10777+
drop
10778+
i32.const 0
1077510779
local.set $exactIntegerRange
1077610780
i32.const 1
1077710781
drop

‎tests/compiler/resolve-elementaccess.debug.wat‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6590,6 +6590,10 @@
65906590
return
65916591
end
65926592
i32.const 0
6593+
i32.const 0
6594+
i32.eq
6595+
drop
6596+
i32.const 0
65936597
local.set $exactIntegerRange
65946598
i32.const 1
65956599
drop

‎tests/compiler/resolve-ternary.debug.wat‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8080,6 +8080,10 @@
80808080
return
80818081
end
80828082
i32.const 0
8083+
i32.const 0
8084+
i32.eq
8085+
drop
8086+
i32.const 0
80838087
local.set $exactIntegerRange
80848088
i32.const 1
80858089
drop

‎tests/compiler/std/array.debug.wat‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31746,12 +31746,12 @@
3174631746
local.get $10
3174731747
call $~lib/string/String#get:length
3174831748
local.set $sepLen
31749-
i32.const 32
31749+
i32.const 28
3175031750
local.get $sepLen
3175131751
i32.add
3175231752
local.get $lastIndex
3175331753
i32.mul
31754-
i32.const 32
31754+
i32.const 28
3175531755
i32.add
3175631756
local.set $estLen
3175731757
global.get $~lib/memory/__stack_pointer
@@ -49919,6 +49919,10 @@
4991949919
return
4992049920
end
4992149921
i32.const 0
49922+
i32.const 0
49923+
i32.eq
49924+
drop
49925+
i32.const 0
4992249926
local.set $exactIntegerRange
4992349927
i32.const 1
4992449928
drop

‎tests/compiler/std/array.release.wat‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29756,11 +29756,11 @@
2975629756
i32.const 1
2975729757
i32.shr_u
2975829758
local.tee $10
29759-
i32.const 32
29759+
i32.const 28
2976029760
i32.add
2976129761
local.get $9
2976229762
i32.mul
29763-
i32.const 32
29763+
i32.const 28
2976429764
i32.add
2976529765
local.tee $11
2976629766
i32.const 1

0 commit comments

Comments
 (0)