diff --git a/runtime/lib/string.cc b/runtime/lib/string.cc index e411e0763e4..ad611b76ed2 100644 --- a/runtime/lib/string.cc +++ b/runtime/lib/string.cc @@ -245,6 +245,12 @@ DEFINE_NATIVE_ENTRY(StringBase_joinReplaceAllResult, 0, 4) { return result.ptr(); } +DEFINE_NATIVE_ENTRY(StringBase_intern, 0, 1) { + const String& receiver = + String::CheckedHandle(zone, arguments->NativeArgAt(0)); + return Symbols::New(thread, receiver); +} + DEFINE_NATIVE_ENTRY(OneByteString_substringUnchecked, 0, 3) { const String& receiver = String::CheckedHandle(zone, arguments->NativeArgAt(0)); diff --git a/runtime/tests/vm/dart/string_intern_test.dart b/runtime/tests/vm/dart/string_intern_test.dart new file mode 100644 index 00000000000..ae2b55c6eb3 --- /dev/null +++ b/runtime/tests/vm/dart/string_intern_test.dart @@ -0,0 +1,24 @@ +// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import "dart:_internal" show intern; +import "package:expect/expect.dart"; + +@pragma("vm:never-inline") +String genString(int i) => "abc-${i}-xyz"; + +main() { + int random = Object().hashCode; + var a = genString(random); + var b = genString(random); + Expect.notIdentical(a, b); + + var internedA = intern(a); + Expect.equals(a, internedA); + // Likely, but not guarenteed: Expect.identical(a, internedA); + var internedB = intern(b); + Expect.equals(b, internedB); + // Likely, but not guarenteed: Expect.identical(a, internedB); + Expect.identical(internedA, internedB); +} diff --git a/runtime/tests/vm/dart/weak_canonical_string_table_test.dart b/runtime/tests/vm/dart/weak_canonical_string_table_test.dart new file mode 100644 index 00000000000..346d7655829 --- /dev/null +++ b/runtime/tests/vm/dart/weak_canonical_string_table_test.dart @@ -0,0 +1,17 @@ +// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// VMOptions=--old_gen_heap_size=20 + +import "dart:_internal" show intern; + +@pragma("vm:never-inline") +use(x) => x; + +main() { + const MB = 1 << 20; + for (var i = 0; i < 20 * MB; i++) { + use(intern((i.toString()))); // Should not hit OutOfMemory + } +} diff --git a/runtime/vm/bootstrap_natives.h b/runtime/vm/bootstrap_natives.h index 418422cbb2a..026767ff4ce 100644 --- a/runtime/vm/bootstrap_natives.h +++ b/runtime/vm/bootstrap_natives.h @@ -125,6 +125,7 @@ namespace dart { V(StringBase_createFromCodePoints, 3) \ V(StringBase_substringUnchecked, 3) \ V(StringBase_joinReplaceAllResult, 4) \ + V(StringBase_intern, 1) \ V(StringBuffer_createStringFromUint16Array, 3) \ V(OneByteString_substringUnchecked, 3) \ V(OneByteString_allocateFromOneByteList, 3) \ diff --git a/sdk/lib/_internal/vm/lib/internal_patch.dart b/sdk/lib/_internal/vm/lib/internal_patch.dart index 301a1f539d0..cd597732ca1 100644 --- a/sdk/lib/_internal/vm/lib/internal_patch.dart +++ b/sdk/lib/_internal/vm/lib/internal_patch.dart @@ -431,3 +431,6 @@ class FinalizerEntry { @FfiNative('FinalizerEntry_SetExternalSize') external void setExternalSize(int externalSize); } + +@pragma("vm:external-name", "StringBase_intern") +external String intern(String str);