[Native] Add non-owning strings
diff --git a/kotlin-native/runtime/src/main/cpp/KString.cpp b/kotlin-native/runtime/src/main/cpp/KString.cpp
index f593cc4..e59d5c7 100644
--- a/kotlin-native/runtime/src/main/cpp/KString.cpp
+++ b/kotlin-native/runtime/src/main/cpp/KString.cpp
@@ -205,6 +205,21 @@
     });
 }
 
+extern "C" OBJ_GETTER(CreateBorrowedString, StringEncoding encoding, const char* data, uint32_t length) {
+    if (length == 0) RETURN_RESULT_OF0(TheEmptyString);
+    if (encoding == StringEncoding::kUTF8 && utf8StringIsASCII(data, length)) {
+        encoding = StringEncoding::kLatin1;
+    }
+    auto flags = (static_cast<uint32_t>(encoding) << StringHeader::ENCODING_OFFSET) | StringHeader::BORROWED;
+    auto result = AllocArrayInstance(theStringTypeInfo, StringHeader::extraLength(flags) / sizeof(KChar), OBJ_RESULT);
+    auto header = StringHeader::of(result);
+    header->flags_ = flags;
+    auto desc = reinterpret_cast<StringHeader::BorrowedData*>(header->data_);
+    desc->size_ = length;
+    desc->data_ = const_cast<char*>(data);
+    return result;
+}
+
 extern "C" char* CreateCStringFromString(KConstRef kref) {
     if (kref == nullptr) return nullptr;
     std::string utf8 = kotlin::to_string<KStringConversionMode::UNCHECKED>(kref);
diff --git a/kotlin-native/runtime/src/main/cpp/KString.h b/kotlin-native/runtime/src/main/cpp/KString.h
index 8cba01e..a8018aa 100644
--- a/kotlin-native/runtime/src/main/cpp/KString.h
+++ b/kotlin-native/runtime/src/main/cpp/KString.h
@@ -36,23 +36,30 @@
     uint16_t flags_;
     alignas(KChar) char data_[];
 
+    struct __attribute__((packed)) BorrowedData {
+        uint16_t padding_;
+        uint32_t size_;
+        char *data_;
+    };
+
     enum {
         HASHCODE_COMPUTED = 1 << 0,
         IGNORE_LAST_BYTE = 1 << 1,
+        BORROWED = 1 << 2,
         ENCODING_OFFSET = 12,
     };
 
     ALWAYS_INLINE StringEncoding encoding() const { return static_cast<StringEncoding>(flags_ >> ENCODING_OFFSET); }
 
-    ALWAYS_INLINE char *data() { return data_; }
-    ALWAYS_INLINE const char *data() const { return data_; }
-    ALWAYS_INLINE size_t size() const { return count_ * sizeof(KChar) - extraLength(flags_); }
+    ALWAYS_INLINE char *data() { return flags_ & BORROWED ? reinterpret_cast<BorrowedData*>(data_)->data_ : data_; }
+    ALWAYS_INLINE const char *data() const { return flags_ & BORROWED ? reinterpret_cast<const BorrowedData*>(data_)->data_ : data_; }
+    ALWAYS_INLINE size_t size() const { return flags_ & BORROWED ? reinterpret_cast<const BorrowedData*>(data_)->size_ : count_ * sizeof(KChar) - extraLength(flags_); }
 
     ALWAYS_INLINE static StringHeader* of(KRef string) { return reinterpret_cast<StringHeader*>(string); }
     ALWAYS_INLINE static const StringHeader* of(KConstRef string) { return reinterpret_cast<const StringHeader*>(string); }
 
     ALWAYS_INLINE constexpr static size_t extraLength(int flags) {
-        return (offsetof(StringHeader, data_) - sizeof(ArrayHeader)) + !!(flags & IGNORE_LAST_BYTE);
+        return (offsetof(StringHeader, data_) - sizeof(ArrayHeader)) + (flags & BORROWED ? sizeof(BorrowedData) : 0) + (flags & IGNORE_LAST_BYTE ? 1 : 0);
     }
 };
 
@@ -121,6 +128,7 @@
 OBJ_GETTER(CreateStringFromUtf8OrThrow, const char* utf8, uint32_t length);
 OBJ_GETTER(CreateStringFromUtf16, const KChar* utf16, uint32_t length);
 OBJ_GETTER(CreateUninitializedString, StringEncoding encoding, uint32_t length);
+OBJ_GETTER(CreateBorrowedString, StringEncoding encoding, const char* data, uint32_t length);
 
 char* CreateCStringFromString(KConstRef kstring);
 void DisposeCString(char* cstring);
diff --git a/kotlin-native/runtime/src/main/cpp/ObjCInteropUtils.mm b/kotlin-native/runtime/src/main/cpp/ObjCInteropUtils.mm
index ea1e14d..e883ffd 100644
--- a/kotlin-native/runtime/src/main/cpp/ObjCInteropUtils.mm
+++ b/kotlin-native/runtime/src/main/cpp/ObjCInteropUtils.mm
@@ -64,15 +64,23 @@
   NSStringEncoding encoding = [immutableCopyOrSameStr fastestEncoding];
   switch (encoding) {
     case NSUTF8StringEncoding:
-      result = CreateStringFromUtf8(
-        [immutableCopyOrSameStr UTF8String],
-        [immutableCopyOrSameStr lengthOfBytesUsingEncoding: encoding], OBJ_RESULT);
+      if (auto borrowed = CFStringGetCStringPtr((CFStringRef)immutableCopyOrSameStr, encoding)) {
+        result = CreateBorrowedString(StringEncoding::kUTF8, borrowed, strlen(borrowed), OBJ_RESULT);
+      } else {
+        result = CreateStringFromUtf8(
+          [immutableCopyOrSameStr UTF8String],
+          [immutableCopyOrSameStr lengthOfBytesUsingEncoding: encoding], OBJ_RESULT);
+      }
       break;
     case NSASCIIStringEncoding:
     case NSISOLatin1StringEncoding:
-      result = CreateUninitializedString(StringEncoding::kLatin1, length, OBJ_RESULT);
-      [immutableCopyOrSameStr getBytes: StringHeader::of(result)->data() maxLength: length usedLength: nullptr
-        encoding: encoding options: 0 range: NSMakeRange(0, length) remainingRange: nullptr];
+      if (auto borrowed = CFStringGetCStringPtr((CFStringRef)immutableCopyOrSameStr, encoding)) {
+        result = CreateBorrowedString(StringEncoding::kLatin1, borrowed, strlen(borrowed), OBJ_RESULT);
+      } else {
+        result = CreateUninitializedString(StringEncoding::kLatin1, length, OBJ_RESULT);
+        [immutableCopyOrSameStr getBytes: StringHeader::of(result)->data() maxLength: length usedLength: nullptr
+          encoding: encoding options: 0 range: NSMakeRange(0, length) remainingRange: nullptr];
+      }
       break;
     default:
       result = CreateUninitializedString(StringEncoding::kUTF16, length, OBJ_RESULT);