Giter Site home page Giter Site logo

Comments (3)

tmikov avatar tmikov commented on June 20, 2024 3

This is not really about inlining source maps, which are orthogonal to the issue here. Hermes can consume a source map, if it is given one, but this is almost never done by the existing tools, and even if it was, it does not affect the size of the generated bytecode file, it just affects the source locations.

This is something else entirely. Hermes bytecode by default always contains line number information, because that is required to generate proper stack traces.

If we take this source, for example:

function foo() {
    throw Error("Noo!");
}
function bar() {
    foo();
}
bar();

We can compile it to bytecode:

$ hermesc -emit-binary prog.js -out prog.hbc

$ ls -l prog.hbc
-rw-r--r--  1 tmikov  staff  520 Jun  3 11:50 prog.hbc

And then run it:

$ hermes prog.hbc
Uncaught Error: Noo!
    at foo (prog.js:2:16)
    at bar (prog.js:5:8)
    at global (prog.js:7:4)

As you can see, we get a nice stack trace. This is not debug information, this is just expected functionality provided by all JS engines.

Optionally, it is possible to strip this line number information in order to get an even smaller binary. The --output-source-map CLI flag forces Hermes to save the line numbers information in a separate source map file:

$ hermesc -emit-binary prog.js -out prog.hbc --output-source-map

$ ls -l prog.*
-rw-r--r--  1 tmikov  staff  376 Jun  3 11:49 prog.hbc
-rw-r--r--  1 tmikov  staff  173 Jun  3 11:49 prog.hbc.map
-rw-r--r--  1 tmikov  staff   81 Jun  3 11:42 prog.js

Note that prog.hbc decreased from 520 bytes to 376 bytes. However if we run it now, we don't get a nice stack trace:

$ hermes prog.hbc
Uncaught Error: Noo!
    at foo (address at prog.hbc:1:63)
    at bar (address at prog.hbc:1:79)
    at global (address at prog.hbc:1:43)

The "columns" in the above stack trace are actually bytecode offsets. The engine no longer has access to the line numbers, so all it can do is dump numbers.

You can still get to the original lines, but you need to use the generated source map. For example:

$ npm install sourcemap-lookup

$ node ./node_modules/sourcemap-lookup/index.js prog.hbc:1:79

Original Position:
	prog.js, Line 5:7

Code Section:
1 | function foo() {
2 |     throw Error("Noo!");
3 | }
4 | function bar() {
5>|     foo();
           ^
6 | }
7 | bar();
8 |

So, it is up to the integrator whether to remove the line numbers and use the generated source map for symbolication of stack traces.

from expo.

EvanBacon avatar EvanBacon commented on June 20, 2024 1

@iway1 I opened a PR so if you can see how I approached this. Feel free to try in your own project and report back if error handling was still reasonable in production.

from expo.

EvanBacon avatar EvanBacon commented on June 20, 2024

Can confirm this behavior. Will add my findings here.

  1. The terminal logging is not an error, the output is larger.
  2. With --no-bytecode enabled, both JS files are the same size. This means the issue is either with the pipeline or the HBC command. I suspect that the hbc command automatically inlines source maps when source maps aren't explicitly enabled. Skipping source maps is an optimization we added to Expo CLI to reduce unused work, but it might not be worth it.
  3. I checked the HBC command help output to see if it indicated this behavior:
    • EXPO_DEBUG=1 npx expo export prints the hbc command we use.
    • /Users/path/to/expo/node_modules/react-native/sdks/hermesc/osx-bin/hermesc -help doesn't show anything about inlining source maps. Leading me to believe it may still be a hidden default.
  4. When I decompile the hbc for both larger and smaller bundles, they both come out to be the same size! This could be the result of comments (inline source maps) getting removed on decompilation.
  5. There's no plain-text comment at the end of the bin file so it's hard to confirm my theory, perhaps executing in an engine and throwing an error could show more results.
  6. When I disassemble the HBC it appears that the bundles are different. I see a number of lines like [Debug offsets: source_locs=0xb46d5, scope_desc_data=0x0].
  7. The maps don't appear to be added as an inline comment when source maps are disabled, so it's unclear to me where the extra data is coming from tbh.
  8. Tagging @tmikov since he would likely know exactly what's happening here.

It doesn't appear to be related to inline source maps. When source maps aren't enabled, we skip all the extra work and don't pass any source map-related info to the HBC command. If the bundle is larger, it's seemingly coming from the hermesc command and not from Expo CLI.


I'm considering just always enabling source maps and throwing away the results as a hack to work around the size increase. Here's my findings:

  1. Is it much slower to always enable source maps? Let's profile just the hermes command with source maps provided:
    • Source maps enabled: ~1.2s
    • Source maps disabled: ~815ms
    • Baseline bundle (--no-bytecode, no source maps, full cache) is about 1.9s, so this 1.2s is a notable increase overall. We should avoid needlessly generating code and not using it. I'd lean toward figuring out what's going on in the HBC command.
  2. Will wait to hear from the Hermes team before continuing. It's likely meaningful data that's being added to the bundle and I'm not super interested in solving bugs related to HBC being broken without the upstream-intended debug data.

Was pondering the [Debug offsets... lines, and figured I'd check the HBC output again. I was a little confused by this part:

Choose optimization level:
    -O0                              - No optimizations
    -Og                              - Optimizations suitable for debugging
    -O                               - Expensive optimizations

Because we use -O, and not -Og, I figured debugging wasn't enabled. This was not the case. Adding -g0 made the bundles just as small as when source maps are enabled!

So it seems like when source maps are skipped, debug info is injected by Hermes to compensate. I'd still like to understand how these debug traces are used before choosing to enable/disable them in Expo CLI (I'd like to avoid adding extra options to the CLI).

from expo.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.