Skip to content

Commit b490c44

Browse files
authored
Remove support for TEXTDECODER=0 (#24700)
Setting this to zero doesn't provide much value to anyone since the conditional usage is relatively cheap (compared to the fallback) and the API available in all browsers these days. We still need the fallback for things like audio worklets and JS shell environments.
1 parent f772205 commit b490c44

File tree

9 files changed

+29
-64
lines changed

9 files changed

+29
-64
lines changed

ChangeLog.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ See docs/process.md for more on how version tagging works.
2020

2121
4.0.12 (in development)
2222
-----------------------
23+
- Support for `-sTEXT_DECODER=0` was removed, due to widespread support for
24+
`TextDecoder`. The remaining valid values for this setting are `=1`
25+
(conditional use of `TextDecoder` with fallback) and `=2` (unconditional use
26+
of `TextDecoder`). (#24700)
2327

2428
4.0.11 - 07/14/25
2529
-----------------

site/source/docs/tools_reference/settings_reference.rst

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2692,10 +2692,11 @@ Default value: 0
26922692
TEXTDECODER
26932693
===========
26942694

2695-
If enabled, use the JavaScript TextDecoder API for string marshalling.
2696-
Enabled by default, set this to 0 to disable.
2695+
The default value or 1 means the generated code will use TextDecoder if
2696+
available and fall back to custom decoder code when not available.
26972697
If set to 2, we assume TextDecoder is present and usable, and do not emit
2698-
any JS code to fall back if it is missing.
2698+
any JS code to fall back if it is missing. Setting this zero to avoid even
2699+
conditional usage of TextDecoder is no longer supported.
26992700
Note: In -Oz builds, the default value of TEXTDECODER is set to 2, to save on
27002701
code size (except when AUDIO_WORKLET is specified, or when `shell` is part
27012702
of ENVIRONMENT since TextDecoder is not available in those environments).

src/lib/libstrings.js

Lines changed: 7 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,15 @@
44
* SPDX-License-Identifier: MIT
55
*/
66

7+
#if TEXTDECODER != 1 && TEXTDECODER != 2
8+
#error "TEXTDECODER must be either 1 or 2"
9+
#endif
10+
711
addToLibrary({
812
// TextDecoder constructor defaults to UTF-8
913
#if TEXTDECODER == 2
1014
$UTF8Decoder: "new TextDecoder()",
11-
#elif TEXTDECODER == 1
15+
#else
1216
$UTF8Decoder: "typeof TextDecoder != 'undefined' ? new TextDecoder() : undefined",
1317
#endif
1418

@@ -35,49 +39,28 @@ addToLibrary({
3539
* @param {boolean=} ignoreNul - If true, the function will not stop on a NUL character.
3640
* @return {string}
3741
*/`,
38-
#if TEXTDECODER
3942
$UTF8ArrayToString__deps: ['$UTF8Decoder', '$findStringEnd'],
40-
#endif
4143
$UTF8ArrayToString: (heapOrArray, idx = 0, maxBytesToRead, ignoreNul) => {
4244
#if CAN_ADDRESS_2GB
4345
idx >>>= 0;
4446
#endif
4547

46-
#if TEXTDECODER
4748
var endPtr = findStringEnd(heapOrArray, idx, maxBytesToRead, ignoreNul);
48-
#else
49-
var endIdx = idx + maxBytesToRead;
50-
#endif
5149

5250
#if TEXTDECODER == 2
5351
return UTF8Decoder.decode(heapOrArray.buffer ? {{{ getUnsharedTextDecoderView('heapOrArray', 'idx', 'endPtr') }}} : new Uint8Array(heapOrArray.slice(idx, endPtr)));
5452
#else // TEXTDECODER == 2
55-
#if TEXTDECODER
5653
// When using conditional TextDecoder, skip it for short strings as the overhead of the native call is not worth it.
5754
if (endPtr - idx > 16 && heapOrArray.buffer && UTF8Decoder) {
5855
return UTF8Decoder.decode({{{ getUnsharedTextDecoderView('heapOrArray', 'idx', 'endPtr') }}});
5956
}
60-
#endif // TEXTDECODER
6157
var str = '';
62-
#if TEXTDECODER
63-
// If building with TextDecoder, we have already computed the string length
64-
// above, so test loop end condition against that
6558
while (idx < endPtr) {
66-
#else
67-
while (!(idx >= endIdx)) {
68-
#endif
6959
// For UTF8 byte structure, see:
7060
// http://en.wikipedia.org/wiki/UTF-8#Description
7161
// https://www.ietf.org/rfc/rfc2279.txt
7262
// https://tools.ietf.org/html/rfc3629
7363
var u0 = heapOrArray[idx++];
74-
#if !TEXTDECODER
75-
// If not building with TextDecoder enabled, we don't know the string
76-
// length, so scan for \0 byte.
77-
// If building with TextDecoder, we know exactly at what byte index the
78-
// string ends, so checking for nulls here would be redundant.
79-
if (!u0 && !ignoreNul) return str;
80-
#endif
8164
if (!(u0 & 0x80)) { str += String.fromCharCode(u0); continue; }
8265
var u1 = heapOrArray[idx++] & 63;
8366
if ((u0 & 0xE0) == 0xC0) { str += String.fromCharCode(((u0 & 31) << 6) | u1); continue; }
@@ -310,32 +293,26 @@ addToLibrary({
310293

311294
#if TEXTDECODER == 2
312295
$UTF16Decoder: "new TextDecoder('utf-16le');",
313-
#elif TEXTDECODER == 1
296+
#else
314297
$UTF16Decoder: "typeof TextDecoder != 'undefined' ? new TextDecoder('utf-16le') : undefined;",
315298
#endif
316299

317300
// Given a pointer 'ptr' to a null-terminated UTF16LE-encoded string in the
318301
// emscripten HEAP, returns a copy of that string as a Javascript String
319302
// object.
320-
#if TEXTDECODER
321303
$UTF16ToString__deps: ['$UTF16Decoder', '$findStringEnd'],
322-
#endif
323304
$UTF16ToString: (ptr, maxBytesToRead, ignoreNul) => {
324305
#if ASSERTIONS
325306
assert(ptr % 2 == 0, 'Pointer passed to UTF16ToString must be aligned to two bytes!');
326307
#endif
327308
var idx = {{{ getHeapOffset('ptr', 'u16') }}};
328-
#if TEXTDECODER
329309
var endIdx = findStringEnd(HEAPU16, idx, maxBytesToRead / 2, ignoreNul);
330310

331311
#if TEXTDECODER != 2
332312
// When using conditional TextDecoder, skip it for short strings as the overhead of the native call is not worth it.
333313
if (endIdx - idx > 16 && UTF16Decoder)
334314
#endif // TEXTDECODER != 2
335315
return UTF16Decoder.decode({{{ getUnsharedTextDecoderView('HEAPU16', 'idx', 'endIdx') }}});
336-
#else
337-
var maxIdx = idx + maxBytesToRead / 2;
338-
#endif // TEXTDECODER
339316

340317
#if TEXTDECODER != 2
341318
// Fallback: decode without UTF16Decoder
@@ -344,25 +321,8 @@ addToLibrary({
344321
// If maxBytesToRead is not passed explicitly, it will be undefined, and the
345322
// for-loop's condition will always evaluate to true. The loop is then
346323
// terminated on the first null char.
347-
for (
348-
var i = idx;
349-
#if TEXTDECODER
350-
// If building with TextDecoder, we have already computed the string length
351-
// above, so test loop end condition against that
352-
i < endIdx;
353-
#else
354-
!(i >= maxIdx);
355-
#endif
356-
++i
357-
) {
324+
for (var i = idx; i < endIdx; ++i) {
358325
var codeUnit = HEAPU16[i];
359-
#if !TEXTDECODER
360-
// If not building with TextDecoder enabled, we don't know the string
361-
// length, so scan for \0 character.
362-
// If building with TextDecoder, we know exactly at what index the
363-
// string ends, so checking for nulls here would be redundant.
364-
if (!codeUnit && !ignoreNul) break;
365-
#endif
366326
// fromCharCode constructs a character from a UTF-16 code unit, so we can
367327
// pass the UTF16 string right through.
368328
str += String.fromCharCode(codeUnit);

src/settings.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1769,10 +1769,11 @@ var PTHREADS_DEBUG = false;
17691769
// [link]
17701770
var EVAL_CTORS = 0;
17711771

1772-
// If enabled, use the JavaScript TextDecoder API for string marshalling.
1773-
// Enabled by default, set this to 0 to disable.
1772+
// The default value or 1 means the generated code will use TextDecoder if
1773+
// available and fall back to custom decoder code when not available.
17741774
// If set to 2, we assume TextDecoder is present and usable, and do not emit
1775-
// any JS code to fall back if it is missing.
1775+
// any JS code to fall back if it is missing. Setting this zero to avoid even
1776+
// conditional usage of TextDecoder is no longer supported.
17761777
// Note: In -Oz builds, the default value of TEXTDECODER is set to 2, to save on
17771778
// code size (except when AUDIO_WORKLET is specified, or when `shell` is part
17781779
// of ENVIRONMENT since TextDecoder is not available in those environments).
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
54108
1+
53970
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
26909
1+
26771
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
52158
1+
52020

test/test_core.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ def decorated(self, textdecoder, *args, **kwargs):
391391
self.set_setting('TEXTDECODER', textdecoder)
392392
f(self, *args, **kwargs)
393393

394-
parameterize(decorated, {'': (0,), 'textdecoder': (2,)})
394+
parameterize(decorated, {'': (1,), 'force_textdecoder': (2,)})
395395

396396
return decorated
397397

test/test_other.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16364,10 +16364,6 @@ def test_install(self):
1636416364
def test_TextDecoder(self, args1, args2):
1636516365
self.cflags += args1 + args2
1636616366

16367-
self.do_runf('hello_world.c', cflags=['-sTEXTDECODER=0'])
16368-
just_fallback = os.path.getsize('hello_world.js')
16369-
print('just_fallback:\t%s' % just_fallback)
16370-
1637116367
self.do_runf('hello_world.c')
1637216368
td_with_fallback = os.path.getsize('hello_world.js')
1637316369
print('td_with_fallback:\t%s' % td_with_fallback)
@@ -16378,7 +16374,10 @@ def test_TextDecoder(self, args1, args2):
1637816374

1637916375
# td_with_fallback should always be largest of all three in terms of code side
1638016376
self.assertGreater(td_with_fallback, td_without_fallback)
16381-
self.assertGreater(td_with_fallback, just_fallback)
1638216377

16383-
# the fallback is also expected to be larger in code size than using td
16384-
self.assertGreater(just_fallback, td_without_fallback)
16378+
def test_TextDecoder_invalid(self):
16379+
err = self.expect_fail([EMCC, test_file('hello_world.c'), '-sTEXTDECODER=0'])
16380+
self.assertContained('#error "TEXTDECODER must be either 1 or 2"', err)
16381+
16382+
err = self.expect_fail([EMCC, test_file('hello_world.c'), '-sTEXTDECODER=3'])
16383+
self.assertContained('#error "TEXTDECODER must be either 1 or 2"', err)

0 commit comments

Comments
 (0)