|
20 | 20 | #include "src/objects/allocation-site-inl.h" |
21 | 21 | #include "src/objects/objects-inl.h" |
22 | 22 | #include "src/objects/shared-function-info.h" |
| 23 | +#include "test/unittests/heap/heap-utils.h" // For ManualGCScope. |
23 | 24 | #include "test/unittests/test-utils.h" |
24 | 25 | #include "testing/gtest/include/gtest/gtest.h" |
25 | 26 |
|
@@ -928,5 +929,211 @@ TEST_F(CompilerTest, ProfilerEnabledDuringBackgroundCompile) { |
928 | 929 | cpu_profiler->StopProfiling(profile); |
929 | 930 | } |
930 | 931 |
|
| 932 | +using BackgroundMergeTest = TestWithNativeContext; |
| 933 | + |
| 934 | +// Tests that a GC during merge doesn't break the merge. |
| 935 | +TEST_F(BackgroundMergeTest, GCDuringMerge) { |
| 936 | + v8_flags.verify_code_merge = true; |
| 937 | + |
| 938 | + HandleScope scope(isolate()); |
| 939 | + const char* source = |
| 940 | + // f is compiled eagerly thanks to the IIFE hack. |
| 941 | + "f = (function f(x) {" |
| 942 | + " let b = x;" |
| 943 | + // f is compiled eagerly, so g's SFI exists. But, it is not compiled. |
| 944 | + " return function g() {" |
| 945 | + // g isn't compiled, so h's SFI does not exist. |
| 946 | + " return function h() {" |
| 947 | + " return b;" |
| 948 | + " }" |
| 949 | + " }" |
| 950 | + "})"; |
| 951 | + Handle<String> source_string = |
| 952 | + isolate() |
| 953 | + ->factory() |
| 954 | + ->NewStringFromUtf8(base::CStrVector(source)) |
| 955 | + .ToHandleChecked(); |
| 956 | + |
| 957 | + const int kTopLevelId = 0; |
| 958 | + const int kFId = 1; |
| 959 | + const int kGId = 2; |
| 960 | + const int kHId = 3; |
| 961 | + |
| 962 | + // Compile the script once to warm up the compilation cache. |
| 963 | + Handle<JSFunction> old_g; |
| 964 | + IsCompiledScope old_g_bytecode_keepalive; |
| 965 | + { |
| 966 | + // Compile in a new handle scope, so that the script can die while the inner |
| 967 | + // functions stay alive. |
| 968 | + HandleScope scope(isolate()); |
| 969 | + ScriptCompiler::CompilationDetails compilation_details; |
| 970 | + Handle<SharedFunctionInfo> top_level_sfi = |
| 971 | + Compiler::GetSharedFunctionInfoForScript( |
| 972 | + isolate(), source_string, ScriptDetails(), |
| 973 | + v8::ScriptCompiler::kNoCompileOptions, |
| 974 | + ScriptCompiler::kNoCacheNoReason, NOT_NATIVES_CODE, |
| 975 | + &compilation_details) |
| 976 | + .ToHandleChecked(); |
| 977 | + |
| 978 | + { |
| 979 | + Tagged<Script> script = Cast<Script>(top_level_sfi->script()); |
| 980 | + CHECK(!script->infos()->get(kTopLevelId).IsCleared()); |
| 981 | + CHECK(!script->infos()->get(kFId).IsCleared()); |
| 982 | + CHECK(!script->infos()->get(kGId).IsCleared()); |
| 983 | + // h in the script infos list was never initialized by the compilation, so |
| 984 | + // it's the default value for a WeakFixedArray, which is `undefined`. |
| 985 | + CHECK(Is<Undefined>(script->infos()->get(kHId))); |
| 986 | + } |
| 987 | + |
| 988 | + Handle<JSFunction> top_level = |
| 989 | + Factory::JSFunctionBuilder{isolate(), top_level_sfi, |
| 990 | + isolate()->native_context()} |
| 991 | + .Build(); |
| 992 | + |
| 993 | + Handle<JSObject> global(isolate()->context()->global_object(), isolate()); |
| 994 | + Execution::CallScript(isolate(), top_level, global, |
| 995 | + isolate()->factory()->empty_fixed_array()) |
| 996 | + .Check(); |
| 997 | + |
| 998 | + Handle<JSFunction> f = Cast<JSFunction>( |
| 999 | + JSObject::GetProperty(isolate(), global, "f").ToHandleChecked()); |
| 1000 | + |
| 1001 | + CHECK(f->is_compiled(isolate())); |
| 1002 | + |
| 1003 | + // Execute f to get g's SFI (no g bytecode yet) |
| 1004 | + Handle<JSFunction> g = Cast<JSFunction>( |
| 1005 | + Execution::Call(isolate(), f, global, 0, nullptr).ToHandleChecked()); |
| 1006 | + CHECK(!g->is_compiled(isolate())); |
| 1007 | + |
| 1008 | + // Execute g's SFI to initialize g's bytecode, and to get h. |
| 1009 | + Handle<JSFunction> h = Cast<JSFunction>( |
| 1010 | + Execution::Call(isolate(), g, global, 0, nullptr).ToHandleChecked()); |
| 1011 | + CHECK(g->is_compiled(isolate())); |
| 1012 | + CHECK(!h->is_compiled(isolate())); |
| 1013 | + |
| 1014 | + CHECK_EQ(top_level->shared()->function_literal_id(), kTopLevelId); |
| 1015 | + CHECK_EQ(f->shared()->function_literal_id(), kFId); |
| 1016 | + CHECK_EQ(g->shared()->function_literal_id(), kGId); |
| 1017 | + CHECK_EQ(h->shared()->function_literal_id(), kHId); |
| 1018 | + |
| 1019 | + // Age everything so that subsequent GCs can pick it up if possible. |
| 1020 | + SharedFunctionInfo::EnsureOldForTesting(top_level->shared()); |
| 1021 | + SharedFunctionInfo::EnsureOldForTesting(f->shared()); |
| 1022 | + SharedFunctionInfo::EnsureOldForTesting(g->shared()); |
| 1023 | + SharedFunctionInfo::EnsureOldForTesting(h->shared()); |
| 1024 | + old_g = scope.CloseAndEscape(g); |
| 1025 | + } |
| 1026 | + Handle<Script> old_script(Cast<Script>(old_g->shared()->script()), isolate()); |
| 1027 | + |
| 1028 | + // Make sure bytecode is cleared... |
| 1029 | + for (int i = 0; i < 3; ++i) { |
| 1030 | + InvokeMajorGC(); |
| 1031 | + } |
| 1032 | + CHECK(!old_g->is_compiled(isolate())); |
| 1033 | + |
| 1034 | + // The top-level script should now be dead. |
| 1035 | + CHECK(old_script->infos()->get(kTopLevelId).IsCleared()); |
| 1036 | + // f should still be alive by global reference. |
| 1037 | + CHECK(!old_script->infos()->get(kFId).IsCleared()); |
| 1038 | + // g should be kept alive by our old_g handle. |
| 1039 | + CHECK(!old_script->infos()->get(kGId).IsCleared()); |
| 1040 | + // h should be dead since g's bytecode was flushed. |
| 1041 | + CHECK(old_script->infos()->get(kHId).IsCleared()); |
| 1042 | + |
| 1043 | + // Copy the old_script_infos WeakFixedArray, so that we can inspect it after |
| 1044 | + // the merge mutated the original. |
| 1045 | + Handle<WeakFixedArray> unmutated_old_script_list = |
| 1046 | + isolate()->factory()->CopyWeakFixedArray( |
| 1047 | + direct_handle(old_script->infos(), isolate())); |
| 1048 | + |
| 1049 | + { |
| 1050 | + HandleScope scope(isolate()); |
| 1051 | + ScriptStreamingData streamed_source( |
| 1052 | + std::make_unique<DummySourceStream>(source), |
| 1053 | + v8::ScriptCompiler::StreamedSource::UTF8); |
| 1054 | + ScriptCompiler::CompilationDetails details; |
| 1055 | + streamed_source.task = std::make_unique<i::BackgroundCompileTask>( |
| 1056 | + &streamed_source, isolate(), ScriptType::kClassic, |
| 1057 | + ScriptCompiler::CompileOptions::kNoCompileOptions, &details); |
| 1058 | + |
| 1059 | + streamed_source.task->RunOnMainThread(isolate()); |
| 1060 | + |
| 1061 | + Handle<SharedFunctionInfo> top_level_sfi; |
| 1062 | + { |
| 1063 | + // Use a manual GC scope, because we want to test a GC in a very precise |
| 1064 | + // spot in the merge. |
| 1065 | + ManualGCScope manual_gc(isolate()); |
| 1066 | + // There's one more reference to the old_g -- clear it so that nothing is |
| 1067 | + // keeping it alive |
| 1068 | + CHECK(!old_script->infos()->get(kGId).IsCleared()); |
| 1069 | + CHECK(!unmutated_old_script_list->get(kGId).IsCleared()); |
| 1070 | + old_g.PatchValue({}); |
| 1071 | + CHECK(!old_script->infos()->get(kFId).IsCleared()); |
| 1072 | + |
| 1073 | + BackgroundMergeTask::ForceGCDuringNextMergeForTesting(); |
| 1074 | + |
| 1075 | + top_level_sfi = streamed_source.task |
| 1076 | + ->FinalizeScript(isolate(), source_string, |
| 1077 | + ScriptDetails(), old_script) |
| 1078 | + .ToHandleChecked(); |
| 1079 | + CHECK(!old_script->infos()->get(kFId).IsCleared()); |
| 1080 | + } |
| 1081 | + |
| 1082 | + CHECK_EQ(top_level_sfi->script(), *old_script); |
| 1083 | + |
| 1084 | + Handle<JSFunction> top_level = |
| 1085 | + Factory::JSFunctionBuilder{isolate(), top_level_sfi, |
| 1086 | + isolate()->native_context()} |
| 1087 | + .Build(); |
| 1088 | + |
| 1089 | + Handle<JSObject> global(isolate()->context()->global_object(), isolate()); |
| 1090 | + |
| 1091 | + Handle<JSFunction> f = Cast<JSFunction>( |
| 1092 | + JSObject::GetProperty(isolate(), global, "f").ToHandleChecked()); |
| 1093 | + |
| 1094 | + // f should normally be compiled (with the old shared function info but the |
| 1095 | + // new bytecode). However, the extra GCs in finalization might cause it to |
| 1096 | + // be flushed, so we can't guarantee this check. |
| 1097 | + // CHECK(f->is_compiled(isolate())); |
| 1098 | + |
| 1099 | + // Execute f to get g's SFI (no g bytecode yet) |
| 1100 | + Handle<JSFunction> g = Cast<JSFunction>( |
| 1101 | + Execution::Call(isolate(), f, global, 0, nullptr).ToHandleChecked()); |
| 1102 | + CHECK(!g->is_compiled(isolate())); |
| 1103 | + |
| 1104 | + // Execute g's SFI to initialize g's bytecode, and to get h. |
| 1105 | + Handle<JSFunction> h = Cast<JSFunction>( |
| 1106 | + Execution::Call(isolate(), g, global, 0, nullptr).ToHandleChecked()); |
| 1107 | + CHECK(g->is_compiled(isolate())); |
| 1108 | + CHECK(!h->is_compiled(isolate())); |
| 1109 | + |
| 1110 | + CHECK_EQ(top_level->shared()->function_literal_id(), kTopLevelId); |
| 1111 | + CHECK_EQ(f->shared()->function_literal_id(), kFId); |
| 1112 | + CHECK_EQ(g->shared()->function_literal_id(), kGId); |
| 1113 | + CHECK_EQ(h->shared()->function_literal_id(), kHId); |
| 1114 | + |
| 1115 | + CHECK_EQ(top_level->shared()->script(), *old_script); |
| 1116 | + CHECK_EQ(f->shared()->script(), *old_script); |
| 1117 | + CHECK_EQ(g->shared()->script(), *old_script); |
| 1118 | + CHECK_EQ(h->shared()->script(), *old_script); |
| 1119 | + |
| 1120 | + CHECK_EQ(MakeWeak(top_level->shared()), |
| 1121 | + old_script->infos()->get(kTopLevelId)); |
| 1122 | + CHECK_EQ(MakeWeak(f->shared()), old_script->infos()->get(kFId)); |
| 1123 | + CHECK_EQ(MakeWeak(g->shared()), old_script->infos()->get(kGId)); |
| 1124 | + CHECK_EQ(MakeWeak(h->shared()), old_script->infos()->get(kHId)); |
| 1125 | + |
| 1126 | + // The old top-level died, so we have a new one. |
| 1127 | + CHECK_NE(MakeWeak(top_level->shared()), |
| 1128 | + unmutated_old_script_list->get(kTopLevelId)); |
| 1129 | + // The old f was still alive, so it's the same. |
| 1130 | + CHECK_EQ(MakeWeak(f->shared()), unmutated_old_script_list->get(kFId)); |
| 1131 | + // The old g was still alive, so it's the same. |
| 1132 | + CHECK_EQ(MakeWeak(g->shared()), unmutated_old_script_list->get(kGId)); |
| 1133 | + // The old h died, so it's different. |
| 1134 | + CHECK_NE(MakeWeak(h->shared()), unmutated_old_script_list->get(kHId)); |
| 1135 | + } |
| 1136 | +} |
| 1137 | + |
931 | 1138 | } // namespace internal |
932 | 1139 | } // namespace v8 |
0 commit comments