@@ -71,6 +71,7 @@ use super::bindings::watch_promise;
7171use super :: exception_state:: ExceptionState ;
7272use super :: jsrealm:: JsRealmInner ;
7373use super :: op_driver:: OpDriver ;
74+ use super :: ops_exports;
7475use super :: setup;
7576use super :: snapshot;
7677use super :: stats:: RuntimeActivityStatsFactory ;
@@ -598,6 +599,28 @@ pub struct RuntimeOptions {
598599 /// Should op registration be skipped?
599600 pub skip_op_registration : bool ,
600601
602+ /// Make the `ext:core/ops` synthetic module export **every** registered op,
603+ /// the way it did before the export list was shrunk to the set of names that
604+ /// extension sources actually import.
605+ ///
606+ /// By default deno_core scans the extension sources it is given — including
607+ /// `lazy_loaded_*` and residual sources — for
608+ /// `import { … } from "ext:core/ops"` clauses and exports only those names.
609+ /// Anything the scan cannot prove to be a plain named import (a namespace
610+ /// import, an `export * from`, a dynamic `import("ext:core/ops")`) already
611+ /// switches that runtime back to exporting everything automatically, so this
612+ /// flag is only needed when the importing module is **not** among the sources
613+ /// deno_core sees — e.g. a module served by the embedder's own
614+ /// `ModuleLoader` that imports ops by name.
615+ ///
616+ /// Turning this on restores the pre-shrink blob cost: one export cell, and
617+ /// hence one materialized op function, per registered op (~270 bytes of
618+ /// snapshot each). Prefer letting the scanner see the source.
619+ ///
620+ /// Only meaningful when the runtime builds the module itself, i.e. when there
621+ /// is no startup snapshot or the snapshot is being created.
622+ pub export_all_ops_from_virtual_module : bool ,
623+
601624 /// Isolate creation parameters.
602625 pub create_params : Option < v8:: CreateParams > ,
603626
@@ -849,6 +872,69 @@ impl JsRuntime {
849872 ) ?;
850873 startup_phase_end ( _phase, "into_sources_and_source_maps" ) ;
851874
875+ // Work out which names `ext:core/ops` has to export, before the sources are
876+ // externalized. Only the runtime that *builds* the module pays for this;
877+ // when the module comes out of a snapshot it is already shaped.
878+ let ops_export_filter = if init_mode == InitMode :: New {
879+ let _phase = startup_phase_begin ( ) ;
880+ let mut scan = ops_exports:: OpsImportScan :: default ( ) ;
881+ if options. export_all_ops_from_virtual_module {
882+ scan. force_export_all ( ) ;
883+ }
884+ for source in sources
885+ . js
886+ . iter ( )
887+ . chain ( sources. esm . iter ( ) )
888+ . chain ( sources. lazy_esm . iter ( ) )
889+ . chain ( sources. lazy_js . iter ( ) )
890+ {
891+ scan. add_source ( source. code . as_str ( ) ) ;
892+ }
893+ // Residual `lazy_loaded_*` sources are instantiated *after* the snapshot
894+ // is deserialized, but they are known here, so their imports go into the
895+ // same union rather than exploding at load time.
896+ for ( _, source) in options
897+ . residual_lazy_js_sources
898+ . iter ( )
899+ . chain ( options. residual_lazy_esm_sources . iter ( ) )
900+ {
901+ scan. add_source ( source) ;
902+ }
903+ // deno_core's own built-ins are executed after the synthetic module is
904+ // created, so they have to be in the union too.
905+ for source in CONTEXT_SETUP_SOURCES . iter ( ) . chain ( BUILTIN_SOURCES . iter ( ) ) {
906+ scan. add_source ( source. source . as_str ( ) ) ;
907+ }
908+ for source in & BUILTIN_ES_MODULES {
909+ scan. add_source ( source. load ( ) ?. as_str ( ) ) ;
910+ }
911+ let filter = scan. finish ( ) ;
912+ startup_phase_end ( _phase, "scan_ops_module_imports" ) ;
913+ if startup_phases_enabled ( ) {
914+ #[ allow( clippy:: print_stderr, reason = "diagnostic" ) ]
915+ {
916+ // The fail-safe direction of this analysis is "export everything",
917+ // which costs the whole win silently. Make it visible next to the
918+ // other startup diagnostics.
919+ match & filter {
920+ ops_exports:: OpsExportFilter :: All => eprintln ! (
921+ "[startup] {:>32} ALL (export-all requested, or a source uses \
922+ ext:core/ops in a way the scanner cannot analyse)",
923+ "ext:core/ops exports"
924+ ) ,
925+ ops_exports:: OpsExportFilter :: Only ( names) => eprintln ! (
926+ "[startup] {:>32} {} names" ,
927+ "ext:core/ops exports" ,
928+ names. len( )
929+ ) ,
930+ }
931+ }
932+ }
933+ filter
934+ } else {
935+ ops_exports:: OpsExportFilter :: All
936+ } ;
937+
852938 for loaded_source in sources
853939 . js
854940 . iter ( )
@@ -1257,8 +1343,11 @@ impl JsRuntime {
12571343 // ) {
12581344 if init_mode == InitMode :: New {
12591345 let _phase = startup_phase_begin ( ) ;
1260- js_runtime
1261- . execute_virtual_ops_module ( context_global, module_map. clone ( ) ) ;
1346+ js_runtime. execute_virtual_ops_module (
1347+ context_global,
1348+ module_map. clone ( ) ,
1349+ & ops_export_filter,
1350+ ) ;
12621351 startup_phase_end ( _phase, "execute_virtual_ops_module" ) ;
12631352 }
12641353
@@ -1471,6 +1560,7 @@ impl JsRuntime {
14711560 & mut self ,
14721561 context_global : & v8:: Global < v8:: Context > ,
14731562 module_map : Rc < ModuleMap > ,
1563+ filter : & ops_exports:: OpsExportFilter ,
14741564 ) {
14751565 scope ! ( scope, self ) ;
14761566 let context_local = v8:: Local :: new ( scope, context_global) ;
@@ -1482,6 +1572,7 @@ impl JsRuntime {
14821572 context_state. methods_ctx_offset ,
14831573 scope,
14841574 global,
1575+ filter,
14851576 ) ;
14861577 let mod_id = module_map. new_synthetic_module (
14871578 scope,
0 commit comments