Skip to content

Commit d7c1c60

Browse files
MaxGraeydcodeIO
authored andcommitted
Implement itoa32/64 for base 10 (AssemblyScript#151)
1 parent 5ce57a6 commit d7c1c60

13 files changed

+4954
-1414
lines changed

std/assembly/internal/itoa.ts

+256
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
2+
import {
3+
CharCode,
4+
allocate,
5+
HEADER_SIZE as STRING_HEADER_SIZE
6+
} from "./string";
7+
8+
import { loadUnsafe } from "./arraybuffer";
9+
10+
@inline
11+
function getPowers10Table(): u32[] {
12+
return <u32[]>[
13+
1,
14+
10,
15+
100,
16+
1000,
17+
10000,
18+
100000,
19+
1000000,
20+
10000000,
21+
100000000,
22+
1000000000
23+
];
24+
}
25+
26+
/*
27+
Lookup table for pairwise char codes in range [0-99]
28+
29+
"00", "01", "02", "03", "04", "05", "06", "07", "08", "09",
30+
"10", "11", "12", "13", "14", "15", "16", "17", "18", "19",
31+
"20", "21", "22", "23", "24", "25", "26", "27", "28", "29",
32+
"30", "31", "32", "33", "34", "35", "36", "37", "38", "39",
33+
"40", "41", "42", "43", "44", "45", "46", "47", "48", "49",
34+
"50", "51", "52", "53", "54", "55", "56", "57", "58", "59",
35+
"60", "61", "62", "63", "64", "65", "66", "67", "68", "69",
36+
"70", "71", "72", "73", "74", "75", "76", "77", "78", "79",
37+
"80", "81", "82", "83", "84", "85", "86", "87", "88", "89",
38+
"90", "91", "92", "93", "94", "95", "96", "97", "98", "99"
39+
*/
40+
@inline
41+
function getDigitsTable(): u32[] {
42+
return <u32[]>[
43+
0x00300030, 0x00310030, 0x00320030, 0x00330030, 0x00340030,
44+
0x00350030, 0x00360030, 0x00370030, 0x00380030, 0x00390030,
45+
0x00300031, 0x00310031, 0x00320031, 0x00330031, 0x00340031,
46+
0x00350031, 0x00360031, 0x00370031, 0x00380031, 0x00390031,
47+
0x00300032, 0x00310032, 0x00320032, 0x00330032, 0x00340032,
48+
0x00350032, 0x00360032, 0x00370032, 0x00380032, 0x00390032,
49+
0x00300033, 0x00310033, 0x00320033, 0x00330033, 0x00340033,
50+
0x00350033, 0x00360033, 0x00370033, 0x00380033, 0x00390033,
51+
0x00300034, 0x00310034, 0x00320034, 0x00330034, 0x00340034,
52+
0x00350034, 0x00360034, 0x00370034, 0x00380034, 0x00390034,
53+
0x00300035, 0x00310035, 0x00320035, 0x00330035, 0x00340035,
54+
0x00350035, 0x00360035, 0x00370035, 0x00380035, 0x00390035,
55+
0x00300036, 0x00310036, 0x00320036, 0x00330036, 0x00340036,
56+
0x00350036, 0x00360036, 0x00370036, 0x00380036, 0x00390036,
57+
0x00300037, 0x00310037, 0x00320037, 0x00330037, 0x00340037,
58+
0x00350037, 0x00360037, 0x00370037, 0x00380037, 0x00390037,
59+
0x00300038, 0x00310038, 0x00320038, 0x00330038, 0x00340038,
60+
0x00350038, 0x00360038, 0x00370038, 0x00380038, 0x00390038,
61+
0x00300039, 0x00310039, 0x00320039, 0x00330039, 0x00340039,
62+
0x00350039, 0x00360039, 0x00370039, 0x00380039, 0x00390039
63+
];
64+
}
65+
66+
// Count number of decimals in value
67+
function decimalCount<T>(value: T): i32 {
68+
// make value abs
69+
var sign = value >> (8 * sizeof<T>() - 1);
70+
var v = (value ^ sign) - sign;
71+
var l = 8 * sizeof<T>() - <i32>clz<T>(v | 10); // log2
72+
var t = l * 1233 >>> 12; // log10
73+
74+
var lutbuf = changetype<ArrayBuffer>(getPowers10Table().buffer_);
75+
if (sizeof<T>() <= 4) {
76+
let power = loadUnsafe<u32,T>(lutbuf, t);
77+
t -= <i32>(v < power);
78+
} else { // sizeof<T>() == 8
79+
let le10 = t <= 10;
80+
let offset = select<i32>(0, 10, le10); // offset = t <= 10 ? 0 : 10
81+
let factor = select< T >(1, 10000000000, le10); // factor = t <= 10 ? 1 : 10 ^ 10
82+
let power = loadUnsafe<u32,T>(lutbuf, t - offset);
83+
t -= <i32>(v < factor * power);
84+
}
85+
return t + 1;
86+
}
87+
88+
function utoa32_lut(buffer: usize, num: u32, offset: u32): void {
89+
var r: u32, t: u32, d1: u32, d2: u32;
90+
var lutbuf = changetype<ArrayBuffer>(getDigitsTable().buffer_);
91+
92+
while (num >= 10000) {
93+
// in most VMs i32/u32 div and modulo by constant can be shared and simplificate
94+
t = num / 10000;
95+
r = num % 10000;
96+
num = t;
97+
98+
d1 = r / 100;
99+
d2 = r % 100;
100+
101+
let digits1 = loadUnsafe<u32,u64>(lutbuf, d1);
102+
let digits2 = loadUnsafe<u32,u64>(lutbuf, d2);
103+
104+
offset -= 4;
105+
store<u64>(buffer + (offset << 1), digits1 | (digits2 << 32), STRING_HEADER_SIZE);
106+
}
107+
108+
if (num >= 100) {
109+
t = num / 100;
110+
d1 = num % 100;
111+
num = t;
112+
offset -= 2;
113+
let digits = loadUnsafe<u32,u32>(lutbuf, d1);
114+
store<u32>(buffer + (offset << 1), digits, STRING_HEADER_SIZE);
115+
}
116+
117+
if (num >= 10) {
118+
offset -= 2;
119+
let digits = loadUnsafe<u32,u32>(lutbuf, num);
120+
store<u32>(buffer + (offset << 1), digits, STRING_HEADER_SIZE);
121+
} else {
122+
offset -= 1;
123+
let digit = CharCode._0 + num;
124+
store<u16>(buffer + (offset << 1), digit, STRING_HEADER_SIZE);
125+
}
126+
}
127+
128+
function utoa64_lut(buffer: usize, num: u64, offset: u32): void {
129+
var t: u64, r: u32, b: u32, c: u32;
130+
var b1: u32, b2: u32, c1: u32, c2: u32;
131+
132+
var lutbuf = changetype<ArrayBuffer>(getDigitsTable().buffer_);
133+
134+
while (num >= 100000000) {
135+
t = num / 100000000;
136+
r = <u32>(num - t * 100000000);
137+
num = t;
138+
139+
b = r / 10000;
140+
c = r % 10000;
141+
142+
b1 = b / 100;
143+
b2 = b % 100;
144+
c1 = c / 100;
145+
c2 = c % 100;
146+
147+
let digits1 = loadUnsafe<u32,u64>(lutbuf, c1);
148+
let digits2 = loadUnsafe<u32,u64>(lutbuf, c2);
149+
150+
offset -= 4;
151+
store<u64>(buffer + (offset << 1), digits1 | (digits2 << 32), STRING_HEADER_SIZE);
152+
153+
digits1 = loadUnsafe<u32,u64>(lutbuf, b1);
154+
digits2 = loadUnsafe<u32,u64>(lutbuf, b2);
155+
156+
offset -= 4;
157+
store<u64>(buffer + (offset << 1), digits1 | (digits2 << 32), STRING_HEADER_SIZE);
158+
}
159+
160+
r = <u32>num;
161+
if (r) utoa32_lut(buffer, r, offset);
162+
}
163+
164+
function utoa_simple<T>(buffer: usize, num: T, offset: u32): void {
165+
var t: T, r: u32;
166+
do {
167+
t = num / 10;
168+
r = <u32>(num % 10);
169+
num = t;
170+
offset -= 1;
171+
store<u16>(buffer + (offset << 1), CharCode._0 + r, STRING_HEADER_SIZE);
172+
} while (num);
173+
}
174+
175+
@inline
176+
export function utoa32_core(buffer: usize, num: u32, offset: u32): void {
177+
if (ASC_SHRINK_LEVEL >= 1) {
178+
utoa_simple(buffer, num, offset);
179+
} else {
180+
utoa32_lut(buffer, num, offset);
181+
}
182+
}
183+
184+
@inline
185+
export function utoa64_core(buffer: usize, num: u64, offset: u32): void {
186+
if (ASC_SHRINK_LEVEL >= 1) {
187+
utoa_simple(buffer, num, offset);
188+
} else {
189+
utoa64_lut(buffer, num, offset);
190+
}
191+
}
192+
193+
export function utoa32(value: u32): string {
194+
if (!value) return "0";
195+
196+
var decimals = decimalCount<u32>(value);
197+
var buffer = allocate(decimals);
198+
199+
utoa32_core(changetype<usize>(buffer), value, decimals);
200+
return changetype<string>(buffer);
201+
}
202+
203+
export function itoa32(value: i32): string {
204+
if (!value) return "0";
205+
206+
var isneg = value < 0;
207+
if (isneg) value = -value;
208+
209+
var decimals = decimalCount<u32>(value) + <i32>isneg;
210+
var buffer = allocate(decimals);
211+
212+
utoa32_core(changetype<usize>(buffer), value, decimals);
213+
if (isneg) store<u16>(changetype<usize>(buffer), CharCode.MINUS, STRING_HEADER_SIZE);
214+
215+
return changetype<string>(buffer);
216+
}
217+
218+
export function utoa64(value: u64): string {
219+
if (!value) return "0";
220+
221+
var buffer: String;
222+
if (value <= u32.MAX_VALUE) {
223+
let value32 = <u32>value;
224+
let decimals = decimalCount<u32>(value32);
225+
buffer = allocate(decimals);
226+
utoa32_core(changetype<usize>(buffer), value32, decimals);
227+
} else {
228+
let decimals = decimalCount<u64>(value);
229+
buffer = allocate(decimals);
230+
utoa64_core(changetype<usize>(buffer), value, decimals);
231+
}
232+
233+
return changetype<string>(buffer);
234+
}
235+
236+
export function itoa64(value: i64): string {
237+
if (!value) return "0";
238+
239+
var isneg = value < 0;
240+
if (isneg) value = -value;
241+
242+
var buffer: String;
243+
if (<u64>value <= <u64>u32.MAX_VALUE) {
244+
let value32 = <u32>value;
245+
let decimals = decimalCount<u32>(value32) + <i32>isneg;
246+
buffer = allocate(decimals);
247+
utoa32_core(changetype<usize>(buffer), value32, decimals);
248+
} else {
249+
let decimals = decimalCount<u64>(value) + <i32>isneg;
250+
buffer = allocate(decimals);
251+
utoa64_core(changetype<usize>(buffer), value, decimals);
252+
}
253+
if (isneg) store<u16>(changetype<usize>(buffer), CharCode.MINUS, STRING_HEADER_SIZE);
254+
255+
return changetype<string>(buffer);
256+
}

std/assembly/internal/typedarray.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export abstract class TypedArray<T,V> {
2424
this.byteLength = byteLength;
2525
}
2626

27+
@inline
2728
get length(): i32 {
2829
return (this.byteLength - this.byteOffset) >> alignof<T>();
2930
}
@@ -36,7 +37,7 @@ export abstract class TypedArray<T,V> {
3637
return loadUnsafeWithOffset<T,T>(this.buffer, index, byteOffset);
3738
}
3839

39-
@operator("{}")
40+
@inline @operator("{}")
4041
protected __unchecked_get(index: i32): T {
4142
return loadUnsafeWithOffset<T,T>(this.buffer, index, this.byteOffset);
4243
}
@@ -49,7 +50,7 @@ export abstract class TypedArray<T,V> {
4950
storeUnsafeWithOffset<T,V>(this.buffer, index, value, byteOffset);
5051
}
5152

52-
@operator("{}=")
53+
@inline @operator("{}=")
5354
protected __unchecked_set(index: i32, value: V): void {
5455
storeUnsafeWithOffset<T,V>(this.buffer, index, value, this.byteOffset);
5556
}

std/assembly/string.ts

+10
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,16 @@ export class String {
1414

1515
readonly length: i32; // capped to [0, MAX_LENGTH]
1616

17+
static fromCharCode(code: i32): String {
18+
var out = allocate(1);
19+
store<u16>(
20+
changetype<usize>(out),
21+
<u16>code,
22+
HEADER_SIZE
23+
);
24+
return out;
25+
}
26+
1727
@operator("[]")
1828
charAt(pos: i32): String {
1929
assert(this !== null);

std/assembly/typedarray.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export class Uint8ClampedArray extends TypedArray<u8,u32> {
2626
super.__set(index, max(min(value, 255), 0));
2727
}
2828

29-
@operator("{}=")
29+
@inline @operator("{}=")
3030
protected __unchecked_set(index: i32, value: i32): void {
3131
super.__unchecked_set(index, max(min(value, 255), 0));
3232
}

tests/compiler/std/array-access.optimized.wat

+1-1
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@
132132
(call $~lib/env/abort
133133
(i32.const 0)
134134
(i32.const 12)
135-
(i32.const 255)
135+
(i32.const 265)
136136
(i32.const 4)
137137
)
138138
(unreachable)

tests/compiler/std/array-access.untouched.wat

+1-1
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@
213213
(call $~lib/env/abort
214214
(i32.const 0)
215215
(i32.const 12)
216-
(i32.const 255)
216+
(i32.const 265)
217217
(i32.const 4)
218218
)
219219
(unreachable)

tests/compiler/std/array.optimized.wat

+2-2
Original file line numberDiff line numberDiff line change
@@ -5855,7 +5855,7 @@
58555855
(call $~lib/env/abort
58565856
(i32.const 0)
58575857
(i32.const 696)
5858-
(i32.const 19)
5858+
(i32.const 29)
58595859
(i32.const 4)
58605860
)
58615861
(unreachable)
@@ -5903,7 +5903,7 @@
59035903
(call $~lib/env/abort
59045904
(i32.const 0)
59055905
(i32.const 696)
5906-
(i32.const 75)
5906+
(i32.const 85)
59075907
(i32.const 4)
59085908
)
59095909
(unreachable)

tests/compiler/std/array.untouched.wat

+2-2
Original file line numberDiff line numberDiff line change
@@ -8647,7 +8647,7 @@
86478647
(call $~lib/env/abort
86488648
(i32.const 0)
86498649
(i32.const 696)
8650-
(i32.const 19)
8650+
(i32.const 29)
86518651
(i32.const 4)
86528652
)
86538653
(unreachable)
@@ -8699,7 +8699,7 @@
86998699
(call $~lib/env/abort
87008700
(i32.const 0)
87018701
(i32.const 696)
8702-
(i32.const 75)
8702+
(i32.const 85)
87038703
(i32.const 4)
87048704
)
87058705
(unreachable)

0 commit comments

Comments
 (0)