From ad7e54169cd7a30b8aeac2f021234b7c1b8b7acb Mon Sep 17 00:00:00 2001 From: Eduardo Speroni Date: Thu, 8 Oct 2026 12:46:16 -0300 Subject: [PATCH 1/4] feat(metadata-generator): JSON output of the metadata model objc-metadata-generator -output-json writes one .json per module with the same information as the YAML output (members, structural type descriptions, flags) plus each enum's underlying type. build-step-metadata-generator.py enables it when NS_JSON_METADATA_PATH is set. YAML and binary outputs are unchanged. --- .../build-step-metadata-generator.py | 7 + metadata-generator/src/CMakeLists.txt | 1 + metadata-generator/src/Json/JsonSerializer.h | 511 ++++++++++++++++++ metadata-generator/src/main.cpp | 32 +- 4 files changed, 544 insertions(+), 7 deletions(-) create mode 100644 metadata-generator/src/Json/JsonSerializer.h diff --git a/metadata-generator/build-step-metadata-generator.py b/metadata-generator/build-step-metadata-generator.py index 1acd946c9..fdf51b1c4 100755 --- a/metadata-generator/build-step-metadata-generator.py +++ b/metadata-generator/build-step-metadata-generator.py @@ -113,6 +113,7 @@ def map_and_list(func, iterable): "Library/Developer/Shared/Documentation/DocSets/com.apple.adc.documentation.{}.docset" .format(docset_platform)) yaml_output_folder = env_or_none("NS_DEBUG_METADATA_PATH") or env_or_none("TNS_DEBUG_METADATA_PATH") +json_output_folder = env_or_none("NS_JSON_METADATA_PATH") strict_includes = env_or_none("NS_DEBUG_METADATA_STRICT_INCLUDES") or env_or_none("TNS_DEBUG_METADATA_STRICT_INCLUDES") @@ -146,6 +147,12 @@ def generate_metadata(arch): generator_call.extend(["-output-yaml", current_yaml_output_folder]) print("Generating debug metadata in: \"{}\"".format(current_yaml_output_folder)) + # optionally add json output folder + if json_output_folder is not None: + current_json_output_folder = os.path.join(json_output_folder, arch) + generator_call.extend(["-output-json", current_json_output_folder]) + print("Generating JSON metadata in: \"{}\"".format(current_json_output_folder)) + whitelist_file_name = os.path.join(src_root, "whitelist.mdg") if os.path.exists(whitelist_file_name): generator_call.extend(["--whitelist-modules-file", whitelist_file_name]) diff --git a/metadata-generator/src/CMakeLists.txt b/metadata-generator/src/CMakeLists.txt index 037abdc98..8fd309851 100644 --- a/metadata-generator/src/CMakeLists.txt +++ b/metadata-generator/src/CMakeLists.txt @@ -33,6 +33,7 @@ set(GENERATOR_HEADERS Utils/stream.h Utils/StringHasher.h Utils/StringUtils.h + Json/JsonSerializer.h Yaml/MetaYamlTraits.h Yaml/YamlSerializer.h ) diff --git a/metadata-generator/src/Json/JsonSerializer.h b/metadata-generator/src/Json/JsonSerializer.h new file mode 100644 index 000000000..7d31f2d4e --- /dev/null +++ b/metadata-generator/src/Json/JsonSerializer.h @@ -0,0 +1,511 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include + +#include "Meta/MetaEntities.h" +#include "Meta/Utils.h" + +namespace Json { + +static std::string typeTypeToString(Meta::TypeType t) { + switch (t) { + case Meta::TypeType::TypeVoid: + return "Void"; + case Meta::TypeType::TypeBool: + return "Bool"; + case Meta::TypeType::TypeShort: + return "Short"; + case Meta::TypeType::TypeUShort: + return "Ushort"; + case Meta::TypeType::TypeInt: + return "Int"; + case Meta::TypeType::TypeUInt: + return "UInt"; + case Meta::TypeType::TypeLong: + return "Long"; + case Meta::TypeType::TypeULong: + return "ULong"; + case Meta::TypeType::TypeLongLong: + return "LongLong"; + case Meta::TypeType::TypeULongLong: + return "ULongLong"; + case Meta::TypeType::TypeSignedChar: + return "Char"; + case Meta::TypeType::TypeUnsignedChar: + return "UChar"; + case Meta::TypeType::TypeUnichar: + return "Unichar"; + case Meta::TypeType::TypeCString: + return "CString"; + case Meta::TypeType::TypeFloat: + return "Float"; + case Meta::TypeType::TypeDouble: + return "Double"; + case Meta::TypeType::TypeSelector: + return "Selector"; + case Meta::TypeType::TypeClass: + return "Class"; + case Meta::TypeType::TypeInstancetype: + return "Instancetype"; + case Meta::TypeType::TypeId: + return "Id"; + case Meta::TypeType::TypeConstantArray: + return "ConstantArray"; + case Meta::TypeType::TypeIncompleteArray: + return "IncompleteArray"; + case Meta::TypeType::TypeInterface: + return "Interface"; + case Meta::TypeType::TypeBridgedInterface: + return "BridgedInterface"; + case Meta::TypeType::TypePointer: + return "Pointer"; + case Meta::TypeType::TypeFunctionPointer: + return "FunctionPointer"; + case Meta::TypeType::TypeBlock: + return "Block"; + case Meta::TypeType::TypeStruct: + return "Struct"; + case Meta::TypeType::TypeUnion: + return "Union"; + case Meta::TypeType::TypeAnonymousStruct: + return "AnonymousStruct"; + case Meta::TypeType::TypeAnonymousUnion: + return "AnonymousUnion"; + case Meta::TypeType::TypeEnum: + return "Enum"; + case Meta::TypeType::TypeVaList: + return "VaList"; + case Meta::TypeType::TypeProtocol: + return "Protocol"; + case Meta::TypeType::TypeTypeArgument: + return "TypeArgument"; + case Meta::TypeType::TypeExtVector: + return "ExtVector"; + case Meta::TypeType::TypeNullable: + return "Nullable"; + case Meta::TypeType::TypeNonNullable: + return "NonNullable"; + } + return "Unknown"; +} + +static std::string metaTypeToString(Meta::MetaType t) { + switch (t) { + case Meta::MetaType::Undefined: + return "Undefined"; + case Meta::MetaType::Struct: + return "Struct"; + case Meta::MetaType::Union: + return "Union"; + case Meta::MetaType::Function: + return "Function"; + case Meta::MetaType::Enum: + return "Enum"; + case Meta::MetaType::Var: + return "Var"; + case Meta::MetaType::Interface: + return "Interface"; + case Meta::MetaType::Protocol: + return "Protocol"; + case Meta::MetaType::Category: + return "Category"; + case Meta::MetaType::Method: + return "Method"; + case Meta::MetaType::Property: + return "Property"; + case Meta::MetaType::EnumConstant: + return "EnumConstant"; + } + return "Unknown"; +} + +static llvm::json::Object typeToJson(Meta::Type* type) { + llvm::json::Object obj; + obj["Type"] = typeTypeToString(type->getType()); + + switch (type->getType()) { + case Meta::TypeType::TypeId: { + auto& t = type->as(); + llvm::json::Array protocols; + for (auto* p : t.protocols) protocols.push_back(p->jsName); + obj["WithProtocols"] = std::move(protocols); + break; + } + case Meta::TypeType::TypeClass: { + auto& t = type->as(); + llvm::json::Array protocols; + for (auto* p : t.protocols) protocols.push_back(p->jsName); + if (!protocols.empty()) obj["WithProtocols"] = std::move(protocols); + break; + } + case Meta::TypeType::TypeConstantArray: { + auto& t = type->as(); + obj["ArrayType"] = typeToJson(t.innerType); + obj["Size"] = t.size; + break; + } + case Meta::TypeType::TypeIncompleteArray: { + auto& t = type->as(); + obj["ArrayType"] = typeToJson(t.innerType); + break; + } + case Meta::TypeType::TypeInterface: { + auto& t = type->as(); + obj["Name"] = t.interface->name; + if (!t.typeArguments.empty()) { + llvm::json::Array args; + for (auto* a : t.typeArguments) args.push_back(typeToJson(a)); + obj["TypeParameters"] = std::move(args); + } + llvm::json::Array protocols; + for (auto* p : t.protocols) protocols.push_back(p->jsName); + obj["WithProtocols"] = std::move(protocols); + break; + } + case Meta::TypeType::TypeBridgedInterface: { + auto& t = type->as(); + obj["Name"] = t.name; + std::string bridgedTo = t.isId() ? "id" + : (t.bridgedInterface == nullptr + ? "[None]" + : t.bridgedInterface->jsName); + obj["BridgedTo"] = bridgedTo; + break; + } + case Meta::TypeType::TypePointer: { + auto& t = type->as(); + obj["PointerType"] = typeToJson(t.innerType); + break; + } + case Meta::TypeType::TypeFunctionPointer: { + auto& t = type->as(); + llvm::json::Array sig; + for (auto* s : t.signature) sig.push_back(typeToJson(s)); + obj["Signature"] = std::move(sig); + break; + } + case Meta::TypeType::TypeBlock: { + auto& t = type->as(); + llvm::json::Array sig; + for (auto* s : t.signature) sig.push_back(typeToJson(s)); + obj["Signature"] = std::move(sig); + break; + } + case Meta::TypeType::TypeStruct: { + auto& t = type->as(); + obj["Module"] = t.structMeta->module->getFullModuleName(); + obj["Name"] = t.structMeta->name; + break; + } + case Meta::TypeType::TypeUnion: { + auto& t = type->as(); + obj["Module"] = t.unionMeta->module->getFullModuleName(); + obj["Name"] = t.unionMeta->name; + break; + } + case Meta::TypeType::TypeAnonymousStruct: { + auto& t = type->as(); + llvm::json::Array fields; + for (auto& f : t.fields) { + llvm::json::Object field; + field["Name"] = f.name; + field["Signature"] = typeToJson(f.encoding); + fields.push_back(std::move(field)); + } + obj["Fields"] = std::move(fields); + break; + } + case Meta::TypeType::TypeAnonymousUnion: { + auto& t = type->as(); + llvm::json::Array fields; + for (auto& f : t.fields) { + llvm::json::Object field; + field["Name"] = f.name; + field["Signature"] = typeToJson(f.encoding); + fields.push_back(std::move(field)); + } + obj["Fields"] = std::move(fields); + break; + } + case Meta::TypeType::TypeEnum: { + auto& t = type->as(); + obj["Name"] = t.enumMeta->jsName; + if (t.underlyingType != nullptr) + obj["UnderlyingType"] = typeToJson(t.underlyingType); + break; + } + case Meta::TypeType::TypeTypeArgument: { + auto& t = type->as(); + obj["Name"] = t.name; + obj["UnderlyingType"] = typeToJson(t.underlyingType); + llvm::json::Array protocols; + for (auto* p : t.protocols) protocols.push_back(p->jsName); + if (!protocols.empty()) obj["WithProtocols"] = std::move(protocols); + break; + } + case Meta::TypeType::TypeExtVector: { + auto& t = type->as(); + obj["InnerType"] = typeToJson(t.innerType); + obj["Size"] = t.size; + break; + } + case Meta::TypeType::TypeNullable: { + auto& t = type->as(); + obj["InnerType"] = typeToJson(t.innerType); + break; + } + case Meta::TypeType::TypeNonNullable: { + auto& t = type->as(); + obj["InnerType"] = typeToJson(t.innerType); + break; + } + default: + break; + } + return obj; +} + +static llvm::json::Array flagsToJson(Meta::MetaFlags flags) { + llvm::json::Array arr; + if (flags & Meta::MetaFlags::IsIosAppExtensionAvailable) + arr.push_back("IsIosAppExtensionAvailable"); + if (flags & Meta::MetaFlags::MemberIsOptional) + arr.push_back("MemberIsOptional"); + if (flags & Meta::MetaFlags::FunctionIsVariadic) + arr.push_back("FunctionIsVariadic"); + if (flags & Meta::MetaFlags::FunctionOwnsReturnedCocoaObject) + arr.push_back("FunctionOwnsReturnedCocoaObject"); + if (flags & Meta::MetaFlags::FunctionReturnsUnmanaged) + arr.push_back("FunctionReturnsUnmanaged"); + if (flags & Meta::MetaFlags::MethodIsVariadic) + arr.push_back("MethodIsVariadic"); + if (flags & Meta::MetaFlags::MethodIsNullTerminatedVariadic) + arr.push_back("MethodIsNullTerminatedVariadic"); + if (flags & Meta::MetaFlags::MethodOwnsReturnedCocoaObject) + arr.push_back("MethodOwnsReturnedCocoaObject"); + if (flags & Meta::MetaFlags::MethodHasErrorOutParameter) + arr.push_back("MethodHasErrorOutParameter"); + if (flags & Meta::MetaFlags::MethodIsInitializer) + arr.push_back("MethodIsInitializer"); + return arr; +} + +static void mapBaseMetaJson(llvm::json::Object& obj, Meta::Meta* meta) { + obj["Name"] = meta->name; + obj["JsName"] = meta->jsName; + if (!meta->demangledName.empty()) obj["DemangledName"] = meta->demangledName; + obj["Filename"] = meta->fileName; + obj["Module"] = meta->module ? meta->module->getFullModuleName() : ""; + if (!meta->introducedIn.isUnknown()) + obj["IntroducedIn"] = meta->introducedIn.toString(); + obj["Flags"] = flagsToJson(meta->flags); + obj["Type"] = metaTypeToString(meta->type); +} + +static llvm::json::Object methodToJson(Meta::MethodMeta* meta) { + llvm::json::Object obj; + mapBaseMetaJson(obj, meta); + llvm::json::Array sig; + for (auto* t : meta->signature) sig.push_back(typeToJson(t)); + obj["Signature"] = std::move(sig); + return obj; +} + +static llvm::json::Object propertyToJson(Meta::PropertyMeta* meta) { + llvm::json::Object obj; + mapBaseMetaJson(obj, meta); + if (meta->getter) obj["Getter"] = methodToJson(meta->getter); + if (meta->setter) obj["Setter"] = methodToJson(meta->setter); + return obj; +} + +static void mapBaseClassMetaJson(llvm::json::Object& obj, + Meta::BaseClassMeta* meta) { + mapBaseMetaJson(obj, meta); + + llvm::json::Array instanceMethods; + for (auto* m : meta->instanceMethods) + instanceMethods.push_back(methodToJson(m)); + obj["InstanceMethods"] = std::move(instanceMethods); + + llvm::json::Array staticMethods; + for (auto* m : meta->staticMethods) staticMethods.push_back(methodToJson(m)); + obj["StaticMethods"] = std::move(staticMethods); + + llvm::json::Array instanceProperties; + for (auto* p : meta->instanceProperties) + instanceProperties.push_back(propertyToJson(p)); + obj["InstanceProperties"] = std::move(instanceProperties); + + llvm::json::Array staticProperties; + for (auto* p : meta->staticProperties) + staticProperties.push_back(propertyToJson(p)); + obj["StaticProperties"] = std::move(staticProperties); + + llvm::json::Array protocols; + for (auto* p : meta->protocols) protocols.push_back(p->jsName); + obj["Protocols"] = std::move(protocols); +} + +static llvm::json::Object metaToJson(Meta::Meta* meta) { + llvm::json::Object obj; + + switch (meta->type) { + case Meta::MetaType::Function: { + auto& m = meta->as(); + mapBaseMetaJson(obj, meta); + llvm::json::Array sig; + for (auto* t : m.signature) sig.push_back(typeToJson(t)); + obj["Signature"] = std::move(sig); + break; + } + case Meta::MetaType::Struct: { + auto& m = meta->as(); + auto& rec = m.as(); + mapBaseMetaJson(obj, meta); + llvm::json::Array fields; + for (auto& f : rec.fields) { + llvm::json::Object field; + field["Name"] = f.name; + field["Signature"] = typeToJson(f.encoding); + fields.push_back(std::move(field)); + } + obj["Fields"] = std::move(fields); + break; + } + case Meta::MetaType::Union: { + auto& m = meta->as(); + auto& rec = m.as(); + mapBaseMetaJson(obj, meta); + llvm::json::Array fields; + for (auto& f : rec.fields) { + llvm::json::Object field; + field["Name"] = f.name; + field["Signature"] = typeToJson(f.encoding); + fields.push_back(std::move(field)); + } + obj["Fields"] = std::move(fields); + break; + } + case Meta::MetaType::Var: { + auto& m = meta->as(); + mapBaseMetaJson(obj, meta); + obj["Signature"] = typeToJson(m.signature); + if (m.hasValue) obj["Value"] = m.value; + break; + } + case Meta::MetaType::Enum: { + auto& m = meta->as(); + mapBaseMetaJson(obj, meta); + llvm::json::Array fullNameFields; + for (auto& f : m.fullNameFields) { + llvm::json::Object field; + field[f.name] = f.value; + fullNameFields.push_back(std::move(field)); + } + obj["FullNameFields"] = std::move(fullNameFields); + llvm::json::Array swiftNameFields; + for (auto& f : m.swiftNameFields) { + llvm::json::Object field; + field[f.name] = f.value; + swiftNameFields.push_back(std::move(field)); + } + obj["SwiftNameFields"] = std::move(swiftNameFields); + break; + } + case Meta::MetaType::EnumConstant: { + auto& m = meta->as(); + mapBaseMetaJson(obj, meta); + obj["Value"] = m.value; + break; + } + case Meta::MetaType::Interface: { + auto& m = meta->as(); + auto& bc = m.as(); + mapBaseClassMetaJson(obj, &bc); + if (m.base != nullptr) obj["Base"] = m.base->jsName; + break; + } + case Meta::MetaType::Protocol: { + auto& m = meta->as(); + auto& bc = m.as(); + mapBaseClassMetaJson(obj, &bc); + break; + } + case Meta::MetaType::Category: { + auto& m = meta->as(); + auto& bc = m.as(); + mapBaseClassMetaJson(obj, &bc); + if (m.extendedInterface) + obj["ExtendedInterface"] = m.extendedInterface->jsName; + break; + } + case Meta::MetaType::Method: { + auto& m = meta->as(); + obj = methodToJson(&m); + break; + } + case Meta::MetaType::Property: { + auto& m = meta->as(); + obj = propertyToJson(&m); + break; + } + default: + mapBaseMetaJson(obj, meta); + break; + } + return obj; +} + +static llvm::json::Object moduleToJson( + std::pair>& modulePair) { + llvm::json::Object root; + + llvm::json::Object moduleObj; + moduleObj["FullName"] = modulePair.first->getFullModuleName(); + moduleObj["IsPartOfFramework"] = modulePair.first->isPartOfFramework(); + moduleObj["IsSystemModule"] = modulePair.first->IsSystem; + std::vector libs; + Meta::Utils::getAllLinkLibraries(modulePair.first, libs); + llvm::json::Array libsArr; + for (auto& lib : libs) { + llvm::json::Object libObj; + libObj["Library"] = lib.Library; + libObj["IsFramework"] = lib.IsFramework; + libsArr.push_back(std::move(libObj)); + } + moduleObj["Libraries"] = std::move(libsArr); + root["Module"] = std::move(moduleObj); + + llvm::json::Array items; + for (auto* meta : modulePair.second) items.push_back(metaToJson(meta)); + root["Items"] = std::move(items); + + return root; +} + +class JsonSerializer { + public: + static void serialize( + const std::string& outputFilePath, + std::pair>& modulePair) { + std::error_code errorCode; + llvm::raw_fd_ostream fileStream(outputFilePath, errorCode, + llvm::sys::fs::OpenFlags::OF_None); + if (errorCode) + throw std::runtime_error(std::string("Unable to open file ") + + outputFilePath + "."); + + llvm::json::Object root = moduleToJson(modulePair); + fileStream << llvm::formatv("{0:2}", llvm::json::Value(std::move(root))); + fileStream.close(); + } +}; + +} // namespace Json diff --git a/metadata-generator/src/main.cpp b/metadata-generator/src/main.cpp index f4b7b8aee..cc969e282 100644 --- a/metadata-generator/src/main.cpp +++ b/metadata-generator/src/main.cpp @@ -1,5 +1,15 @@ +#include +#include +#include +#include +#include + +#include +#include + #include "Binary/binarySerializer.h" #include "HeadersParser/Parser.h" +#include "Json/JsonSerializer.h" #include "Meta/DeclarationConverterVisitor.h" #include "Meta/Filters/HandleExceptionalMetasFilter.h" #include "Meta/Filters/HandleMethodsAndPropertiesWithSameNameFilter.h" @@ -10,13 +20,6 @@ #include "TypeScript/DefinitionWriter.h" #include "TypeScript/DocSetManager.h" #include "Yaml/YamlSerializer.h" -#include -#include -#include -#include -#include -#include -#include // Command line parameters llvm::cl::opt cla_verbose("verbose", llvm::cl::desc("Set verbose output mode"), llvm::cl::value_desc("bool")); @@ -24,6 +27,9 @@ llvm::cl::opt cla_strictIncludes("strict-includes", llvm::cl::desc("Set llvm::cl::opt cla_outputUmbrellaHeaderFile("output-umbrella", llvm::cl::desc("Specify the output umbrella header file"), llvm::cl::value_desc("file_path")); llvm::cl::opt cla_inputUmbrellaHeaderFile("input-umbrella", llvm::cl::desc("Specify the input umbrella header file"), llvm::cl::value_desc("file_path")); llvm::cl::opt cla_outputYamlFolder("output-yaml", llvm::cl::desc("Specify the output yaml folder"), llvm::cl::value_desc("")); +llvm::cl::opt cla_outputJsonFolder( + "output-json", llvm::cl::desc("Specify the output json folder"), + llvm::cl::value_desc("")); llvm::cl::opt cla_outputModuleMapsFolder("output-modulemaps", llvm::cl::desc("Specify the fodler where modulemap files of all parsed modules will be dumped"), llvm::cl::value_desc("")); llvm::cl::opt cla_outputBinFile("output-bin", llvm::cl::desc("Specify the output binary metadata file"), llvm::cl::value_desc("")); llvm::cl::opt cla_outputDtsFolder("output-typescript", llvm::cl::desc("Specify the output .d.ts folder"), llvm::cl::value_desc("")); @@ -94,6 +100,18 @@ class MetaGenerationConsumer : public clang::ASTConsumer { } } + // Serialize Meta objects to JSON + if (!cla_outputJsonFolder.empty()) { + llvm::sys::fs::create_directories(cla_outputJsonFolder); + for (std::pair >& + modulePair : metasByModules) { + std::string jsonFileName = + modulePair.first->getFullModuleName() + ".json"; + Json::JsonSerializer::serialize( + cla_outputJsonFolder + "/" + jsonFileName, modulePair); + } + } + // Serialize Meta objects to binary metadata if (!cla_outputBinFile.empty()) { binary::MetaFile file(metaContainer.size() / 10); // Average number of hash collisions: 10 per bucket From e1b7f1092d5fa825a095b18db1f097b0cc5d0fd6 Mon Sep 17 00:00:00 2001 From: Eduardo Speroni Date: Thu, 8 Oct 2026 12:46:18 -0300 Subject: [PATCH 2/4] feat(scripts): generate AOT call stubs as objc_msgSend casts from metadata JSON generate-aot.py reads a config of (class, selector, static) entries and the metadata JSON, resolves each member through the class's protocols and Base chain, and emits one C stub per declaring-class member built on the NativeScriptAOT.h bridge: cast objc_msgSend / objc_msgSendSuper with exact C types, struct typedefs derived from metadata field layouts, argument-count guard, @try around the send, and typed return setters. Entries the bridge cannot model are skipped with a reason. --- scripts/aot-config.example.json | 13 + scripts/generate-aot.py | 762 ++++++++++++++++++++++++++++++++ 2 files changed, 775 insertions(+) create mode 100644 scripts/aot-config.example.json create mode 100644 scripts/generate-aot.py diff --git a/scripts/aot-config.example.json b/scripts/aot-config.example.json new file mode 100644 index 000000000..aff23a5b3 --- /dev/null +++ b/scripts/aot-config.example.json @@ -0,0 +1,13 @@ +{ + "methods": [ + {"class": "UIScreen", "selector": "scale"}, + {"class": "UIView", "selector": "frame"}, + {"class": "UIView", "selector": "setNeedsLayout"}, + {"class": "UIView", "selector": "addSubview:"}, + {"class": "UIView", "selector": "convertPoint:toView:"}, + {"class": "UILabel", "selector": "description"}, + {"class": "NSDictionary", "selector": "objectForKey:"}, + {"class": "UIColor", "selector": "colorWithRed:green:blue:alpha:", "static": true}, + {"class": "NSIndexPath", "selector": "indexPathForItem:inSection:", "static": true} + ] +} diff --git a/scripts/generate-aot.py b/scripts/generate-aot.py new file mode 100644 index 000000000..0b75f21c8 --- /dev/null +++ b/scripts/generate-aot.py @@ -0,0 +1,762 @@ +#!/usr/bin/env python3 +"""Generate NativeScript AOT call stubs. + +Reads an AOT config listing (class, selector) pairs and the metadata +generator's JSON output (one .json per module, from -output-json), +and writes one Objective-C file of stubs that send each message through a +cast objc_msgSend with the exact C ABI types from metadata. The stubs talk to +the runtime only through ; the file needs no +framework header because struct layouts are emitted from metadata. + +Config schema: + {"methods": [{"class": "UIScreen", "selector": "scale"}, + {"class": "UIColor", "selector": "colorWithRed:green:blue:alpha:", + "static": true}]} +Unknown keys are ignored. + +Usage: + generate-aot.py CONFIG --metadata DIR [--output FILE] [--report] +""" + +import argparse +import json +import os +import re +import sys + + +class InputError(Exception): + pass + + +class Unsupported(Exception): + def __init__(self, message, category): + super().__init__(message) + self.category = category + + +RECORD = "struct not representable" + + +# --------------------------------------------------------------------------- +# Metadata +# --------------------------------------------------------------------------- + + +class Metadata: + def __init__(self, directory): + if not os.path.isdir(directory): + raise InputError(f"metadata directory not found: {directory}") + self.classes = {} + self.protocols = {} + self.records = {} # (kind, module, name) -> item + self.records_by_name = {} # (kind, name) -> item + self.enums = {} + files = sorted(f for f in os.listdir(directory) if f.endswith(".json")) + if not files: + raise InputError(f"no .json files in {directory}") + for name in files: + path = os.path.join(directory, name) + try: + with open(path) as f: + data = json.load(f) + items = data["Items"] + except (ValueError, KeyError, TypeError) as e: + raise InputError(f"malformed metadata file {path}: {e}") + for item in items: + self._index(item) + + def _index(self, item): + kind = item.get("Type") + if kind in ("Interface", "Protocol"): + table = self.classes if kind == "Interface" else self.protocols + for key in ("Name", "JsName", "DemangledName"): + value = item.get(key) + if value: + table.setdefault((key, value), item) + elif kind in ("Struct", "Union"): + self.records.setdefault((kind, item.get("Module"), item["Name"]), item) + self.records_by_name.setdefault((kind, item["Name"]), item) + elif kind == "Enum": + self.enums.setdefault(item.get("JsName") or item["Name"], item) + + @staticmethod + def _lookup(table, name): + for key in ("Name", "JsName", "DemangledName"): + item = table.get((key, name)) + if item is not None: + return item + return None + + def find_class(self, name): + return self._lookup(self.classes, name) + + def find_protocol(self, name): + return self._lookup(self.protocols, name) + + def find_record(self, kind, module, name): + item = self.records.get((kind, module, name)) + if item is None: + item = self.records_by_name.get((kind, name)) + return item + + def find_method(self, class_name, selector, is_static): + """Returns (method, binding class name) or None. + + Searches the declaration's own methods and property accessors, then + its protocols, then its Base chain. The binding class is the one whose + template the runtime binds the member on: the declaring class, or for + a protocol member the class in the chain that adopts the protocol. A + protocol name is accepted as the starting point and binds itself. + """ + pending = [] + cls = self.find_class(class_name) + if cls is not None: + pending.append((cls, cls["Name"])) + proto = self.find_protocol(class_name) + if proto is not None: + pending.append((proto, cls["Name"] if cls is not None else proto["Name"])) + methods_key = "StaticMethods" if is_static else "InstanceMethods" + props_key = "StaticProperties" if is_static else "InstanceProperties" + seen = set() + while pending: + decl, binder = pending.pop(0) + ident = (decl.get("Type"), decl.get("Name")) + if ident in seen: + continue + seen.add(ident) + for method in decl.get(methods_key) or []: + if method.get("Name") == selector: + return method, binder + for prop in decl.get(props_key) or []: + for accessor in ("Getter", "Setter"): + method = prop.get(accessor) + if method and method.get("Name") == selector: + return method, binder + protocols = [self.find_protocol(p) for p in decl.get("Protocols") or []] + pending[0:0] = [(p, binder) for p in protocols if p is not None] + base = self.find_class(decl["Base"]) if decl.get("Base") else None + if base is not None: + pending.append((base, base["Name"])) + return None + + +# --------------------------------------------------------------------------- +# Types +# --------------------------------------------------------------------------- + +# metadata kind -> (C type, signed?) +INTEGERS = { + "Char": ("signed char", True), + "UChar": ("unsigned char", False), + "Short": ("short", True), + "Ushort": ("unsigned short", False), + "UShort": ("unsigned short", False), + "Int": ("int", True), + "UInt": ("unsigned int", False), + "Long": ("long", True), + "ULong": ("unsigned long", False), + "LongLong": ("long long", True), + "ULongLong": ("unsigned long long", False), +} + +OBJECT_KINDS = ("Id", "Instancetype", "Interface", "BridgedInterface") + + +class CType: + """A resolved parameter or return type. + + kind is one of void, bool, int, uint, float, double, sel, class, object, + pointer, struct. + """ + + def __init__(self, kind, ctype, record=None): + self.kind = kind + self.ctype = ctype + self.record = record + + +def describe(t): + if not isinstance(t, dict): + return repr(t) + kind = t.get("Type") + name = t.get("Name") + return f"{kind} {name}" if name else str(kind) + + +class TypeMapper: + def __init__(self, metadata): + self.md = metadata + self.records = {} # id(item) -> RecordDef + self.record_order = [] + self.vectors = {} # (ctype, size) -> typedef name + self.typedef_names = set() + + def unwrap(self, t): + while isinstance(t, dict): + kind = t.get("Type") + if kind in ("Nullable", "NonNullable"): + t = t.get("InnerType") + elif kind == "TypeArgument": + t = t.get("UnderlyingType") + elif kind == "Enum": + underlying = t.get("UnderlyingType") + if underlying is None: + item = self.md.enums.get(t.get("Name")) + underlying = item.get("UnderlyingType") if item else None + if underlying is None: + raise Unsupported(f"enum {t.get('Name')} has no underlying type in metadata", + "enum without underlying type in metadata") + t = underlying + else: + return t + raise InputError(f"malformed type {t!r}") + + def resolve(self, t, is_return): + t = self.unwrap(t) + kind = t.get("Type") + if kind == "Void": + if not is_return: + raise Unsupported("void parameter", "void parameter") + return CType("void", "void") + if kind == "Bool": + return CType("bool", "BOOL") + if kind in ("LongLong", "ULongLong"): + # The generic path converts these to and from BigInt beyond 2^53, + # which the bridge's 64-bit getters and setters do not model. + raise Unsupported(f"{kind} (BigInt marshalling)", "long long (BigInt marshalling)") + if kind in INTEGERS: + ctype, signed = INTEGERS[kind] + return CType("int" if signed else "uint", ctype) + if kind == "Float": + return CType("float", "float") + if kind == "Double": + return CType("double", "double") + if kind == "Selector": + return CType("sel", "SEL") + if kind == "Class": + return CType("class", "Class") + if kind in OBJECT_KINDS: + if kind == "BridgedInterface" and t.get("BridgedTo") == "[None]": + raise Unsupported(f"unbridged CF type {t.get('Name')}", "unbridged CF type") + return CType("object", "id") + if kind == "Pointer": + inner = self.unwrap(t.get("PointerType")) + if inner.get("Type") != "Void": + raise Unsupported(f"typed pointer to {describe(inner)} (runtime needs the pointee type)", + "typed pointer") + return CType("pointer", "void*") + if kind == "Struct": + record = self.record(t, "Struct") + return CType("struct", record.cname, record) + if kind == "Block": + what = "block " + ("return" if is_return else "parameter") + raise Unsupported(what, what) + if kind == "FunctionPointer": + what = "function pointer " + ("return" if is_return else "parameter") + raise Unsupported(what, what) + raise Unsupported(f"type {describe(t)}", f"type {kind}") + + # --- records --- + + def record(self, t, kind): + item = self.md.find_record(kind, t.get("Module"), t.get("Name")) + if item is None: + raise Unsupported(f"{kind.lower()} {t.get('Name')} not found in metadata", RECORD) + key = id(item) + existing = self.records.get(key) + if existing is not None: + if existing.error: + raise existing.error + if existing.building: + raise Unsupported(f"{kind.lower()} {item['Name']} contains itself by value", RECORD) + return existing + rec = RecordDef(item, kind, self._unique_name("AOT_" + sanitize(item["Name"]))) + self.records[key] = rec + rec.building = True + try: + fields = item.get("Fields") or [] + if not fields: + raise Unsupported(f"{kind.lower()} {item['Name']} has no fields in metadata", RECORD) + rec.fields = self._fields(fields) + except Unsupported as e: + rec.error = Unsupported(f"{kind.lower()} {item['Name']}: {e}", e.category) + raise rec.error + finally: + rec.building = False + self.record_order.append(rec) + return rec + + def _unique_name(self, base): + name = base + n = 2 + while name in self.typedef_names: + name = f"{base}_{n}" + n += 1 + self.typedef_names.add(name) + return name + + def _fields(self, fields): + out = [] + for i, field in enumerate(fields): + name = field.get("Name") or "" + if not IDENT.match(name) or name in C_KEYWORDS: + name = f"f{i}" + # Without BitWidth in metadata a bit-field struct (NSDecimal) gets + # the same wrong layout as the runtime's own description of it. + width = field.get("BitWidth") + if width is None: + out.append(self.field_decl(field.get("Signature"), name)) + elif int(width) == 0: + out.append(self.field_decl(field.get("Signature"), "") + " : 0") + else: + out.append(self.field_decl(field.get("Signature"), name) + f" : {int(width)}") + return out + + def field_decl(self, t, name): + """Returns a C declaration for a field of type t named name.""" + t = self.unwrap(t) + kind = t.get("Type") + if kind == "Bool": + return f"BOOL {name}" + if kind in INTEGERS: + return f"{INTEGERS[kind][0]} {name}" + if kind == "Unichar": + return f"unsigned short {name}" + if kind in ("Float", "Double"): + return f"{kind.lower()} {name}" + if kind in ("Pointer", "CString", "FunctionPointer", "Block", "Selector", + "Class", "Protocol") or kind in OBJECT_KINDS: + return f"void* {name}" + if kind in ("Struct", "Union"): + return f"{self.record(t, kind).cname} {name}" + if kind in ("AnonymousStruct", "AnonymousUnion"): + keyword = "struct" if kind == "AnonymousStruct" else "union" + if not t.get("Fields"): + raise Unsupported(f"field {name} is an empty {keyword}", RECORD) + inner = "; ".join(self._fields(t["Fields"])) + ";" + return f"{keyword} {{ {inner} }} {name}" + if kind == "ConstantArray": + return self.field_decl(t.get("ArrayType"), f"{name}[{int(t.get('Size'))}]") + if kind == "ExtVector": + return f"{self.vector(t)} {name}" + raise Unsupported(f"field {name} of type {describe(t)}", RECORD) + + def vector(self, t): + inner = self.unwrap(t.get("InnerType")) + kind = inner.get("Type") + if kind in INTEGERS: + ctype = INTEGERS[kind][0] + elif kind in ("Float", "Double"): + ctype = kind.lower() + else: + raise Unsupported(f"vector of {describe(inner)}", RECORD) + size = int(t.get("Size")) + key = (ctype, size) + if key not in self.vectors: + self.vectors[key] = self._unique_name(f"AOT_{ctype.replace(' ', '_')}{size}") + return self.vectors[key] + + def emit_typedefs(self): + lines = [] + for (ctype, size), name in sorted(self.vectors.items(), key=lambda kv: kv[1]): + lines.append(f"typedef {ctype} {name} __attribute__((ext_vector_type({size})));") + for rec in self.record_order: + keyword = "struct" if rec.kind == "Struct" else "union" + lines.append(f"typedef {keyword} {{") + for decl in rec.fields: + lines.append(f" {decl};") + lines.append(f"}} {rec.cname};") + return lines + + +class RecordDef: + def __init__(self, item, kind, cname): + self.item = item + self.kind = kind + self.cname = cname + self.fields = [] + self.error = None + self.building = False + + @property + def runtime_name(self): + return self.item.get("JsName") or self.item["Name"] + + +IDENT = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$") +C_KEYWORDS = { + "auto", "break", "case", "char", "const", "continue", "default", "do", + "double", "else", "enum", "extern", "float", "for", "goto", "if", "inline", + "int", "long", "register", "restrict", "return", "short", "signed", + "sizeof", "static", "struct", "switch", "typedef", "union", "unsigned", + "void", "volatile", "while", "id", "SEL", "Class", "BOOL", "YES", "NO", + "nil", "self", "super", "in", "out", "inout", "bycopy", "byref", "oneway", +} + + +# --------------------------------------------------------------------------- +# Code generation +# --------------------------------------------------------------------------- + + +def sanitize(name): + return re.sub(r"[^A-Za-z0-9_]", "_", name) + + +def sanitize_selector(sel): + return sanitize(sel.rstrip(":").replace(":", "_")) + + +def method_stub_name(cls, sel, is_static): + suffix = "_static" if is_static else "" + return f"AOT_{sanitize(cls)}_{sanitize_selector(sel)}{suffix}" + + +def _build_msgsend_call(fn, receiver, ret, args): + params = ", ".join(["id", "SEL"] + [a.ctype for a in args]) + values = "".join(f", a{i}" for i in range(len(args))) + return f"(({ret.ctype} (*)({params})){fn})({receiver}, sel{values})" + + +def build_super_call(fn, ret, args): + params = ", ".join(["struct objc_super*", "SEL"] + [a.ctype for a in args]) + values = "".join(f", a{i}" for i in range(len(args))) + return f"(({ret.ctype} (*)({params})){fn})(&sup, sel{values})" + + +ARG_GETTERS = { + "object": ("id", "nil", "__ns_aot_arg_object"), + "sel": ("SEL", "NULL", "__ns_aot_arg_selector"), + "class": ("Class", "Nil", "__ns_aot_arg_class"), + "pointer": ("void*", "NULL", "__ns_aot_arg_pointer"), +} + +RETURN_SETTERS = { + "bool": "__ns_aot_return_bool(info, r);", + "int": "__ns_aot_return_int64(info, (int64_t)r);", + "uint": "__ns_aot_return_uint64(info, (uint64_t)r);", + "float": "__ns_aot_return_double(info, (double)r);", + "double": "__ns_aot_return_double(info, r);", + "sel": "__ns_aot_return_selector(info, r);", + "class": "__ns_aot_return_class(info, r);", + "pointer": "__ns_aot_return_pointer(info, r);", +} + +RETURN_INIT = { + "bool": "NO", "int": "0", "uint": "0", "float": "0", "double": "0", + "sel": "NULL", "class": "Nil", "object": "nil", "pointer": "NULL", +} + + +class Stub: + def __init__(self, cls, selector, is_static, method, ret, args, name): + self.cls = cls + self.selector = selector + self.is_static = is_static + self.method = method + self.ret = ret + self.args = args + self.name = name + self.registrations = [] + + def flags(self): + return set(self.method.get("Flags") or []) + + def emit(self): + out = [] + w = out.append + w(f"static bool {self.name}(NSAOTCallInfo info) {{") + w(f" if (__ns_aot_arg_count(info) != {len(self.args)}) return false;") + + struct_vars = {} + for t in [self.ret] + self.args: + if t.kind == "struct" and t.record.cname not in struct_vars: + var = f"st{len(struct_vars)}" + struct_vars[t.record.cname] = var + w(f" static NSAOTStructType {var} = NULL;") + w(f" if (!{var}) {var} = __ns_aot_struct_type(\"{t.record.runtime_name}\");") + w(f" if (!{var}) return false;") + + w(f" SEL sel = @selector({self.selector});") + if self.is_static: + w(" Class cls = __ns_aot_get_static_class(info);") + w(" if (cls == nil) return false;") + else: + w(" bool callSuper = false;") + w(" id target = __ns_aot_get_target(info, sel, &sel, &callSuper);") + w(" if (target == nil) return false;") + + for i, a in enumerate(self.args): + k = a.kind + if k == "bool": + w(f" BOOL a{i} = NO;") + w(f" if (!__ns_aot_arg_bool(info, {i}, &a{i})) return true;") + elif k in ("int", "uint"): + raw = "int64_t" if k == "int" else "uint64_t" + getter = "__ns_aot_arg_int64" if k == "int" else "__ns_aot_arg_uint64" + w(f" {raw} raw{i} = 0;") + w(f" if (!{getter}(info, {i}, &raw{i})) return true;") + w(f" {a.ctype} a{i} = ({a.ctype})raw{i};") + elif k in ("float", "double"): + w(f" double raw{i} = 0;") + w(f" if (!__ns_aot_arg_double(info, {i}, &raw{i})) return true;") + w(f" {a.ctype} a{i} = ({a.ctype})raw{i};") + elif k == "struct": + var = struct_vars[a.record.cname] + w(f" {a.ctype} a{i};") + w(f" if (!__ns_aot_arg_struct(info, {i}, {var}, &a{i})) return true;") + else: + ctype, init, getter = ARG_GETTERS[k] + w(f" {ctype} a{i} = {init};") + w(f" if (!{getter}(info, {i}, &a{i})) return true;") + + rk = self.ret.kind + if rk == "struct": + w(f" {self.ret.ctype} r;") + elif rk != "void": + w(f" {self.ret.ctype} r = {RETURN_INIT[rk]};") + assign = "" if rk == "void" else "r = " + if rk == "struct": + send_fn = f"AOT_SEND_STRET({self.ret.ctype})" + super_fn = f"AOT_SEND_SUPER_STRET({self.ret.ctype})" + else: + send_fn = "objc_msgSend" + super_fn = "objc_msgSendSuper" + + w(" @try {") + if self.is_static: + w(f" {assign}{_build_msgsend_call(send_fn, '(id)cls', self.ret, self.args)};") + else: + w(" if (callSuper) {") + w(" struct objc_super sup = {target, class_getSuperclass(object_getClass(target))};") + w(f" {assign}{build_super_call(super_fn, self.ret, self.args)};") + w(" } else {") + w(f" {assign}{_build_msgsend_call(send_fn, 'target', self.ret, self.args)};") + w(" }") + w(" } @catch (NSException* e) {") + w(" __ns_aot_throw_exception(info, e);") + w(" return true;") + w(" }") + + if rk == "object": + owned = "true" if "MethodOwnsReturnedCocoaObject" in self.flags() else "false" + marshal = "false" if self.keeps_object_return() else "true" + w(f" __ns_aot_return_object(info, r, {owned}, {marshal});") + elif rk == "struct": + w(f" __ns_aot_return_struct(info, {struct_vars[self.ret.record.cname]}, &r);") + elif rk != "void": + w(f" {RETURN_SETTERS[rk]}") + w(" return true;") + w("}") + return "\n".join(out) + + def keeps_object_return(self): + """Mirrors the generic path, which leaves instancetype and + NSMutableString returns as objects instead of JS primitives.""" + t = self.method["Signature"][0] + while isinstance(t, dict) and t.get("Type") in ("Nullable", "NonNullable"): + t = t.get("InnerType") + if not isinstance(t, dict): + return False + if t.get("Type") == "Instancetype": + return True + return t.get("Type") == "Interface" and t.get("Name") == "NSMutableString" + + +SKIP_FLAGS = ( + ("MethodIsInitializer", "initializer"), + ("MethodHasErrorOutParameter", "NSError** out-parameter"), + ("MethodIsVariadic", "variadic"), + ("MethodIsNullTerminatedVariadic", "variadic"), +) + + +# ARC rejects these in @selector(), and the stubs must compile under ARC. +ARC_FORBIDDEN_SELECTORS = {"retain", "release", "autorelease", "dealloc", "retainCount"} + + +def plan_entry(md, mapper, entry): + """Returns (Stub, None, None) or (None, reason, category).""" + cls, selector, is_static = entry + found = md.find_method(cls, selector, is_static) + if found is None: + if md.find_class(cls) is None and md.find_protocol(cls) is None: + return None, "class not found in metadata", "class not found" + return None, "selector not found in metadata", "selector not found" + method, binder = found + if selector in ARC_FORBIDDEN_SELECTORS: + return None, "memory-management selector", "memory-management selector" + flags = set(method.get("Flags") or []) + for flag, reason in SKIP_FLAGS: + if flag in flags: + return None, reason, reason + signature = method.get("Signature") + if not isinstance(signature, list) or not signature: + raise InputError(f"method {selector} has no Signature") + if len(signature) - 1 != selector.count(":"): + return None, "parameter count does not match selector", "parameter count mismatch" + try: + ret = mapper.resolve(signature[0], True) + args = [mapper.resolve(t, False) for t in signature[1:]] + except Unsupported as e: + return None, f"unsupported: {e}", f"unsupported: {e.category}" + return Stub(binder, selector, is_static, method, ret, args, None), None, None + + +PREAMBLE = """\ +// Generated by scripts/generate-aot.py. Do not edit. +#import +#import +#import +#import + +typedef void (*AOTSendFn)(void); + +// x86_64 returns a struct through a hidden pointer, and needs the _stret entry +// points, when it is classified MEMORY. Size over 16 bytes approximates that +// classification; smaller structs with unaligned or long double fields are not +// modeled. arm64 never uses _stret. +#if defined(__x86_64__) +#define AOT_SEND_STRET(T) (sizeof(T) > 16 ? (AOTSendFn)objc_msgSend_stret : (AOTSendFn)objc_msgSend) +#define AOT_SEND_SUPER_STRET(T) \\ + (sizeof(T) > 16 ? (AOTSendFn)objc_msgSendSuper_stret : (AOTSendFn)objc_msgSendSuper) +#else +#define AOT_SEND_STRET(T) ((AOTSendFn)objc_msgSend) +#define AOT_SEND_SUPER_STRET(T) ((AOTSendFn)objc_msgSendSuper) +#endif +""" + + +def gen_external_registration(stubs): + lines = ['__attribute__((visibility("default")))', + "void __ns_register_aot_calls(void (*reg)(const char*, const char*, bool," + " NSAOTCallHandler)) {"] + for s in stubs: + static_str = "true" if s.is_static else "false" + for cls in s.registrations: + lines.append(f' reg("{cls}", "{s.selector}", {static_str}, {s.name});') + lines.append("}") + return "\n".join(lines) + + +def load_config(path): + try: + with open(path) as f: + config = json.load(f) + except (OSError, ValueError) as e: + raise InputError(f"cannot read config {path}: {e}") + methods = config.get("methods") if isinstance(config, dict) else None + if not isinstance(methods, list): + raise InputError(f"{path}: expected an object with a \"methods\" array") + entries = [] + seen = set() + for i, m in enumerate(methods): + if not isinstance(m, dict): + raise InputError(f"{path}: methods[{i}] is not an object") + cls = m.get("class") + sel = m.get("selector") + is_static = m.get("static", False) + if not isinstance(cls, str) or not cls or not isinstance(sel, str) or not sel: + raise InputError(f"{path}: methods[{i}] needs non-empty \"class\" and \"selector\" strings") + if not isinstance(is_static, bool): + raise InputError(f"{path}: methods[{i}].static must be a boolean") + if not re.match(r"^[A-Za-z_][A-Za-z0-9_:]*$", sel): + raise InputError(f"{path}: methods[{i}] has an invalid selector {sel!r}") + if not re.match(r"^[A-Za-z_][A-Za-z0-9_.$]*$", cls): + raise InputError(f"{path}: methods[{i}] has an invalid class name {cls!r}") + key = (cls, sel, is_static) + if key not in seen: + seen.add(key) + entries.append(key) + return entries + + +def generate(config_path, metadata_dir, output_path, report): + entries = load_config(config_path) + md = Metadata(metadata_dir) + mapper = TypeMapper(md) + + stubs = [] + by_key = {} + outcomes = [] + names = set() + for entry in entries: + stub, reason, category = plan_entry(md, mapper, entry) + if stub is not None: + key = (stub.cls, stub.selector, stub.is_static) + if key in by_key: + stub = by_key[key] + else: + base = method_stub_name(stub.cls, stub.selector, stub.is_static) + name = base + n = 2 + while name in names: + name = f"{base}_{n}" + n += 1 + names.add(name) + stub.name = name + by_key[key] = stub + stubs.append(stub) + # The runtime binds at the declaring class; the config name is + # registered too so a lookup keyed on it also resolves. + for cls in (stub.cls, entry[0]): + if cls not in stub.registrations: + stub.registrations.append(cls) + reason = None + category = stub.cls + outcomes.append((entry, reason, category)) + + parts = [PREAMBLE] + typedefs = mapper.emit_typedefs() + if typedefs: + parts.append("\n".join(typedefs) + "\n") + for s in stubs: + parts.append(s.emit() + "\n") + parts.append(gen_external_registration(stubs) + "\n") + + out_dir = os.path.dirname(os.path.abspath(output_path)) + os.makedirs(out_dir, exist_ok=True) + with open(output_path, "w") as f: + f.write("\n".join(parts)) + + skipped = [o for o in outcomes if o[1] is not None] + for (cls, sel, is_static), reason, detail in outcomes if report else skipped: + prefix = "+" if is_static else "-" + if reason is not None: + status = f"skipped: {reason}" + elif detail == cls: + status = "generated" + else: + status = f"generated, declared on {detail}" + print(f"{prefix}[{cls} {sel}] {status}") + print(f"wrote {output_path}") + print(f"generated {len(outcomes) - len(skipped)} entries as {len(stubs)} stubs, skipped {len(skipped)}") + by_category = {} + for _entry, _reason, category in skipped: + by_category[category] = by_category.get(category, 0) + 1 + for category, n in sorted(by_category.items(), key=lambda kv: (-kv[1], kv[0])): + print(f" {n:4d} {category}") + + +def main(argv): + parser = argparse.ArgumentParser( + description="Generate NativeScript AOT call stubs from an AOT config and metadata JSON.") + parser.add_argument("config", help="AOT config JSON ({\"methods\": [...]})") + parser.add_argument("-m", "--metadata", required=True, + help="directory of .json files from the metadata generator's -output-json") + parser.add_argument("-o", "--output", default="NativeScriptAOTStubs.m", + help="output Objective-C file (default: %(default)s)") + parser.add_argument("--report", action="store_true", + help="list the outcome of every config entry") + args = parser.parse_args(argv) + try: + generate(args.config, args.metadata, args.output, args.report) + except InputError as e: + print(f"error: {e}", file=sys.stderr) + return 2 + return 0 + + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:])) From 9b26314fbbb5ee989e37b51a313d185172344595 Mon Sep 17 00:00:00 2001 From: Eduardo Speroni Date: Thu, 8 Oct 2026 12:46:33 -0300 Subject: [PATCH 3/4] feat(runtime): bind app-compiled AOT call stubs in place of libffi dispatch NativeScriptAOT.h is a public C bridge for stubs generated per (class, selector): the runtime discovers the app's registrar with dlsym at startup, and when a prototype template is built it binds a registered stub as the V8 callback for the method or property getter, looking the member up by class name (walking the ObjC superclass chain), SEL and static-ness. A stub resolves its receiver, selector (swizzled when the generic path would) and super dispatch through the bridge, converts arguments and results with the bound method's metadata through the same Interop code as the generic path, catches NSException into the same JS error, and declines to the generic callback for anything it does not model (argument-count mismatch, alloc receivers). callSuper is cached per Class in a StateFor slot that clears when ClassPrototypes grows. The id and Class return branches of Interop::GetResult and the NSException-to-JS-Error conversion are extracted into helpers shared by both paths. __native_call_profiler (lazy global) records generic-path calls, emits aot-config entries for the hottest ones, and reports served/declined stub counts while profiling. TestRunner compiles generated stubs for TestFixtures, NSObject and UIScreen (TestRunner/AOT) and AOTDirectCallsTests asserts served/declined counts per call. Release TestRunner, iPhone 16 Pro simulator: screen.scale 173 -> 125 ns, UIScreen.mainScreen 158 -> 119 ns, UIScreen.mainScreen.scale 335 -> 232 ns; struct returns unchanged. Suite 1776/0 incl. the ASan lane; 1742/0 with no stubs registered. --- NativeScript/runtime/AOTCalls.h | 60 ++ NativeScript/runtime/Interop.h | 29 +- NativeScript/runtime/Interop.mm | 259 ++--- NativeScript/runtime/LazyGlobals.cpp | 2 + NativeScript/runtime/MetadataBuilder.h | 12 +- NativeScript/runtime/MetadataBuilder.mm | 75 +- NativeScript/runtime/MethodCallProfiler.h | 45 + NativeScript/runtime/MethodCallProfiler.mm | 270 ++++++ NativeScript/runtime/NativeScriptAOT.h | 120 +++ NativeScript/runtime/NativeScriptAOTBridge.mm | 664 +++++++++++++ NativeScript/runtime/Runtime.mm | 2 + TestFixtures/exported-symbols.txt | 1 + TestRunner/AOT/NativeScriptAOTStubs.m | 886 ++++++++++++++++++ TestRunner/AOT/README.md | 13 + TestRunner/AOT/aot-config.json | 53 ++ TestRunner/app/tests/AOTDirectCallsTests.js | 378 ++++++++ TestRunner/app/tests/index.js | 1 + v8ios.xcodeproj/project.pbxproj | 32 + 18 files changed, 2768 insertions(+), 134 deletions(-) create mode 100644 NativeScript/runtime/AOTCalls.h create mode 100644 NativeScript/runtime/MethodCallProfiler.h create mode 100644 NativeScript/runtime/MethodCallProfiler.mm create mode 100644 NativeScript/runtime/NativeScriptAOT.h create mode 100644 NativeScript/runtime/NativeScriptAOTBridge.mm create mode 100644 TestRunner/AOT/NativeScriptAOTStubs.m create mode 100644 TestRunner/AOT/README.md create mode 100644 TestRunner/AOT/aot-config.json create mode 100644 TestRunner/app/tests/AOTDirectCallsTests.js diff --git a/NativeScript/runtime/AOTCalls.h b/NativeScript/runtime/AOTCalls.h new file mode 100644 index 000000000..336c2d4bf --- /dev/null +++ b/NativeScript/runtime/AOTCalls.h @@ -0,0 +1,60 @@ +#ifndef AOTCalls_h +#define AOTCalls_h + +#include + +#include +#include + +#include "Common.h" +#include "NativeScriptAOT.h" + +// Binding of app-compiled AOT call stubs (see NativeScriptAOT.h) to the +// prototype templates built by MetadataBuilder. +namespace tns::aot { + +struct ClassStubs; + +struct Binding { + // Installed as the FunctionTemplate callback in place of the generic one. + v8::FunctionCallback callback = nullptr; + // Stored in the site's CacheItem::userData_, where the callback reads it. + void* handler = nullptr; +}; + +// Registers the stubs exported by the app binary, if any. Every runtime calls +// it before building its first prototype template; later calls are no-ops. +void DiscoverExternalStubs(); + +// The function handed to the app's __ns_register_aot_calls. Registrations +// outside that call are ignored. +void Register(const char* className, const char* selector, bool isStatic, + NSAOTCallHandler handler); + +// The stubs that apply to a class's template: those registered under its own +// name, then under each ObjC superclass's name, nearest first. A class's +// template can re-register a member it inherits (protocol members are added +// to every adopting class), and the own member shadows the ancestor's, so a +// stub registered for the ancestor must bind there too; it messages the same +// selector, so it is valid for the descendant. +struct StubScope { + std::vector classes; + bool empty() const { return classes.empty(); } +}; + +// Callers resolve it once per Register* pass and look members up with it. +// Empty, without touching the ObjC runtime, when no stubs are registered. +StubScope FindStubScope(const char* className); + +bool FindInstanceMethod(const StubScope& scope, SEL selector, Binding& out); +bool FindStaticMethod(const StubScope& scope, SEL selector, Binding& out); +bool FindInstanceGetter(const StubScope& scope, SEL getter, Binding& out); +bool FindStaticGetter(const StubScope& scope, SEL getter, Binding& out); + +// Counted only while MethodCallProfiler is enabled. +uint64_t ServedCallCount(); +uint64_t DeclinedCallCount(); + +} // namespace tns::aot + +#endif /* AOTCalls_h */ diff --git a/NativeScript/runtime/Interop.h b/NativeScript/runtime/Interop.h index 85a556f15..daeca55a9 100644 --- a/NativeScript/runtime/Interop.h +++ b/NativeScript/runtime/Interop.h @@ -96,6 +96,29 @@ class Interop { std::shared_ptr> parentStruct = nullptr, bool isStructMember = false, bool ownsReturnedObject = false, bool returnsUnmanaged = false, bool isInitializer = false); + // Converts an object returned by native code to its JS value. typeEncoding + // is the declared return type and may be null; without it NSMutableString + // returns are marshalled like NSString, NSProxy results get no metadata + // from the declared type, and id conformances are not attached. + static v8::Local ObjectToJsValue(v8::Local context, + id value, + const TypeEncoding* typeEncoding, + bool marshalToPrimitive, + bool ownsReturnedObject, + bool isInitializer); + static v8::Local ClassToJsValue(v8::Local context, + Class value); + // Builds the JS Error for an NSException raised by a native call: name and + // reason copied over, the exception itself attached as `nativeException`. + // Returns an empty handle if no Error could be created. `message` receives + // the text the error was created with. + static v8::Local NSExceptionToJsError( + v8::Local context, id exception, std::string& message); + static v8::Local StructToValue( + v8::Local context, void* result, const StructInfo& structInfo, + std::shared_ptr> parentStruct); + static SEL GetSwizzledMethodSelector(SEL selector); + static bool IsNumbericType(BinaryTypeEncodingType type); static void SetStructPropertyValue(v8::Local context, StructWrapper* wrapper, const StructField& field, @@ -160,20 +183,14 @@ class Interop { static bool isRefTypeEqual(const TypeEncoding* typeEncoding, const char* clazz); static v8::Local ToArray(v8::Local object); - static v8::Local StructToValue( - v8::Local context, void* result, - const StructInfo& structInfo, - std::shared_ptr> parentStruct); static const TypeEncoding* CreateEncoding(BinaryTypeEncodingType type); static v8::Local HandleOf(v8::Local context, v8::Local value); static v8::Local CallFunctionInternal(MethodCall& methodCall); - static bool IsNumbericType(BinaryTypeEncodingType type); static v8::Local GetInteropType(v8::Local context, BinaryTypeEncodingType type); static std::vector GetAdditionalProtocols( const TypeEncoding* typeEncoding); - static SEL GetSwizzledMethodSelector(SEL selector); template static inline void SetValue(void* dest, T value) { diff --git a/NativeScript/runtime/Interop.mm b/NativeScript/runtime/Interop.mm index d35a05b27..6892449c8 100644 --- a/NativeScript/runtime/Interop.mm +++ b/NativeScript/runtime/Interop.mm @@ -1059,37 +1059,10 @@ inline bool isBool() { } if (typeEncoding->type == BinaryTypeEncodingType::ClassEncoding) { - Class result = call->GetResult(); - if (result == nil) { - return Null(isolate); - } - - std::shared_ptr cache = Caches::Get(isolate); - while (true) { - const char* name = class_getName(result); - - const Meta* meta = ArgConverter::GetMeta(name); - if (meta != nullptr && - (meta->type() == MetaType::Interface || meta->type() == MetaType::ProtocolType)) { - const BaseClassMeta* baseMeta = static_cast(meta); - Class knownClass = meta->type() == MetaType::Interface ? objc_getClass(meta->name()) : nil; - KnownUnknownClassPair pair(knownClass); - std::vector emptyProtocols; - cache->ObjectCtorInitializer(context, baseMeta, pair, emptyProtocols); - } - - auto it = cache->CtorFuncs.find(name); - if (it != cache->CtorFuncs.end()) { - return it->second->Get(isolate); - } - - result = class_getSuperclass(result); - if (!result) { - break; - } + Local klass = Interop::ClassToJsValue(context, call->GetResult()); + if (!klass.IsEmpty()) { + return klass; } - - tns::Assert(false, isolate); } if (typeEncoding->type == BinaryTypeEncodingType::BlockEncoding) { @@ -1240,88 +1213,167 @@ inline bool isBool() { if (typeEncoding->type == BinaryTypeEncodingType::InterfaceDeclarationReference || typeEncoding->type == BinaryTypeEncodingType::IdEncoding || typeEncoding->type == BinaryTypeEncodingType::InstanceTypeEncoding) { - id result = call->GetResult(); + return Interop::ObjectToJsValue(context, call->GetResult(), typeEncoding, + marshalToPrimitive, ownsReturnedObject, isInitializer); + } - if (result == nil) { - return Null(isolate); - } + return Interop::GetPrimitiveReturnType(context, typeEncoding->type, call); +} - if (marshalToPrimitive && result == [NSNull null]) { - return Null(isolate); - } +Local Interop::ObjectToJsValue(Local context, id result, + const TypeEncoding* typeEncoding, bool marshalToPrimitive, + bool ownsReturnedObject, bool isInitializer) { + Isolate* isolate = v8::Isolate::GetCurrent(); - if ([result isKindOfClass:[@YES class]]) { - return v8::Boolean::New(isolate, [result boolValue]); - } + if (result == nil) { + return Null(isolate); + } - if (marshalToPrimitive && [result isKindOfClass:[NSDate class]]) { - double time = [result timeIntervalSince1970] * 1000.0; - Local date; - if (Date::New(context, time).ToLocal(&date)) { - return date; - } + if (marshalToPrimitive && result == [NSNull null]) { + return Null(isolate); + } - std::ostringstream errorStream; - errorStream << "Unable to convert " << [result description] << " to a Date object"; - std::string errorMessage = errorStream.str(); - Local error = Exception::Error(tns::ToV8String(isolate, errorMessage)); - isolate->ThrowException(error); - return Local(); + if ([result isKindOfClass:[@YES class]]) { + return v8::Boolean::New(isolate, [result boolValue]); + } + + if (marshalToPrimitive && [result isKindOfClass:[NSDate class]]) { + double time = [result timeIntervalSince1970] * 1000.0; + Local date; + if (Date::New(context, time).ToLocal(&date)) { + return date; } - if (marshalToPrimitive && [result isKindOfClass:[NSString class]]) { - if (typeEncoding->type == BinaryTypeEncodingType::InterfaceDeclarationReference) { - const char* returnClassName = typeEncoding->details.declarationReference.name.valuePtr(); - Class returnClass = objc_getClass(returnClassName); - if (returnClass != nil && returnClass == [NSMutableString class]) { - marshalToPrimitive = false; - } - } + std::ostringstream errorStream; + errorStream << "Unable to convert " << [result description] << " to a Date object"; + std::string errorMessage = errorStream.str(); + Local error = Exception::Error(tns::ToV8String(isolate, errorMessage)); + isolate->ThrowException(error); + return Local(); + } - if (marshalToPrimitive) { - // Convert NSString instances to javascript strings for all instance method calls - return tns::ToV8String(isolate, result); + if (marshalToPrimitive && [result isKindOfClass:[NSString class]]) { + if (typeEncoding != nullptr && + typeEncoding->type == BinaryTypeEncodingType::InterfaceDeclarationReference) { + const char* returnClassName = typeEncoding->details.declarationReference.name.valuePtr(); + Class returnClass = objc_getClass(returnClassName); + if (returnClass != nil && returnClass == [NSMutableString class]) { + marshalToPrimitive = false; } } - if (marshalToPrimitive && [result isKindOfClass:[NSNumber class]] && - ![result isKindOfClass:[NSDecimalNumber class]]) { - // Convert NSNumber instances to javascript numbers for all instance method calls - double value = [result doubleValue]; - return Number::New(isolate, value); + if (marshalToPrimitive) { + // Convert NSString instances to javascript strings for all instance method calls + return tns::ToV8String(isolate, result); } + } - auto cache = Caches::Get(isolate); - auto poInstance = ArgConverter::FindCachedInstance(isolate, cache, result); - if (poInstance != nullptr) { - return poInstance->Get(isolate); - } + if (marshalToPrimitive && [result isKindOfClass:[NSNumber class]] && + ![result isKindOfClass:[NSDecimalNumber class]]) { + // Convert NSNumber instances to javascript numbers for all instance method calls + double value = [result doubleValue]; + return Number::New(isolate, value); + } + + auto cache = Caches::Get(isolate); + auto poInstance = ArgConverter::FindCachedInstance(isolate, cache, result); + if (poInstance != nullptr) { + return poInstance->Get(isolate); + } - // For NSProxy we will try to read the metadata from - // typeEncoding->details.interfaceDeclarationReference.name because class_getSuperclass will - // directly return NSProxy and thus missing to attach all instance members - const TypeEncoding* te = [result isProxy] ? typeEncoding : nullptr; + // For NSProxy we will try to read the metadata from + // typeEncoding->details.interfaceDeclarationReference.name because class_getSuperclass will + // directly return NSProxy and thus missing to attach all instance members + const TypeEncoding* te = [result isProxy] ? typeEncoding : nullptr; - ObjCDataWrapper* wrapper = new ObjCDataWrapper(result, te); - std::vector additionalProtocols = Interop::GetAdditionalProtocols(typeEncoding); - Local jsResult = - ArgConverter::ConvertArgument(context, wrapper, false, additionalProtocols); + ObjCDataWrapper* wrapper = new ObjCDataWrapper(result, te); + std::vector additionalProtocols = typeEncoding != nullptr + ? Interop::GetAdditionalProtocols(typeEncoding) + : std::vector(); + Local jsResult = + ArgConverter::ConvertArgument(context, wrapper, false, additionalProtocols); - if (ownsReturnedObject || isInitializer) { - [result release]; + if (ownsReturnedObject || isInitializer) { + [result release]; + } + + if ([result isKindOfClass:[NSArray class]]) { + // attach Symbol.iterator to the instance + SymbolIterator::Set(context, jsResult); + } + + tns::DeleteWrapperIfUnused(isolate, jsResult, wrapper); + + return jsResult; +} + +Local Interop::ClassToJsValue(Local context, Class result) { + Isolate* isolate = v8::Isolate::GetCurrent(); + if (result == nil) { + return Null(isolate); + } + + std::shared_ptr cache = Caches::Get(isolate); + while (true) { + const char* name = class_getName(result); + + const Meta* meta = ArgConverter::GetMeta(name); + if (meta != nullptr && + (meta->type() == MetaType::Interface || meta->type() == MetaType::ProtocolType)) { + const BaseClassMeta* baseMeta = static_cast(meta); + Class knownClass = meta->type() == MetaType::Interface ? objc_getClass(meta->name()) : nil; + KnownUnknownClassPair pair(knownClass); + std::vector emptyProtocols; + cache->ObjectCtorInitializer(context, baseMeta, pair, emptyProtocols); + } + + auto it = cache->CtorFuncs.find(name); + if (it != cache->CtorFuncs.end()) { + return it->second->Get(isolate); } - if ([result isKindOfClass:[NSArray class]]) { - // attach Symbol.iterator to the instance - SymbolIterator::Set(context, jsResult); + result = class_getSuperclass(result); + if (!result) { + break; } + } - tns::DeleteWrapperIfUnused(isolate, jsResult, wrapper); + tns::Assert(false, isolate); + return Local(); +} - return jsResult; +Local Interop::NSExceptionToJsError(Local context, id exception, + std::string& message) { + Isolate* isolate = v8::Isolate::GetCurrent(); + NSException* e = (NSException*)exception; + + NSString* nsName = [e name]; + NSString* nsReason = [e reason]; + Local messageV8 = tns::ToV8String(isolate, nsReason ?: (nsName ?: @"NSException")); + message = tns::ToString(isolate, messageV8); + + Local jsErrVal = Exception::Error(messageV8); + if (jsErrVal.IsEmpty() || !jsErrVal->IsObject()) { + return Local(); } - return Interop::GetPrimitiveReturnType(context, typeEncoding->type, call); + Local jsErrObj = jsErrVal.As(); + if (nsName != nil) { + jsErrObj->Set(context, tns::ToV8String(isolate, "name"), tns::ToV8String(isolate, nsName)) + .FromMaybe(false); + } + if (nsReason != nil) { + jsErrObj->Set(context, tns::ToV8String(isolate, "message"), tns::ToV8String(isolate, nsReason)) + .FromMaybe(false); + } + + ObjCDataWrapper* wrapper = new ObjCDataWrapper((id)e); + Local nativeWrapper = + ArgConverter::CreateJsWrapper(context, wrapper, Local(), true); + jsErrObj->Set(context, tns::ToV8String(isolate, "nativeException"), nativeWrapper) + .FromMaybe(false); + + return jsErrObj; } Local Interop::GetPrimitiveReturnType(Local context, BinaryTypeEncodingType type, @@ -1714,35 +1766,14 @@ inline bool isBool() { Isolate* isolate = v8::Isolate::GetCurrent(); Local context = isolate->GetCurrentContext(); - NSString* nsName = [e name]; - NSString* nsReason = [e reason]; - Local messageV8 = tns::ToV8String(isolate, nsReason ?: (nsName ?: @"NSException")); - std::string message = tns::ToString(isolate, messageV8); - - Local jsErrVal = Exception::Error(messageV8); - if (jsErrVal.IsEmpty() || !jsErrVal->IsObject()) { + std::string message; + Local jsError = Interop::NSExceptionToJsError(context, e, message); + if (jsError.IsEmpty()) { // Fallback: keep the description-only behavior if Error creation fails. throw NativeScriptException(tns::ToString(isolate, [e description])); } - Local jsErrObj = jsErrVal.As(); - if (nsName != nil) { - jsErrObj->Set(context, tns::ToV8String(isolate, "name"), tns::ToV8String(isolate, nsName)) - .FromMaybe(false); - } - if (nsReason != nil) { - jsErrObj - ->Set(context, tns::ToV8String(isolate, "message"), tns::ToV8String(isolate, nsReason)) - .FromMaybe(false); - } - - ObjCDataWrapper* wrapper = new ObjCDataWrapper((id)e); - Local nativeWrapper = - ArgConverter::CreateJsWrapper(context, wrapper, Local(), true); - jsErrObj->Set(context, tns::ToV8String(isolate, "nativeException"), nativeWrapper) - .FromMaybe(false); - - throw NativeScriptException(isolate, jsErrObj.As(), message); + throw NativeScriptException(isolate, jsError, message); } if (errorRef != nullptr) { diff --git a/NativeScript/runtime/LazyGlobals.cpp b/NativeScript/runtime/LazyGlobals.cpp index 81ac7dd94..4ed315fe1 100644 --- a/NativeScript/runtime/LazyGlobals.cpp +++ b/NativeScript/runtime/LazyGlobals.cpp @@ -4,6 +4,7 @@ #include "BuiltinLoader.h" #include "Helpers.h" #include "Messaging.h" +#include "MethodCallProfiler.h" #include "StructuredSerialization.h" #include "TextEncoding.h" @@ -45,6 +46,7 @@ constexpr LazyGlobalEntry kLazyGlobals[] = { {"MessageChannel", "MessageChannel", messaging::GetMessageChannelExports}, {"BroadcastChannel", "BroadcastChannel", messaging::GetBroadcastChannelExports}, + {"__native_call_profiler", "profiler", MethodCallProfiler::GetExports}, }; void LazyGlobalGetter(Local property, diff --git a/NativeScript/runtime/MetadataBuilder.h b/NativeScript/runtime/MetadataBuilder.h index e8b3ae82c..7b13817f5 100644 --- a/NativeScript/runtime/MetadataBuilder.h +++ b/NativeScript/runtime/MetadataBuilder.h @@ -12,7 +12,13 @@ namespace tns { +namespace aot { +struct Trampolines; +} + class MetadataBuilder { + friend struct aot::Trampolines; + public: static void RegisterConstantsOnGlobalObject( v8::Isolate* isolate, v8::Local globalTemplate, @@ -80,7 +86,8 @@ class MetadataBuilder { static void RegisterInstanceMethods( v8::Local context, v8::Local ctorFuncTemplate, - const BaseClassMeta* meta, KnownUnknownClassPair pair, + const BaseClassMeta* meta, const char* className, + KnownUnknownClassPair pair, robin_hood::unordered_map& names); static void RegisterInstanceProperties( v8::Local context, @@ -102,7 +109,8 @@ class MetadataBuilder { robin_hood::unordered_map& names); static void RegisterStaticMethods( v8::Local context, v8::Local ctorFunc, - const BaseClassMeta* meta, KnownUnknownClassPair pair, + const BaseClassMeta* meta, const char* className, + KnownUnknownClassPair pair, robin_hood::unordered_map& names); static void RegisterStaticProperties( v8::Local context, v8::Local ctorFunc, diff --git a/NativeScript/runtime/MetadataBuilder.mm b/NativeScript/runtime/MetadataBuilder.mm index fd4728560..950816b41 100644 --- a/NativeScript/runtime/MetadataBuilder.mm +++ b/NativeScript/runtime/MetadataBuilder.mm @@ -1,5 +1,6 @@ #include "MetadataBuilder.h" #include +#include "AOTCalls.h" #include "ArgConverter.h" #include "Caches.h" #include "Constants.h" @@ -7,6 +8,7 @@ #include "InlineFunctions.h" #include "Interop.h" #include "LazyGlobals.h" +#include "MethodCallProfiler.h" #include "NativeScriptException.h" #include "ObjectManager.h" #include "Runtime.h" @@ -456,7 +458,8 @@ throw NativeScriptException( MetadataBuilder::RegisterInstanceProperties(context, ctorFuncTemplate, meta, meta->name(), pair, instanceMembers); - MetadataBuilder::RegisterInstanceMethods(context, ctorFuncTemplate, meta, pair, instanceMembers); + MetadataBuilder::RegisterInstanceMethods(context, ctorFuncTemplate, meta, meta->name(), pair, + instanceMembers); MetadataBuilder::RegisterInstanceProtocols(context, ctorFuncTemplate, meta, meta->name(), pair, instanceMembers); MetadataBuilder::RegisterAdditionalProtocols(context, ctorFuncTemplate, pair, additionalProtocols, @@ -515,7 +518,8 @@ NamedPropertyHandlerConfiguration config(nullptr, MetadataBuilder::SwizzledInsta } } - MetadataBuilder::RegisterStaticMethods(context, ctorFunc, meta, pair, staticMembers); + MetadataBuilder::RegisterStaticMethods(context, ctorFunc, meta, meta->name(), pair, + staticMembers); MetadataBuilder::RegisterStaticProperties(context, ctorFunc, meta, meta->name(), pair, staticMembers); MetadataBuilder::RegisterStaticProtocols(context, ctorFunc, meta, meta->name(), pair, @@ -566,9 +570,11 @@ NamedPropertyHandlerConfiguration config(nullptr, MetadataBuilder::SwizzledInsta void MetadataBuilder::RegisterInstanceMethods( Local context, Local ctorFuncTemplate, const BaseClassMeta* meta, - KnownUnknownClassPair pair, robin_hood::unordered_map& names) { + const char* className, KnownUnknownClassPair pair, + robin_hood::unordered_map& names) { Isolate* isolate = v8::Isolate::GetCurrent(); Local proto = ctorFuncTemplate->PrototypeTemplate(); + const aot::StubScope stubs = aot::FindStubScope(className); for (auto it = meta->instanceMethods->begin(); it != meta->instanceMethods->end(); it++) { const MethodMeta* methodMeta = (*it).valuePtr(); @@ -582,8 +588,14 @@ NamedPropertyHandlerConfiguration config(nullptr, MetadataBuilder::SwizzledInsta CacheItem* item = new CacheItem(methodMeta, meta->name()); Caches::Get(isolate)->registerCacheBoundObject(item); Local ext = External::New(isolate, item, v8::kExternalPointerTypeTagDefault); + FunctionCallback callback = MethodCallback; + aot::Binding binding; + if (!stubs.empty() && aot::FindInstanceMethod(stubs, methodMeta->selector(), binding)) { + callback = binding.callback; + item->userData_ = binding.handler; + } Local instanceMethodTemplate = - FunctionTemplate::New(isolate, MethodCallback, ext); + FunctionTemplate::New(isolate, callback, ext); proto->Set(tns::ToV8String(isolate, methodMeta->jsName()), instanceMethodTemplate); names.emplace(methodName, 0); } @@ -596,6 +608,7 @@ NamedPropertyHandlerConfiguration config(nullptr, MetadataBuilder::SwizzledInsta robin_hood::unordered_map& names) { Isolate* isolate = v8::Isolate::GetCurrent(); Local proto = ctorFuncTemplate->PrototypeTemplate(); + const aot::StubScope stubs = aot::FindStubScope(className.c_str()); for (auto it = meta->instanceProps->begin(); it != meta->instanceProps->end(); it++) { const PropertyMeta* propMeta = (*it).valuePtr(); @@ -617,7 +630,14 @@ NamedPropertyHandlerConfiguration config(nullptr, MetadataBuilder::SwizzledInsta CacheItem* item = new CacheItem(propMeta, className); Caches::Get(isolate)->registerCacheBoundObject(item); Local ext = External::New(isolate, item, v8::kExternalPointerTypeTagDefault); - Local getter = FunctionTemplate::New(isolate, PropertyGetterCallback, ext); + FunctionCallback getterCallback = PropertyGetterCallback; + aot::Binding binding; + if (!stubs.empty() && propMeta->hasGetter() && + aot::FindInstanceGetter(stubs, propMeta->getter()->selector(), binding)) { + getterCallback = binding.callback; + item->userData_ = binding.handler; + } + Local getter = FunctionTemplate::New(isolate, getterCallback, ext); Local setter = FunctionTemplate::New(isolate, PropertySetterCallback, ext); proto->SetAccessorProperty(tns::ToV8String(isolate, propMeta->jsName()), getter, setter, PropertyAttribute::None); @@ -631,7 +651,8 @@ NamedPropertyHandlerConfiguration config(nullptr, MetadataBuilder::SwizzledInsta const std::string className, KnownUnknownClassPair pair, robin_hood::unordered_map& names) { if (meta->type() == MetaType::ProtocolType) { - MetadataBuilder::RegisterInstanceMethods(context, ctorFuncTemplate, meta, pair, names); + MetadataBuilder::RegisterInstanceMethods(context, ctorFuncTemplate, meta, className.c_str(), + pair, names); MetadataBuilder::RegisterInstanceProperties(context, ctorFuncTemplate, meta, className, pair, names); } @@ -655,7 +676,8 @@ NamedPropertyHandlerConfiguration config(nullptr, MetadataBuilder::SwizzledInsta const Meta* meta = ArgConverter::GetMeta(protocolName.c_str()); if (meta != nullptr) { const BaseClassMeta* baseMeta = static_cast(meta); - MetadataBuilder::RegisterInstanceMethods(context, ctorFuncTemplate, baseMeta, pair, names); + MetadataBuilder::RegisterInstanceMethods(context, ctorFuncTemplate, baseMeta, + baseMeta->name(), pair, names); MetadataBuilder::RegisterInstanceProperties(context, ctorFuncTemplate, baseMeta, baseMeta->name(), pair, names); @@ -673,8 +695,10 @@ NamedPropertyHandlerConfiguration config(nullptr, MetadataBuilder::SwizzledInsta void MetadataBuilder::RegisterStaticMethods( Local context, Local ctorFunc, const BaseClassMeta* meta, - KnownUnknownClassPair pair, robin_hood::unordered_map& names) { + const char* className, KnownUnknownClassPair pair, + robin_hood::unordered_map& names) { Isolate* isolate = v8::Isolate::GetCurrent(); + const aot::StubScope stubs = aot::FindStubScope(className); for (auto it = meta->staticMethods->begin(); it != meta->staticMethods->end(); it++) { const MethodMeta* methodMeta = (*it).valuePtr(); if (!methodMeta->isAvailableInClasses(pair, true)) { @@ -687,8 +711,13 @@ NamedPropertyHandlerConfiguration config(nullptr, MetadataBuilder::SwizzledInsta CacheItem* item = new CacheItem(methodMeta, meta->name()); Caches::Get(isolate)->registerCacheBoundObject(item); Local ext = External::New(isolate, item, v8::kExternalPointerTypeTagDefault); - Local staticMethodTemplate = - FunctionTemplate::New(isolate, MethodCallback, ext); + FunctionCallback callback = MethodCallback; + aot::Binding binding; + if (!stubs.empty() && aot::FindStaticMethod(stubs, methodMeta->selector(), binding)) { + callback = binding.callback; + item->userData_ = binding.handler; + } + Local staticMethodTemplate = FunctionTemplate::New(isolate, callback, ext); Local staticMethod; if (!staticMethodTemplate->GetFunction(context).ToLocal(&staticMethod)) { tns::Assert(false, isolate); @@ -713,6 +742,7 @@ NamedPropertyHandlerConfiguration config(nullptr, MetadataBuilder::SwizzledInsta const std::string className, KnownUnknownClassPair pair, robin_hood::unordered_map& names) { Isolate* isolate = v8::Isolate::GetCurrent(); + const aot::StubScope stubs = aot::FindStubScope(className.c_str()); for (auto it = meta->staticProps->begin(); it != meta->staticProps->end(); it++) { const PropertyMeta* propMeta = (*it).valuePtr(); @@ -736,6 +766,14 @@ NamedPropertyHandlerConfiguration config(nullptr, MetadataBuilder::SwizzledInsta Caches::Get(isolate)->registerCacheBoundObject(item); Local ext = External::New(isolate, item, v8::kExternalPointerTypeTagDefault); + FunctionCallback getterCallback = PropertyNameGetterCallback; + aot::Binding binding; + if (!stubs.empty() && propMeta->hasGetter() && + aot::FindStaticGetter(stubs, propMeta->getter()->selector(), binding)) { + getterCallback = binding.callback; + item->userData_ = binding.handler; + } + Local propName = tns::ToV8String(isolate, propMeta->jsName()); // A real accessor pair, not SetNativeDataProperty: a derived class // inherits this from the base constructor, and assigning through the @@ -743,7 +781,7 @@ NamedPropertyHandlerConfiguration config(nullptr, MetadataBuilder::SwizzledInsta // data property. Local propGetter; Local propSetter; - if (!FunctionTemplate::New(isolate, PropertyNameGetterCallback, ext) + if (!FunctionTemplate::New(isolate, getterCallback, ext) ->GetFunction(context) .ToLocal(&propGetter) || !FunctionTemplate::New(isolate, PropertyNameSetterCallback, ext) @@ -763,7 +801,7 @@ NamedPropertyHandlerConfiguration config(nullptr, MetadataBuilder::SwizzledInsta const std::string className, KnownUnknownClassPair pair, robin_hood::unordered_map& names) { if (meta->type() == MetaType::ProtocolType) { - MetadataBuilder::RegisterStaticMethods(context, ctorFunc, meta, pair, names); + MetadataBuilder::RegisterStaticMethods(context, ctorFunc, meta, className.c_str(), pair, names); MetadataBuilder::RegisterStaticProperties(context, ctorFunc, meta, className, pair, names); } @@ -843,6 +881,11 @@ NamedPropertyHandlerConfiguration config(nullptr, MetadataBuilder::SwizzledInsta } } + if (MethodCallProfiler::IsEnabled()) { + MethodCallProfiler::RecordCall(klass ? std::string(class_getName(klass)) : item->className_, + item->meta_, !instanceMethod); + } + Local context = isolate->GetCurrentContext(); Local result = instanceMethod @@ -870,6 +913,10 @@ NamedPropertyHandlerConfiguration config(nullptr, MetadataBuilder::SwizzledInsta return; } + if (MethodCallProfiler::IsEnabled()) { + MethodCallProfiler::RecordCall(item->className_, item->meta_->getter()); + } + V8EmptyValueArgs args; Local context = isolate->GetCurrentContext(); Local result = MetadataBuilder::InvokeMethod(context, item->meta_->getter(), receiver, @@ -908,6 +955,10 @@ NamedPropertyHandlerConfiguration config(nullptr, MetadataBuilder::SwizzledInsta return; } + if (MethodCallProfiler::IsEnabled()) { + MethodCallProfiler::RecordCall(item->className_, item->meta_->getter(), true); + } + V8EmptyValueArgs args; Local context = isolate->GetCurrentContext(); Local result = MetadataBuilder::InvokeMethod( diff --git a/NativeScript/runtime/MethodCallProfiler.h b/NativeScript/runtime/MethodCallProfiler.h new file mode 100644 index 000000000..bc7abd368 --- /dev/null +++ b/NativeScript/runtime/MethodCallProfiler.h @@ -0,0 +1,45 @@ +#ifndef MethodCallProfiler_h +#define MethodCallProfiler_h + +#include +#include + +#include "Common.h" +#include "Metadata.h" + +namespace tns { + +// Counts native method and property-getter calls that take the generic +// (libffi) path. Exposed to JS as the lazy global `__native_call_profiler`: +// start() / stop() / reset() +// report(topN = 50) human-readable top calls +// aotConfig(topN = 50) JSON candidates for AOT stubs +// aotStats() {served, declined} AOT stub calls, counted only +// between start() and stop() +class MethodCallProfiler { + public: + static inline bool IsEnabled() { + return enabled_.load(std::memory_order_relaxed); + } + static void Enable(); + static void Disable(); + static void Reset(); + static void RecordCall(const std::string& className, const MethodMeta* meta, + bool isStatic = false); + // LazyGlobals exports accessor: `{ profiler: }`. + static v8::MaybeLocal GetExports(v8::Local context); + + private: + static std::atomic enabled_; + + static void JSStart(const v8::FunctionCallbackInfo& info); + static void JSStop(const v8::FunctionCallbackInfo& info); + static void JSReset(const v8::FunctionCallbackInfo& info); + static void JSReport(const v8::FunctionCallbackInfo& info); + static void JSAOTConfig(const v8::FunctionCallbackInfo& info); + static void JSAOTStats(const v8::FunctionCallbackInfo& info); +}; + +} // namespace tns + +#endif /* MethodCallProfiler_h */ diff --git a/NativeScript/runtime/MethodCallProfiler.mm b/NativeScript/runtime/MethodCallProfiler.mm new file mode 100644 index 000000000..a42534ca2 --- /dev/null +++ b/NativeScript/runtime/MethodCallProfiler.mm @@ -0,0 +1,270 @@ +#include "MethodCallProfiler.h" + +#include +#include +#include +#include +#include + +#include "AOTCalls.h" +#include "Helpers.h" + +using namespace v8; + +namespace tns { + +std::atomic MethodCallProfiler::enabled_{false}; + +namespace { + +struct MethodProfile { + std::string className; + std::string selectorName; + std::string returnType; + std::vector argTypes; + uint64_t count = 0; + bool isStatic = false; +}; + +// Leaked so exit()'s static destructors cannot run under a worker thread that +// is still recording. +std::mutex& profileMutex = *new std::mutex(); +std::unordered_map& profiles = + *new std::unordered_map(); + +std::string EncodingToTypeName(const TypeEncoding* enc) { + switch (enc->type) { + case BinaryTypeEncodingType::VoidEncoding: + return "void"; + case BinaryTypeEncodingType::BoolEncoding: + return "BOOL"; + case BinaryTypeEncodingType::IdEncoding: + return "id"; + case BinaryTypeEncodingType::InterfaceDeclarationReference: + return enc->details.interfaceDeclarationReference.name.valuePtr(); + case BinaryTypeEncodingType::InstanceTypeEncoding: + return "instancetype"; + case BinaryTypeEncodingType::SelectorEncoding: + return "SEL"; + case BinaryTypeEncodingType::ClassEncoding: + return "Class"; + case BinaryTypeEncodingType::IntEncoding: + return "int"; + case BinaryTypeEncodingType::UIntEncoding: + return "uint"; + case BinaryTypeEncodingType::LongEncoding: + return "long"; + case BinaryTypeEncodingType::ULongEncoding: + return "ulong"; + case BinaryTypeEncodingType::LongLongEncoding: + return "longlong"; + case BinaryTypeEncodingType::ULongLongEncoding: + return "ulonglong"; + case BinaryTypeEncodingType::FloatEncoding: + return "float"; + case BinaryTypeEncodingType::DoubleEncoding: + return "double"; + case BinaryTypeEncodingType::CharEncoding: + return "char"; + case BinaryTypeEncodingType::UCharEncoding: + return "uchar"; + case BinaryTypeEncodingType::ShortEncoding: + return "short"; + case BinaryTypeEncodingType::UShortEncoding: + return "ushort"; + case BinaryTypeEncodingType::UnicharEncoding: + return "unichar"; + case BinaryTypeEncodingType::StructDeclarationReference: + return enc->details.declarationReference.name.valuePtr(); + case BinaryTypeEncodingType::PointerEncoding: + case BinaryTypeEncodingType::CStringEncoding: + case BinaryTypeEncodingType::IncompleteArrayEncoding: + return "pointer"; + case BinaryTypeEncodingType::BlockEncoding: + case BinaryTypeEncodingType::FunctionPointerEncoding: + return "block"; + case BinaryTypeEncodingType::ProtocolEncoding: + return "id"; + default: + return ""; + } +} + +// Caller holds profileMutex. +std::vector SortedProfiles(int topN) { + std::vector sorted; + sorted.reserve(profiles.size()); + for (const auto& pair : profiles) { + sorted.push_back(&pair.second); + } + std::sort(sorted.begin(), sorted.end(), + [](const MethodProfile* a, const MethodProfile* b) { return a->count > b->count; }); + if (topN > 0 && (int)sorted.size() > topN) { + sorted.resize(topN); + } + return sorted; +} + +int TopNArgument(const FunctionCallbackInfo& info) { + if (info.Length() > 0 && info[0]->IsNumber()) { + return (int)tns::ToNumber(info.GetIsolate(), info[0]); + } + return 50; +} + +} // namespace + +void MethodCallProfiler::Enable() { enabled_.store(true, std::memory_order_relaxed); } + +void MethodCallProfiler::Disable() { enabled_.store(false, std::memory_order_relaxed); } + +void MethodCallProfiler::Reset() { + std::lock_guard lock(profileMutex); + profiles.clear(); +} + +void MethodCallProfiler::RecordCall(const std::string& className, const MethodMeta* meta, + bool isStatic) { + const char* selector = meta->selectorAsString(); + std::string key = className; + key += isStatic ? "\t+\t" : "\t-\t"; + key += selector; + + std::lock_guard lock(profileMutex); + auto it = profiles.find(key); + if (it != profiles.end()) { + it->second.count++; + return; + } + + MethodProfile profile; + profile.className = className; + profile.selectorName = selector; + profile.count = 1; + profile.isStatic = isStatic; + + const auto* encodings = meta->encodings(); + const TypeEncoding* enc = encodings->first(); + std::string returnName = EncodingToTypeName(enc); + profile.returnType = returnName.empty() ? "?" : returnName; + + int paramCount = encodings->count - 1; + for (int i = 0; i < paramCount; i++) { + enc = enc->next(); + std::string argName = EncodingToTypeName(enc); + profile.argTypes.push_back(argName.empty() ? "?" : argName); + } + + profiles.emplace(std::move(key), std::move(profile)); +} + +void MethodCallProfiler::JSStart(const FunctionCallbackInfo& info) { Enable(); } + +void MethodCallProfiler::JSStop(const FunctionCallbackInfo& info) { Disable(); } + +void MethodCallProfiler::JSReset(const FunctionCallbackInfo& info) { Reset(); } + +void MethodCallProfiler::JSReport(const FunctionCallbackInfo& info) { + int topN = TopNArgument(info); + + std::ostringstream out; + { + std::lock_guard lock(profileMutex); + auto sorted = SortedProfiles(topN); + out << "Top " << sorted.size() << " method calls:\n"; + for (size_t i = 0; i < sorted.size(); i++) { + const auto& p = *sorted[i]; + out << " " << (i + 1) << ". " << p.className << " " << (p.isStatic ? "+" : "-") << "[" + << p.selectorName << "] " << p.returnType << "("; + for (size_t j = 0; j < p.argTypes.size(); j++) { + if (j > 0) out << ", "; + out << p.argTypes[j]; + } + out << ") - " << p.count << " calls\n"; + } + } + + info.GetReturnValue().Set(tns::ToV8String(info.GetIsolate(), out.str())); +} + +void MethodCallProfiler::JSAOTConfig(const FunctionCallbackInfo& info) { + int topN = TopNArgument(info); + auto isUnsupported = [](const std::string& t) { + return t == "?" || t == "pointer" || t == "block"; + }; + + std::ostringstream out; + { + std::lock_guard lock(profileMutex); + auto sorted = SortedProfiles(topN); + out << "[\n"; + bool first = true; + for (const auto* p : sorted) { + bool hasUnsupportedType = isUnsupported(p->returnType); + for (const auto& a : p->argTypes) { + hasUnsupportedType = hasUnsupportedType || isUnsupported(a); + } + if (hasUnsupportedType) continue; + + if (!first) out << ",\n"; + first = false; + + out << " { \"class\": \"" << p->className << "\", \"selector\": \"" << p->selectorName + << "\", \"ret\": \"" << p->returnType << "\", \"args\": ["; + for (size_t j = 0; j < p->argTypes.size(); j++) { + if (j > 0) out << ", "; + out << "\"" << p->argTypes[j] << "\""; + } + out << "]"; + if (p->isStatic) { + out << ", \"static\": true"; + } + out << " }"; + } + out << "\n]"; + } + + info.GetReturnValue().Set(tns::ToV8String(info.GetIsolate(), out.str())); +} + +void MethodCallProfiler::JSAOTStats(const FunctionCallbackInfo& info) { + Isolate* isolate = info.GetIsolate(); + Local context = isolate->GetCurrentContext(); + Local stats = Object::New(isolate); + bool success = stats + ->Set(context, tns::ToV8String(isolate, "served"), + Number::New(isolate, (double)aot::ServedCallCount())) + .FromMaybe(false) && + stats + ->Set(context, tns::ToV8String(isolate, "declined"), + Number::New(isolate, (double)aot::DeclinedCallCount())) + .FromMaybe(false); + if (success) { + info.GetReturnValue().Set(stats); + } +} + +MaybeLocal MethodCallProfiler::GetExports(Local context) { + Isolate* isolate = v8::Isolate::GetCurrent(); + EscapableHandleScope scope(isolate); + Local profiler = Object::New(isolate); + const std::pair methods[] = { + {"start", JSStart}, {"stop", JSStop}, {"reset", JSReset}, + {"report", JSReport}, {"aotConfig", JSAOTConfig}, {"aotStats", JSAOTStats}, + }; + for (const auto& [name, callback] : methods) { + Local function; + if (!v8::Function::New(context, callback).ToLocal(&function) || + !profiler->Set(context, tns::ToV8String(isolate, name), function).FromMaybe(false)) { + return MaybeLocal(); + } + } + + Local exports = Object::New(isolate); + if (!exports->Set(context, tns::ToV8String(isolate, "profiler"), profiler).FromMaybe(false)) { + return MaybeLocal(); + } + return scope.Escape(exports); +} + +} // namespace tns diff --git a/NativeScript/runtime/NativeScriptAOT.h b/NativeScript/runtime/NativeScriptAOT.h new file mode 100644 index 000000000..c5c0a4e0c --- /dev/null +++ b/NativeScript/runtime/NativeScriptAOT.h @@ -0,0 +1,120 @@ +#ifndef NativeScriptAOT_h +#define NativeScriptAOT_h + +// C bridge between app-compiled AOT call stubs and the runtime. +// +// A stub is a plain C function generated per (class, selector) by +// scripts/generate-aot.py from the app's aot-config.json and the metadata +// generator's JSON output. The stub is bound to the method or property getter +// when the class's prototype template is built, so V8 calls it directly in +// place of the generic libffi path. Everything the stub needs from the +// runtime goes through the functions below; it never includes runtime +// headers. +// +// Contract: +// - A handler returns true when it handled the call (set a return value or +// threw into V8) and false to decline, in which case the runtime runs the +// generic path for the same call. A declining handler must not have set a +// return value, thrown, or had side effects. Declining is how stubs stay +// correct for everything they do not model: alloc receivers, argument-count +// mismatches (overloads, optional NSError** out parameters), non-object +// receivers. +// - Argument getters return false when conversion threw into V8; the handler +// must then return true without sending the message. +// - Exceptions thrown by the callee (NSException) must be caught by the stub +// and passed to __ns_aot_throw_exception, which converts them the way the +// generic path does. +// - Values returned by the callee with +1 ownership (init, copy, new, or +// methods flagged as owning their return) are passed with owned = true so +// the runtime balances the retain. The stub file may be compiled with or +// without ARC; the cast objc_msgSend calls are +0 to ARC either way. +// - Struct types are resolved once per stub through __ns_aot_struct_type and +// the handle passed back; the handle is stable for the process lifetime. +// +// This header is public API of the framework and is kept C-compatible. + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef const void* NSAOTCallInfo; +typedef const void* NSAOTStructType; +typedef bool (*NSAOTCallHandler)(NSAOTCallInfo info); + +// --- Receiver --- + +int __ns_aot_arg_count(NSAOTCallInfo info); + +// Returns the receiver for an instance call, or nil when the stub must decline +// (no wrapper, alloc wrapper, non-object wrapper). On success *outSelector is +// the selector to send (the runtime may redirect to a swizzled selector) and +// *outCallSuper tells the stub to use objc_msgSendSuper against the +// receiver's superclass, which is how instances of JS-extended classes reach +// the native implementation. +id __ns_aot_get_target(NSAOTCallInfo info, SEL selector, SEL* outSelector, + bool* outCallSuper); + +// Returns the class for a static call, or nil when the stub must decline. +Class __ns_aot_get_static_class(NSAOTCallInfo info); + +// --- Arguments --- +// Each returns false if the conversion threw into V8. + +bool __ns_aot_arg_object(NSAOTCallInfo info, int index, id* out); +bool __ns_aot_arg_bool(NSAOTCallInfo info, int index, BOOL* out); +bool __ns_aot_arg_int64(NSAOTCallInfo info, int index, int64_t* out); +bool __ns_aot_arg_uint64(NSAOTCallInfo info, int index, uint64_t* out); +bool __ns_aot_arg_double(NSAOTCallInfo info, int index, double* out); +bool __ns_aot_arg_selector(NSAOTCallInfo info, int index, SEL* out); +bool __ns_aot_arg_class(NSAOTCallInfo info, int index, Class* out); +bool __ns_aot_arg_pointer(NSAOTCallInfo info, int index, void** out); +bool __ns_aot_arg_struct(NSAOTCallInfo info, int index, NSAOTStructType type, + void* dest); + +// --- Return values --- + +// marshalToPrimitive mirrors the generic path: NSString, NSNumber, NSDate and +// NSNull become JS primitives unless the declared return type is instancetype +// (and an NSString stays an object when the declared type is NSMutableString). +void __ns_aot_return_object(NSAOTCallInfo info, id value, bool owned, + bool marshalToPrimitive); +void __ns_aot_return_bool(NSAOTCallInfo info, BOOL value); +void __ns_aot_return_int64(NSAOTCallInfo info, int64_t value); +void __ns_aot_return_uint64(NSAOTCallInfo info, uint64_t value); +void __ns_aot_return_double(NSAOTCallInfo info, double value); +void __ns_aot_return_selector(NSAOTCallInfo info, SEL value); +void __ns_aot_return_class(NSAOTCallInfo info, Class value); +void __ns_aot_return_pointer(NSAOTCallInfo info, void* value); +void __ns_aot_return_struct(NSAOTCallInfo info, NSAOTStructType type, + const void* data); + +// --- Structs --- + +// Resolves a struct type by its metadata name (e.g. "CGRect"). Returns NULL +// if the metadata has no such struct; the stub must then decline. +NSAOTStructType __ns_aot_struct_type(const char* name); + +// --- Exceptions --- + +void __ns_aot_throw_exception(NSAOTCallInfo info, id exception); + +// --- Registration --- + +// The generated stubs file exports +// void __ns_register_aot_calls(void (*reg)(const char*, const char*, bool, +// NSAOTCallHandler)); +// which the runtime locates with dlsym at startup and calls once; it must call +// reg(className, selector, isStatic, handler) for every stub. className is +// the class whose metadata declares the member (or adopts the protocol that +// does). + +#ifdef __cplusplus +} +#endif + +#endif /* NativeScriptAOT_h */ diff --git a/NativeScript/runtime/NativeScriptAOTBridge.mm b/NativeScript/runtime/NativeScriptAOTBridge.mm new file mode 100644 index 000000000..99108d598 --- /dev/null +++ b/NativeScript/runtime/NativeScriptAOTBridge.mm @@ -0,0 +1,664 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "AOTCalls.h" +#include "ArgConverter.h" +#include "Caches.h" +#include "DataWrapper.h" +#include "FFICall.h" +#include "Helpers.h" +#include "Interop.h" +#include "MetadataBuilder.h" +#include "MethodCallProfiler.h" +#include "NativeScriptAOT.h" +#include "NativeScriptException.h" +#include "Pointer.h" +#include "robin_hood.h" + +using namespace v8; + +namespace tns::aot { + +struct ClassStubs { + robin_hood::unordered_map instanceMembers; + robin_hood::unordered_map staticMembers; +}; + +namespace { + +using ClassStubsMap = + std::unordered_map; + +// Written only while DiscoverExternalStubs' call_once runs, read-only after; +// call_once orders those writes before every runtime's template building. +ClassStubsMap& Registry() { + static auto* registry = new ClassStubsMap(); + return *registry; +} +bool hasStubs = false; + +std::mutex registrationMutex; +bool registrationOpen = false; +size_t duplicateRegistrations = 0; +std::string firstDuplicate; + +std::atomic servedCalls{0}; +std::atomic declinedCalls{0}; + +// What NSAOTCallInfo points to. +struct CallFrame { + const FunctionCallbackInfo& info; + // The method (or property getter) the stub is bound to. + const MethodMeta* meta; + // Set at static property getter sites, which always message the declaring + // class rather than the receiver. + const std::string* staticGetterClassName; + // Cursor over meta's parameter encodings, so in-order argument reads walk + // the encoding list once. + mutable const TypeEncoding* paramEncoding = nullptr; + mutable int paramIndex = -1; +}; + +inline const CallFrame& FrameOf(NSAOTCallInfo callInfo) { + return *static_cast(callInfo); +} + +const TypeEncoding* ParameterEncoding(const CallFrame& frame, int index) { + const auto* encodings = frame.meta->encodings(); + if (index < 0 || index >= encodings->count - 1) { + return nullptr; + } + if (frame.paramIndex > index || frame.paramEncoding == nullptr) { + frame.paramEncoding = encodings->first()->next(); + frame.paramIndex = 0; + } + while (frame.paramIndex < index) { + frame.paramEncoding = frame.paramEncoding->next(); + frame.paramIndex++; + } + return frame.paramEncoding; +} + +// Whether a class's instances must be messaged through objc_msgSendSuper, +// i.e. whether the class is in Caches::ClassPrototypes. ClassBuilder can run +// JS between allocating an extended class and entering it there, so the +// answers are dropped whenever ClassPrototypes grows. +struct AOTState { + Caches* caches = nullptr; + size_t classPrototypesSize = 0; + robin_hood::unordered_map callSuperByClass; +}; + +inline bool Dispatch(NSAOTCallHandler handler, const CallFrame& frame) { + bool handled = handler(&frame); + if (MethodCallProfiler::IsEnabled()) { + (handled ? servedCalls : declinedCalls).fetch_add(1, std::memory_order_relaxed); + } + return handled; +} + +bool Find(const StubScope& scope, bool isStatic, SEL selector, v8::FunctionCallback callback, + Binding& out) { + for (const ClassStubs* stubs : scope.classes) { + const auto& members = isStatic ? stubs->staticMembers : stubs->instanceMembers; + auto it = members.find(selector); + if (it != members.end()) { + out.callback = callback; + out.handler = reinterpret_cast(it->second); + return true; + } + } + return false; +} + +} // namespace + +struct Trampolines { + template + static NSAOTCallHandler HandlerOf(const FunctionCallbackInfo& info, + MetadataBuilder::CacheItem*& item) { + item = static_cast*>( + info.Data().As()->Value(v8::kExternalPointerTypeTagDefault)); + return reinterpret_cast(item->userData_); + } + + static void Method(const FunctionCallbackInfo& info) { + MetadataBuilder::CacheItem* item; + NSAOTCallHandler handler = HandlerOf(info, item); + if (!Dispatch(handler, CallFrame{info, item->meta_, nullptr}) && + !info.GetIsolate()->HasPendingException()) { + MetadataBuilder::MethodCallback(info); + } + } + + static void InstanceGetter(const FunctionCallbackInfo& info) { + MetadataBuilder::CacheItem* item; + NSAOTCallHandler handler = HandlerOf(info, item); + if (!Dispatch(handler, CallFrame{info, item->meta_->getter(), nullptr}) && + !info.GetIsolate()->HasPendingException()) { + MetadataBuilder::PropertyGetterCallback(info); + } + } + + static void StaticGetter(const FunctionCallbackInfo& info) { + MetadataBuilder::CacheItem* item; + NSAOTCallHandler handler = HandlerOf(info, item); + if (!Dispatch(handler, CallFrame{info, item->meta_->getter(), &item->className_}) && + !info.GetIsolate()->HasPendingException()) { + MetadataBuilder::PropertyNameGetterCallback(info); + } + } +}; + +void DiscoverExternalStubs() { + static std::once_flag once; + std::call_once(once, [] { + using Registrar = void (*)(void (*)(const char*, const char*, bool, NSAOTCallHandler)); + auto registrar = reinterpret_cast(dlsym(RTLD_DEFAULT, "__ns_register_aot_calls")); + if (registrar == nullptr) { + return; + } + + { + std::lock_guard lock(registrationMutex); + registrationOpen = true; + } + registrar(Register); + { + std::lock_guard lock(registrationMutex); + registrationOpen = false; + if (duplicateRegistrations > 0) { + Log(@"%zu duplicate AOT stub registration(s) ignored; the first registration wins " + @"(first duplicate: %s)", + duplicateRegistrations, firstDuplicate.c_str()); + } + } + hasStubs = !Registry().empty(); + }); +} + +void Register(const char* className, const char* selector, bool isStatic, + NSAOTCallHandler handler) { + if (className == nullptr || selector == nullptr || handler == nullptr) { + return; + } + + std::lock_guard lock(registrationMutex); + if (!registrationOpen) { + Log(@"AOT stub for %s %c[%s] ignored: stubs register only through __ns_register_aot_calls", + className, isStatic ? '+' : '-', selector); + return; + } + + ClassStubs& stubs = Registry()[className]; + auto& members = isStatic ? stubs.staticMembers : stubs.instanceMembers; + if (!members.emplace(sel_registerName(selector), handler).second) { + if (duplicateRegistrations++ == 0) { + firstDuplicate = std::string(isStatic ? "+[" : "-[") + className + " " + selector + "]"; + } + } +} + +StubScope FindStubScope(const char* className) { + StubScope scope; + if (!hasStubs || className == nullptr) { + return scope; + } + + const auto& registry = Registry(); + auto add = [&](std::string_view name) { + auto it = registry.find(name); + if (it != registry.end()) { + scope.classes.push_back(&it->second); + } + }; + + add(className); + Class klass = objc_getClass(className); + for (klass = klass != nil ? class_getSuperclass(klass) : nil; klass != nil; + klass = class_getSuperclass(klass)) { + add(class_getName(klass)); + } + return scope; +} + +bool FindInstanceMethod(const StubScope& scope, SEL selector, Binding& out) { + return Find(scope, false, selector, Trampolines::Method, out); +} + +bool FindStaticMethod(const StubScope& scope, SEL selector, Binding& out) { + return Find(scope, true, selector, Trampolines::Method, out); +} + +bool FindInstanceGetter(const StubScope& scope, SEL getter, Binding& out) { + return Find(scope, false, getter, Trampolines::InstanceGetter, out); +} + +bool FindStaticGetter(const StubScope& scope, SEL getter, Binding& out) { + return Find(scope, true, getter, Trampolines::StaticGetter, out); +} + +uint64_t ServedCallCount() { return servedCalls.load(std::memory_order_relaxed); } + +uint64_t DeclinedCallCount() { return declinedCalls.load(std::memory_order_relaxed); } + +namespace { + +void ThrowUnexpectedException(Isolate* isolate) { + isolate->ThrowException( + Exception::Error(tns::ToV8String(isolate, "Unexpected native error in an AOT call"))); +} + +bool IsNumeric(BinaryTypeEncodingType type) { + switch (type) { + case BinaryTypeEncodingType::BoolEncoding: + case BinaryTypeEncodingType::UnicharEncoding: + return true; + default: + return Interop::IsNumbericType(type); + } +} + +bool IsPointerSized(BinaryTypeEncodingType type) { + switch (type) { + case BinaryTypeEncodingType::IdEncoding: + case BinaryTypeEncodingType::InterfaceDeclarationReference: + case BinaryTypeEncodingType::InstanceTypeEncoding: + case BinaryTypeEncodingType::ProtocolEncoding: + case BinaryTypeEncodingType::ClassEncoding: + case BinaryTypeEncodingType::SelectorEncoding: + case BinaryTypeEncodingType::BlockEncoding: + case BinaryTypeEncodingType::FunctionPointerEncoding: + case BinaryTypeEncodingType::PointerEncoding: + case BinaryTypeEncodingType::CStringEncoding: + case BinaryTypeEncodingType::IncompleteArrayEncoding: + return true; + default: + return false; + } +} + +bool IsStruct(BinaryTypeEncodingType type) { + return type == BinaryTypeEncodingType::StructDeclarationReference || + type == BinaryTypeEncodingType::AnonymousStructEncoding; +} + +// Converts argument `index` with Interop::WriteValue against the parameter's +// declared encoding, exactly as the generic path fills its ffi buffer. `dest` +// must be large enough for that encoding's type. +bool ConvertArgument(NSAOTCallInfo callInfo, int index, bool (*accepts)(BinaryTypeEncodingType), + void* dest, BinaryTypeEncodingType* outType) { + const CallFrame& frame = FrameOf(callInfo); + Isolate* isolate = frame.info.GetIsolate(); + const TypeEncoding* encoding = ParameterEncoding(frame, index); + if (encoding == nullptr || !accepts(encoding->type)) { + std::string message = std::string("AOT stub for ") + frame.meta->selectorAsString() + + " read argument " + std::to_string(index) + + " with a getter that does not match its declared type"; + isolate->ThrowException(Exception::TypeError(tns::ToV8String(isolate, message))); + return false; + } + + try { + Interop::WriteValue(isolate->GetCurrentContext(), encoding, dest, frame.info[index]); + } catch (NativeScriptException& ex) { + ex.ReThrowToV8(isolate); + return false; + } catch (...) { + ThrowUnexpectedException(isolate); + return false; + } + if (outType != nullptr) { + *outType = encoding->type; + } + return !isolate->HasPendingException(); +} + +template +T Widen(BinaryTypeEncodingType type, const void* value) { + switch (type) { + case BinaryTypeEncodingType::BoolEncoding: + return static_cast(*static_cast(value)); + case BinaryTypeEncodingType::CharEncoding: + return static_cast(*static_cast(value)); + case BinaryTypeEncodingType::UCharEncoding: + return static_cast(*static_cast(value)); + case BinaryTypeEncodingType::ShortEncoding: + return static_cast(*static_cast(value)); + case BinaryTypeEncodingType::UShortEncoding: + return static_cast(*static_cast(value)); + case BinaryTypeEncodingType::UnicharEncoding: + return static_cast(*static_cast(value)); + case BinaryTypeEncodingType::IntEncoding: + return static_cast(*static_cast(value)); + case BinaryTypeEncodingType::UIntEncoding: + return static_cast(*static_cast(value)); + case BinaryTypeEncodingType::LongEncoding: + return static_cast(*static_cast(value)); + case BinaryTypeEncodingType::ULongEncoding: + return static_cast(*static_cast(value)); + case BinaryTypeEncodingType::LongLongEncoding: + return static_cast(*static_cast(value)); + case BinaryTypeEncodingType::ULongLongEncoding: + return static_cast(*static_cast(value)); + case BinaryTypeEncodingType::FloatEncoding: + return static_cast(*static_cast(value)); + case BinaryTypeEncodingType::DoubleEncoding: + return static_cast(*static_cast(value)); + default: + return T(); + } +} + +template +bool ReadNumber(NSAOTCallInfo callInfo, int index, T* out) { + alignas(8) uint8_t value[8] = {}; + BinaryTypeEncodingType type; + if (!ConvertArgument(callInfo, index, IsNumeric, value, &type)) { + return false; + } + *out = Widen(type, value); + return true; +} + +template +bool ReadPointer(NSAOTCallInfo callInfo, int index, T* out) { + void* value = nullptr; + if (!ConvertArgument(callInfo, index, IsPointerSized, &value, nullptr)) { + return false; + } + *out = (T)value; + return true; +} + +void SetReturnValue(const FunctionCallbackInfo& info, Local value) { + if (!value.IsEmpty()) { + info.GetReturnValue().Set(value); + } +} + +#ifdef DEBUG +// The generator derives `owned` and `marshalToPrimitive` from the same +// metadata the generic path reads; a mismatch means stale or wrong stubs. +void AssertReturnFlagsMatchMetadata(Isolate* isolate, const MethodMeta* meta, bool owned, + bool marshalToPrimitive) { + const TypeEncoding* returnEncoding = meta->encodings()->first(); + bool expectedOwned = meta->ownsReturnedCocoaObject() || meta->isInitializer(); + bool expectedMarshal = returnEncoding->type != BinaryTypeEncodingType::InstanceTypeEncoding; + bool isMutableString = + returnEncoding->type == BinaryTypeEncodingType::InterfaceDeclarationReference && + std::strcmp(returnEncoding->details.declarationReference.name.valuePtr(), + "NSMutableString") == 0; + bool marshalMatches = + marshalToPrimitive == expectedMarshal || (isMutableString && !marshalToPrimitive); + tns::Assert( + owned == expectedOwned && marshalMatches, isolate, + std::string("AOT stub return flags disagree with metadata for ") + meta->selectorAsString()); +} +#endif + +} // namespace + +} // namespace tns::aot + +using namespace tns; +using tns::aot::FrameOf; + +extern "C" { + +int __ns_aot_arg_count(NSAOTCallInfo callInfo) { return FrameOf(callInfo).info.Length(); } + +// Mirrors ArgConverter::Invoke and Interop::CallFunctionInternal for an +// instance receiver. +id __ns_aot_get_target(NSAOTCallInfo callInfo, SEL selector, SEL* outSelector, bool* outCallSuper) { + *outSelector = selector; + *outCallSuper = false; + + const auto& info = FrameOf(callInfo).info; + Local thiz = info.This(); + if (thiz->InternalFieldCount() < 1) { + return nil; + } + + Isolate* isolate = info.GetIsolate(); + BaseDataWrapper* wrapper = tns::GetValue(isolate, thiz); + if (wrapper == nullptr || wrapper->Type() != WrapperType::ObjCObject) { + return nil; + } + id target = static_cast(wrapper)->Data(); + if (target == nil) { + return nil; + } + + auto* state = Caches::StateFor(isolate); + if (state == nullptr) { + return nil; + } + if (state->caches == nullptr) { + state->caches = Caches::Get(isolate).get(); + } + auto& classPrototypes = state->caches->ClassPrototypes; + if (classPrototypes.size() != state->classPrototypesSize) { + state->callSuperByClass.clear(); + state->classPrototypesSize = classPrototypes.size(); + } + + Class klass = object_getClass(target); + bool callSuper; + auto it = state->callSuperByClass.find(klass); + if (it != state->callSuperByClass.end()) { + callSuper = it->second; + } else { + callSuper = + classPrototypes.find(std::string_view(class_getName(klass))) != classPrototypes.end(); + state->callSuperByClass.emplace(klass, callSuper); + } + + SEL swizzled = Interop::GetSwizzledMethodSelector(selector); + if ([target respondsToSelector:swizzled]) { + *outSelector = swizzled; + } + *outCallSuper = callSuper; + return target; +} + +// Mirrors MetadataBuilder::MethodCallback (class from the receiver) and +// PropertyNameGetterCallback (class the property was registered on). +Class __ns_aot_get_static_class(NSAOTCallInfo callInfo) { + const auto& frame = FrameOf(callInfo); + if (frame.staticGetterClassName != nullptr) { + return objc_getClass(frame.staticGetterClassName->c_str()); + } + + Local thiz = frame.info.This(); + if (!thiz->IsFunction()) { + return nil; + } + BaseDataWrapper* wrapper = tns::GetValue(frame.info.GetIsolate(), thiz); + if (wrapper == nullptr || wrapper->Type() != WrapperType::ObjCClass) { + return nil; + } + return static_cast(wrapper)->Klass(); +} + +bool __ns_aot_arg_object(NSAOTCallInfo info, int index, id* out) { + return aot::ReadPointer(info, index, out); +} + +bool __ns_aot_arg_bool(NSAOTCallInfo info, int index, BOOL* out) { + return aot::ReadNumber(info, index, out); +} + +bool __ns_aot_arg_int64(NSAOTCallInfo info, int index, int64_t* out) { + return aot::ReadNumber(info, index, out); +} + +bool __ns_aot_arg_uint64(NSAOTCallInfo info, int index, uint64_t* out) { + return aot::ReadNumber(info, index, out); +} + +bool __ns_aot_arg_double(NSAOTCallInfo info, int index, double* out) { + return aot::ReadNumber(info, index, out); +} + +bool __ns_aot_arg_selector(NSAOTCallInfo info, int index, SEL* out) { + return aot::ReadPointer(info, index, out); +} + +bool __ns_aot_arg_class(NSAOTCallInfo info, int index, Class* out) { + return aot::ReadPointer(info, index, out); +} + +bool __ns_aot_arg_pointer(NSAOTCallInfo info, int index, void** out) { + return aot::ReadPointer(info, index, out); +} + +bool __ns_aot_arg_struct(NSAOTCallInfo info, int index, NSAOTStructType type, void* dest) { + (void)type; + return aot::ConvertArgument(info, index, aot::IsStruct, dest, nullptr); +} + +void __ns_aot_return_object(NSAOTCallInfo callInfo, id value, bool owned, bool marshalToPrimitive) { + const auto& frame = FrameOf(callInfo); + Isolate* isolate = frame.info.GetIsolate(); +#ifdef DEBUG + aot::AssertReturnFlagsMatchMetadata(isolate, frame.meta, owned, marshalToPrimitive); +#endif + try { + aot::SetReturnValue(frame.info, Interop::ObjectToJsValue(isolate->GetCurrentContext(), value, + frame.meta->encodings()->first(), + marshalToPrimitive, owned, false)); + } catch (NativeScriptException& ex) { + ex.ReThrowToV8(isolate); + } catch (...) { + aot::ThrowUnexpectedException(isolate); + } +} + +void __ns_aot_return_bool(NSAOTCallInfo callInfo, BOOL value) { + const auto& info = FrameOf(callInfo).info; + info.GetReturnValue().Set(v8::Boolean::New(info.GetIsolate(), value)); +} + +void __ns_aot_return_int64(NSAOTCallInfo callInfo, int64_t value) { + const auto& info = FrameOf(callInfo).info; + info.GetReturnValue().Set(Number::New(info.GetIsolate(), static_cast(value))); +} + +void __ns_aot_return_uint64(NSAOTCallInfo callInfo, uint64_t value) { + const auto& info = FrameOf(callInfo).info; + info.GetReturnValue().Set(Number::New(info.GetIsolate(), static_cast(value))); +} + +void __ns_aot_return_double(NSAOTCallInfo callInfo, double value) { + const auto& info = FrameOf(callInfo).info; + info.GetReturnValue().Set(Number::New(info.GetIsolate(), value)); +} + +void __ns_aot_return_selector(NSAOTCallInfo callInfo, SEL value) { + const auto& info = FrameOf(callInfo).info; + Isolate* isolate = info.GetIsolate(); + if (value == nullptr) { + info.GetReturnValue().Set(Null(isolate)); + return; + } + info.GetReturnValue().Set(tns::ToV8String(isolate, sel_getName(value))); +} + +void __ns_aot_return_class(NSAOTCallInfo callInfo, Class value) { + const auto& info = FrameOf(callInfo).info; + Isolate* isolate = info.GetIsolate(); + try { + aot::SetReturnValue(info, Interop::ClassToJsValue(isolate->GetCurrentContext(), value)); + } catch (NativeScriptException& ex) { + ex.ReThrowToV8(isolate); + } catch (...) { + aot::ThrowUnexpectedException(isolate); + } +} + +void __ns_aot_return_pointer(NSAOTCallInfo callInfo, void* value) { + const auto& info = FrameOf(callInfo).info; + Isolate* isolate = info.GetIsolate(); + if (value == nullptr) { + info.GetReturnValue().Set(Null(isolate)); + return; + } + try { + aot::SetReturnValue(info, Pointer::NewInstance(isolate->GetCurrentContext(), value)); + } catch (NativeScriptException& ex) { + ex.ReThrowToV8(isolate); + } catch (...) { + aot::ThrowUnexpectedException(isolate); + } +} + +void __ns_aot_return_struct(NSAOTCallInfo callInfo, NSAOTStructType type, const void* data) { + const auto& info = FrameOf(callInfo).info; + Isolate* isolate = info.GetIsolate(); + const auto* structInfo = static_cast(type); + try { + aot::SetReturnValue( + info, Interop::StructToValue(isolate->GetCurrentContext(), const_cast(data), + *structInfo, nullptr)); + } catch (NativeScriptException& ex) { + ex.ReThrowToV8(isolate); + } catch (...) { + aot::ThrowUnexpectedException(isolate); + } +} + +NSAOTStructType __ns_aot_struct_type(const char* name) { + if (name == nullptr) { + return nullptr; + } + + try { + const Meta* meta = ArgConverter::GetMeta(name); + if (meta == nullptr || meta->type() != MetaType::Struct) { + return nullptr; + } + // The struct-info cache owns each StructInfo for the process lifetime, so + // its address is a stable handle. + return &FFICall::GetStructInfo(static_cast(meta)); + } catch (...) { + return nullptr; + } +} + +// Mirrors the NSException handling in Interop::CallFunctionInternal and its +// rethrow from MetadataBuilder::InvokeMethod. +void __ns_aot_throw_exception(NSAOTCallInfo callInfo, id exception) { + const auto& info = FrameOf(callInfo).info; + Isolate* isolate = info.GetIsolate(); + try { + if (![exception isKindOfClass:[NSException class]]) { + NativeScriptException(tns::ToString(isolate, [exception description] ?: @"nil")) + .ReThrowToV8(isolate); + return; + } + + std::string message; + Local jsError = + Interop::NSExceptionToJsError(isolate->GetCurrentContext(), exception, message); + if (jsError.IsEmpty()) { + NativeScriptException(tns::ToString(isolate, [exception description])).ReThrowToV8(isolate); + } else { + NativeScriptException(isolate, jsError, message).ReThrowToV8(isolate); + } + } catch (NativeScriptException& ex) { + ex.ReThrowToV8(isolate); + } catch (...) { + aot::ThrowUnexpectedException(isolate); + } +} + +} // extern "C" diff --git a/NativeScript/runtime/Runtime.mm b/NativeScript/runtime/Runtime.mm index 216fb7104..776612982 100644 --- a/NativeScript/runtime/Runtime.mm +++ b/NativeScript/runtime/Runtime.mm @@ -1,6 +1,7 @@ #include "Runtime.h" #include #include +#include "AOTCalls.h" #include "ArgConverter.h" #include "BuiltinLoader.h" #include "Caches.h" @@ -417,6 +418,7 @@ void DisposeIsolateWhenPossible(Isolate* isolate) { } ObjectManager::Init(isolate, globalTemplate); LazyGlobals::Init(isolate, globalTemplate); + aot::DiscoverExternalStubs(); MetadataBuilder::RegisterConstantsOnGlobalObject(isolate, globalTemplate, isWorker); isolate->SetCaptureStackTraceForUncaughtExceptions(true, 100, StackTrace::kOverview); diff --git a/TestFixtures/exported-symbols.txt b/TestFixtures/exported-symbols.txt index 9a915ec8e..0ae2c6c1e 100644 --- a/TestFixtures/exported-symbols.txt +++ b/TestFixtures/exported-symbols.txt @@ -221,3 +221,4 @@ __SCNVector3FromFloat3 __SCNVector4FromFloat4 __SCNMatrix4FromMat4 _getStructFromCallback +___ns_register_aot_calls diff --git a/TestRunner/AOT/NativeScriptAOTStubs.m b/TestRunner/AOT/NativeScriptAOTStubs.m new file mode 100644 index 000000000..9ede5f81f --- /dev/null +++ b/TestRunner/AOT/NativeScriptAOTStubs.m @@ -0,0 +1,886 @@ +// Generated by scripts/generate-aot.py. Do not edit. +#import +#import +#import +#import + +typedef void (*AOTSendFn)(void); + +// x86_64 returns a struct through a hidden pointer, and needs the _stret entry +// points, when it is classified MEMORY. Size over 16 bytes approximates that +// classification; smaller structs with unaligned or long double fields are not +// modeled. arm64 never uses _stret. +#if defined(__x86_64__) +#define AOT_SEND_STRET(T) (sizeof(T) > 16 ? (AOTSendFn)objc_msgSend_stret : (AOTSendFn)objc_msgSend) +#define AOT_SEND_SUPER_STRET(T) \ + (sizeof(T) > 16 ? (AOTSendFn)objc_msgSendSuper_stret : (AOTSendFn)objc_msgSendSuper) +#else +#define AOT_SEND_STRET(T) ((AOTSendFn)objc_msgSend) +#define AOT_SEND_SUPER_STRET(T) ((AOTSendFn)objc_msgSendSuper) +#endif + +typedef struct { + int x; + int y; +} AOT_TNSSimpleStruct; +typedef struct { + AOT_TNSSimpleStruct a; + AOT_TNSSimpleStruct b; +} AOT_TNSNestedStruct; +typedef struct { + long long x; + long long y; + long long z; +} AOT_TNSStruct32; +typedef struct { + int x1; + struct { short x2; struct { signed char x3[2]; } y2; } y1[2]; + long long x4; +} AOT_TNSComplexStruct; +typedef struct { + double x; + double y; +} AOT_CGPoint; +typedef struct { + double width; + double height; +} AOT_CGSize; +typedef struct { + AOT_CGPoint origin; + AOT_CGSize size; +} AOT_CGRect; + +static bool AOT_TNSBaseInterface_baseMethod(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 0) return false; + SEL sel = @selector(baseMethod); + bool callSuper = false; + id target = __ns_aot_get_target(info, sel, &sel, &callSuper); + if (target == nil) return false; + @try { + if (callSuper) { + struct objc_super sup = {target, class_getSuperclass(object_getClass(target))}; + ((void (*)(struct objc_super*, SEL))objc_msgSendSuper)(&sup, sel); + } else { + ((void (*)(id, SEL))objc_msgSend)(target, sel); + } + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + return true; +} + +static bool AOT_TNSBaseInterface_baseMethod_static(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 0) return false; + SEL sel = @selector(baseMethod); + Class cls = __ns_aot_get_static_class(info); + if (cls == nil) return false; + @try { + ((void (*)(id, SEL))objc_msgSend)((id)cls, sel); + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + return true; +} + +static bool AOT_TNSBaseInterface_methodWithParam(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 1) return false; + SEL sel = @selector(methodWithParam:); + bool callSuper = false; + id target = __ns_aot_get_target(info, sel, &sel, &callSuper); + if (target == nil) return false; + id a0 = nil; + if (!__ns_aot_arg_object(info, 0, &a0)) return true; + @try { + if (callSuper) { + struct objc_super sup = {target, class_getSuperclass(object_getClass(target))}; + ((void (*)(struct objc_super*, SEL, id))objc_msgSendSuper)(&sup, sel, a0); + } else { + ((void (*)(id, SEL, id))objc_msgSend)(target, sel, a0); + } + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + return true; +} + +static bool AOT_TNSDerivedInterface_methodWith_param(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 2) return false; + SEL sel = @selector(methodWith:param:); + bool callSuper = false; + id target = __ns_aot_get_target(info, sel, &sel, &callSuper); + if (target == nil) return false; + id a0 = nil; + if (!__ns_aot_arg_object(info, 0, &a0)) return true; + id a1 = nil; + if (!__ns_aot_arg_object(info, 1, &a1)) return true; + @try { + if (callSuper) { + struct objc_super sup = {target, class_getSuperclass(object_getClass(target))}; + ((void (*)(struct objc_super*, SEL, id, id))objc_msgSendSuper)(&sup, sel, a0, a1); + } else { + ((void (*)(id, SEL, id, id))objc_msgSend)(target, sel, a0, a1); + } + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + return true; +} + +static bool AOT_TNSBaseInterface_callBaseMethod(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 1) return false; + SEL sel = @selector(callBaseMethod:); + bool callSuper = false; + id target = __ns_aot_get_target(info, sel, &sel, &callSuper); + if (target == nil) return false; + BOOL a0 = NO; + if (!__ns_aot_arg_bool(info, 0, &a0)) return true; + @try { + if (callSuper) { + struct objc_super sup = {target, class_getSuperclass(object_getClass(target))}; + ((void (*)(struct objc_super*, SEL, BOOL))objc_msgSendSuper)(&sup, sel, a0); + } else { + ((void (*)(id, SEL, BOOL))objc_msgSend)(target, sel, a0); + } + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + return true; +} + +static bool AOT_TNSBaseInterface_baseProperty(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 0) return false; + SEL sel = @selector(baseProperty); + bool callSuper = false; + id target = __ns_aot_get_target(info, sel, &sel, &callSuper); + if (target == nil) return false; + int r = 0; + @try { + if (callSuper) { + struct objc_super sup = {target, class_getSuperclass(object_getClass(target))}; + r = ((int (*)(struct objc_super*, SEL))objc_msgSendSuper)(&sup, sel); + } else { + r = ((int (*)(id, SEL))objc_msgSend)(target, sel); + } + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + __ns_aot_return_int64(info, (int64_t)r); + return true; +} + +static bool AOT_TNSBaseInterface_setBaseProperty(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 1) return false; + SEL sel = @selector(setBaseProperty:); + bool callSuper = false; + id target = __ns_aot_get_target(info, sel, &sel, &callSuper); + if (target == nil) return false; + int64_t raw0 = 0; + if (!__ns_aot_arg_int64(info, 0, &raw0)) return true; + int a0 = (int)raw0; + @try { + if (callSuper) { + struct objc_super sup = {target, class_getSuperclass(object_getClass(target))}; + ((void (*)(struct objc_super*, SEL, int))objc_msgSendSuper)(&sup, sel, a0); + } else { + ((void (*)(id, SEL, int))objc_msgSend)(target, sel, a0); + } + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + return true; +} + +static bool AOT_TNSBaseInterface_baseProperty_static(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 0) return false; + SEL sel = @selector(baseProperty); + Class cls = __ns_aot_get_static_class(info); + if (cls == nil) return false; + int r = 0; + @try { + r = ((int (*)(id, SEL))objc_msgSend)((id)cls, sel); + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + __ns_aot_return_int64(info, (int64_t)r); + return true; +} + +static bool AOT_TNSApi_customGetter(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 0) return false; + SEL sel = @selector(customGetter); + bool callSuper = false; + id target = __ns_aot_get_target(info, sel, &sel, &callSuper); + if (target == nil) return false; + int r = 0; + @try { + if (callSuper) { + struct objc_super sup = {target, class_getSuperclass(object_getClass(target))}; + r = ((int (*)(struct objc_super*, SEL))objc_msgSendSuper)(&sup, sel); + } else { + r = ((int (*)(id, SEL))objc_msgSend)(target, sel); + } + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + __ns_aot_return_int64(info, (int64_t)r); + return true; +} + +static bool AOT_TNSApi_methodThrowsException(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 0) return false; + SEL sel = @selector(methodThrowsException); + bool callSuper = false; + id target = __ns_aot_get_target(info, sel, &sel, &callSuper); + if (target == nil) return false; + @try { + if (callSuper) { + struct objc_super sup = {target, class_getSuperclass(object_getClass(target))}; + ((void (*)(struct objc_super*, SEL))objc_msgSendSuper)(&sup, sel); + } else { + ((void (*)(id, SEL))objc_msgSend)(target, sel); + } + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + return true; +} + +static bool AOT_TNSSwizzleKlass_instanceMethod(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 1) return false; + SEL sel = @selector(instanceMethod:); + bool callSuper = false; + id target = __ns_aot_get_target(info, sel, &sel, &callSuper); + if (target == nil) return false; + int64_t raw0 = 0; + if (!__ns_aot_arg_int64(info, 0, &raw0)) return true; + int a0 = (int)raw0; + int r = 0; + @try { + if (callSuper) { + struct objc_super sup = {target, class_getSuperclass(object_getClass(target))}; + r = ((int (*)(struct objc_super*, SEL, int))objc_msgSendSuper)(&sup, sel, a0); + } else { + r = ((int (*)(id, SEL, int))objc_msgSend)(target, sel, a0); + } + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + __ns_aot_return_int64(info, (int64_t)r); + return true; +} + +static bool AOT_TNSSwizzleKlass_staticMethod_static(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 1) return false; + SEL sel = @selector(staticMethod:); + Class cls = __ns_aot_get_static_class(info); + if (cls == nil) return false; + int64_t raw0 = 0; + if (!__ns_aot_arg_int64(info, 0, &raw0)) return true; + int a0 = (int)raw0; + int r = 0; + @try { + r = ((int (*)(id, SEL, int))objc_msgSend)((id)cls, sel, a0); + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + __ns_aot_return_int64(info, (int64_t)r); + return true; +} + +static bool AOT_TNSPrimitives_methodWithChar(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 1) return false; + SEL sel = @selector(methodWithChar:); + bool callSuper = false; + id target = __ns_aot_get_target(info, sel, &sel, &callSuper); + if (target == nil) return false; + int64_t raw0 = 0; + if (!__ns_aot_arg_int64(info, 0, &raw0)) return true; + signed char a0 = (signed char)raw0; + signed char r = 0; + @try { + if (callSuper) { + struct objc_super sup = {target, class_getSuperclass(object_getClass(target))}; + r = ((signed char (*)(struct objc_super*, SEL, signed char))objc_msgSendSuper)(&sup, sel, a0); + } else { + r = ((signed char (*)(id, SEL, signed char))objc_msgSend)(target, sel, a0); + } + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + __ns_aot_return_int64(info, (int64_t)r); + return true; +} + +static bool AOT_TNSPrimitives_methodWithUChar(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 1) return false; + SEL sel = @selector(methodWithUChar:); + bool callSuper = false; + id target = __ns_aot_get_target(info, sel, &sel, &callSuper); + if (target == nil) return false; + uint64_t raw0 = 0; + if (!__ns_aot_arg_uint64(info, 0, &raw0)) return true; + unsigned char a0 = (unsigned char)raw0; + unsigned char r = 0; + @try { + if (callSuper) { + struct objc_super sup = {target, class_getSuperclass(object_getClass(target))}; + r = ((unsigned char (*)(struct objc_super*, SEL, unsigned char))objc_msgSendSuper)(&sup, sel, a0); + } else { + r = ((unsigned char (*)(id, SEL, unsigned char))objc_msgSend)(target, sel, a0); + } + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + __ns_aot_return_uint64(info, (uint64_t)r); + return true; +} + +static bool AOT_TNSPrimitives_methodWithInt(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 1) return false; + SEL sel = @selector(methodWithInt:); + bool callSuper = false; + id target = __ns_aot_get_target(info, sel, &sel, &callSuper); + if (target == nil) return false; + int64_t raw0 = 0; + if (!__ns_aot_arg_int64(info, 0, &raw0)) return true; + int a0 = (int)raw0; + int r = 0; + @try { + if (callSuper) { + struct objc_super sup = {target, class_getSuperclass(object_getClass(target))}; + r = ((int (*)(struct objc_super*, SEL, int))objc_msgSendSuper)(&sup, sel, a0); + } else { + r = ((int (*)(id, SEL, int))objc_msgSend)(target, sel, a0); + } + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + __ns_aot_return_int64(info, (int64_t)r); + return true; +} + +static bool AOT_TNSPrimitives_methodWithFloat(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 1) return false; + SEL sel = @selector(methodWithFloat:); + bool callSuper = false; + id target = __ns_aot_get_target(info, sel, &sel, &callSuper); + if (target == nil) return false; + double raw0 = 0; + if (!__ns_aot_arg_double(info, 0, &raw0)) return true; + float a0 = (float)raw0; + float r = 0; + @try { + if (callSuper) { + struct objc_super sup = {target, class_getSuperclass(object_getClass(target))}; + r = ((float (*)(struct objc_super*, SEL, float))objc_msgSendSuper)(&sup, sel, a0); + } else { + r = ((float (*)(id, SEL, float))objc_msgSend)(target, sel, a0); + } + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + __ns_aot_return_double(info, (double)r); + return true; +} + +static bool AOT_TNSPrimitives_methodWithDouble_static(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 1) return false; + SEL sel = @selector(methodWithDouble:); + Class cls = __ns_aot_get_static_class(info); + if (cls == nil) return false; + double raw0 = 0; + if (!__ns_aot_arg_double(info, 0, &raw0)) return true; + double a0 = (double)raw0; + double r = 0; + @try { + r = ((double (*)(id, SEL, double))objc_msgSend)((id)cls, sel, a0); + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + __ns_aot_return_double(info, r); + return true; +} + +static bool AOT_TNSPrimitives_methodWithBool2(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 1) return false; + SEL sel = @selector(methodWithBool2:); + bool callSuper = false; + id target = __ns_aot_get_target(info, sel, &sel, &callSuper); + if (target == nil) return false; + BOOL a0 = NO; + if (!__ns_aot_arg_bool(info, 0, &a0)) return true; + BOOL r = NO; + @try { + if (callSuper) { + struct objc_super sup = {target, class_getSuperclass(object_getClass(target))}; + r = ((BOOL (*)(struct objc_super*, SEL, BOOL))objc_msgSendSuper)(&sup, sel, a0); + } else { + r = ((BOOL (*)(id, SEL, BOOL))objc_msgSend)(target, sel, a0); + } + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + __ns_aot_return_bool(info, r); + return true; +} + +static bool AOT_TNSPrimitives_methodWithBool3(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 1) return false; + SEL sel = @selector(methodWithBool3:); + bool callSuper = false; + id target = __ns_aot_get_target(info, sel, &sel, &callSuper); + if (target == nil) return false; + BOOL a0 = NO; + if (!__ns_aot_arg_bool(info, 0, &a0)) return true; + BOOL r = NO; + @try { + if (callSuper) { + struct objc_super sup = {target, class_getSuperclass(object_getClass(target))}; + r = ((BOOL (*)(struct objc_super*, SEL, BOOL))objc_msgSendSuper)(&sup, sel, a0); + } else { + r = ((BOOL (*)(id, SEL, BOOL))objc_msgSend)(target, sel, a0); + } + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + __ns_aot_return_bool(info, r); + return true; +} + +static bool AOT_TNSPrimitives_methodWithSelector(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 1) return false; + SEL sel = @selector(methodWithSelector:); + bool callSuper = false; + id target = __ns_aot_get_target(info, sel, &sel, &callSuper); + if (target == nil) return false; + SEL a0 = NULL; + if (!__ns_aot_arg_selector(info, 0, &a0)) return true; + SEL r = NULL; + @try { + if (callSuper) { + struct objc_super sup = {target, class_getSuperclass(object_getClass(target))}; + r = ((SEL (*)(struct objc_super*, SEL, SEL))objc_msgSendSuper)(&sup, sel, a0); + } else { + r = ((SEL (*)(id, SEL, SEL))objc_msgSend)(target, sel, a0); + } + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + __ns_aot_return_selector(info, r); + return true; +} + +static bool AOT_TNSPrimitives_methodWithClass(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 1) return false; + SEL sel = @selector(methodWithClass:); + bool callSuper = false; + id target = __ns_aot_get_target(info, sel, &sel, &callSuper); + if (target == nil) return false; + Class a0 = Nil; + if (!__ns_aot_arg_class(info, 0, &a0)) return true; + Class r = Nil; + @try { + if (callSuper) { + struct objc_super sup = {target, class_getSuperclass(object_getClass(target))}; + r = ((Class (*)(struct objc_super*, SEL, Class))objc_msgSendSuper)(&sup, sel, a0); + } else { + r = ((Class (*)(id, SEL, Class))objc_msgSend)(target, sel, a0); + } + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + __ns_aot_return_class(info, r); + return true; +} + +static bool AOT_TNSPrimitives_methodWithNSNumber(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 1) return false; + SEL sel = @selector(methodWithNSNumber:); + bool callSuper = false; + id target = __ns_aot_get_target(info, sel, &sel, &callSuper); + if (target == nil) return false; + id a0 = nil; + if (!__ns_aot_arg_object(info, 0, &a0)) return true; + id r = nil; + @try { + if (callSuper) { + struct objc_super sup = {target, class_getSuperclass(object_getClass(target))}; + r = ((id (*)(struct objc_super*, SEL, id))objc_msgSendSuper)(&sup, sel, a0); + } else { + r = ((id (*)(id, SEL, id))objc_msgSend)(target, sel, a0); + } + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + __ns_aot_return_object(info, r, false, true); + return true; +} + +static bool AOT_TNSPrimitives_methodWithId_static(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 1) return false; + SEL sel = @selector(methodWithId:); + Class cls = __ns_aot_get_static_class(info); + if (cls == nil) return false; + id a0 = nil; + if (!__ns_aot_arg_object(info, 0, &a0)) return true; + id r = nil; + @try { + r = ((id (*)(id, SEL, id))objc_msgSend)((id)cls, sel, a0); + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + __ns_aot_return_object(info, r, false, true); + return true; +} + +static bool AOT_TNSObjCTypes_methodWithNSDate(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 1) return false; + SEL sel = @selector(methodWithNSDate:); + bool callSuper = false; + id target = __ns_aot_get_target(info, sel, &sel, &callSuper); + if (target == nil) return false; + id a0 = nil; + if (!__ns_aot_arg_object(info, 0, &a0)) return true; + id r = nil; + @try { + if (callSuper) { + struct objc_super sup = {target, class_getSuperclass(object_getClass(target))}; + r = ((id (*)(struct objc_super*, SEL, id))objc_msgSendSuper)(&sup, sel, a0); + } else { + r = ((id (*)(id, SEL, id))objc_msgSend)(target, sel, a0); + } + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + __ns_aot_return_object(info, r, false, true); + return true; +} + +static bool AOT_TNSTestNativeCallbacks_recordsSimpleStruct_static(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 1) return false; + static NSAOTStructType st0 = NULL; + if (!st0) st0 = __ns_aot_struct_type("TNSSimpleStruct"); + if (!st0) return false; + SEL sel = @selector(recordsSimpleStruct:); + Class cls = __ns_aot_get_static_class(info); + if (cls == nil) return false; + AOT_TNSSimpleStruct a0; + if (!__ns_aot_arg_struct(info, 0, st0, &a0)) return true; + AOT_TNSSimpleStruct r; + @try { + r = ((AOT_TNSSimpleStruct (*)(id, SEL, AOT_TNSSimpleStruct))AOT_SEND_STRET(AOT_TNSSimpleStruct))((id)cls, sel, a0); + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + __ns_aot_return_struct(info, st0, &r); + return true; +} + +static bool AOT_TNSTestNativeCallbacks_recordsNestedStruct_static(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 1) return false; + static NSAOTStructType st0 = NULL; + if (!st0) st0 = __ns_aot_struct_type("TNSNestedStruct"); + if (!st0) return false; + SEL sel = @selector(recordsNestedStruct:); + Class cls = __ns_aot_get_static_class(info); + if (cls == nil) return false; + AOT_TNSNestedStruct a0; + if (!__ns_aot_arg_struct(info, 0, st0, &a0)) return true; + AOT_TNSNestedStruct r; + @try { + r = ((AOT_TNSNestedStruct (*)(id, SEL, AOT_TNSNestedStruct))AOT_SEND_STRET(AOT_TNSNestedStruct))((id)cls, sel, a0); + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + __ns_aot_return_struct(info, st0, &r); + return true; +} + +static bool AOT_TNSTestNativeCallbacks_recordsStruct32_static(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 1) return false; + static NSAOTStructType st0 = NULL; + if (!st0) st0 = __ns_aot_struct_type("TNSStruct32"); + if (!st0) return false; + SEL sel = @selector(recordsStruct32:); + Class cls = __ns_aot_get_static_class(info); + if (cls == nil) return false; + AOT_TNSStruct32 a0; + if (!__ns_aot_arg_struct(info, 0, st0, &a0)) return true; + AOT_TNSStruct32 r; + @try { + r = ((AOT_TNSStruct32 (*)(id, SEL, AOT_TNSStruct32))AOT_SEND_STRET(AOT_TNSStruct32))((id)cls, sel, a0); + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + __ns_aot_return_struct(info, st0, &r); + return true; +} + +static bool AOT_TNSTestNativeCallbacks_recordsComplexStruct_static(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 1) return false; + static NSAOTStructType st0 = NULL; + if (!st0) st0 = __ns_aot_struct_type("TNSComplexStruct"); + if (!st0) return false; + SEL sel = @selector(recordsComplexStruct:); + Class cls = __ns_aot_get_static_class(info); + if (cls == nil) return false; + AOT_TNSComplexStruct a0; + if (!__ns_aot_arg_struct(info, 0, st0, &a0)) return true; + AOT_TNSComplexStruct r; + @try { + r = ((AOT_TNSComplexStruct (*)(id, SEL, AOT_TNSComplexStruct))AOT_SEND_STRET(AOT_TNSComplexStruct))((id)cls, sel, a0); + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + __ns_aot_return_struct(info, st0, &r); + return true; +} + +static bool AOT_RectClass_getRect(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 0) return false; + static NSAOTStructType st0 = NULL; + if (!st0) st0 = __ns_aot_struct_type("CGRect"); + if (!st0) return false; + SEL sel = @selector(getRect); + bool callSuper = false; + id target = __ns_aot_get_target(info, sel, &sel, &callSuper); + if (target == nil) return false; + AOT_CGRect r; + @try { + if (callSuper) { + struct objc_super sup = {target, class_getSuperclass(object_getClass(target))}; + r = ((AOT_CGRect (*)(struct objc_super*, SEL))AOT_SEND_SUPER_STRET(AOT_CGRect))(&sup, sel); + } else { + r = ((AOT_CGRect (*)(id, SEL))AOT_SEND_STRET(AOT_CGRect))(target, sel); + } + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + __ns_aot_return_struct(info, st0, &r); + return true; +} + +static bool AOT_UIScreen_mainScreen_static(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 0) return false; + SEL sel = @selector(mainScreen); + Class cls = __ns_aot_get_static_class(info); + if (cls == nil) return false; + id r = nil; + @try { + r = ((id (*)(id, SEL))objc_msgSend)((id)cls, sel); + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + __ns_aot_return_object(info, r, false, true); + return true; +} + +static bool AOT_UIScreen_scale(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 0) return false; + SEL sel = @selector(scale); + bool callSuper = false; + id target = __ns_aot_get_target(info, sel, &sel, &callSuper); + if (target == nil) return false; + double r = 0; + @try { + if (callSuper) { + struct objc_super sup = {target, class_getSuperclass(object_getClass(target))}; + r = ((double (*)(struct objc_super*, SEL))objc_msgSendSuper)(&sup, sel); + } else { + r = ((double (*)(id, SEL))objc_msgSend)(target, sel); + } + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + __ns_aot_return_double(info, r); + return true; +} + +static bool AOT_UIScreen_nativeScale(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 0) return false; + SEL sel = @selector(nativeScale); + bool callSuper = false; + id target = __ns_aot_get_target(info, sel, &sel, &callSuper); + if (target == nil) return false; + double r = 0; + @try { + if (callSuper) { + struct objc_super sup = {target, class_getSuperclass(object_getClass(target))}; + r = ((double (*)(struct objc_super*, SEL))objc_msgSendSuper)(&sup, sel); + } else { + r = ((double (*)(id, SEL))objc_msgSend)(target, sel); + } + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + __ns_aot_return_double(info, r); + return true; +} + +static bool AOT_UIScreen_bounds(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 0) return false; + static NSAOTStructType st0 = NULL; + if (!st0) st0 = __ns_aot_struct_type("CGRect"); + if (!st0) return false; + SEL sel = @selector(bounds); + bool callSuper = false; + id target = __ns_aot_get_target(info, sel, &sel, &callSuper); + if (target == nil) return false; + AOT_CGRect r; + @try { + if (callSuper) { + struct objc_super sup = {target, class_getSuperclass(object_getClass(target))}; + r = ((AOT_CGRect (*)(struct objc_super*, SEL))AOT_SEND_SUPER_STRET(AOT_CGRect))(&sup, sel); + } else { + r = ((AOT_CGRect (*)(id, SEL))AOT_SEND_STRET(AOT_CGRect))(target, sel); + } + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + __ns_aot_return_struct(info, st0, &r); + return true; +} + +static bool AOT_NSObject_description(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 0) return false; + SEL sel = @selector(description); + bool callSuper = false; + id target = __ns_aot_get_target(info, sel, &sel, &callSuper); + if (target == nil) return false; + id r = nil; + @try { + if (callSuper) { + struct objc_super sup = {target, class_getSuperclass(object_getClass(target))}; + r = ((id (*)(struct objc_super*, SEL))objc_msgSendSuper)(&sup, sel); + } else { + r = ((id (*)(id, SEL))objc_msgSend)(target, sel); + } + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + __ns_aot_return_object(info, r, false, true); + return true; +} + +static bool AOT_NSObject_respondsToSelector(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 1) return false; + SEL sel = @selector(respondsToSelector:); + bool callSuper = false; + id target = __ns_aot_get_target(info, sel, &sel, &callSuper); + if (target == nil) return false; + SEL a0 = NULL; + if (!__ns_aot_arg_selector(info, 0, &a0)) return true; + BOOL r = NO; + @try { + if (callSuper) { + struct objc_super sup = {target, class_getSuperclass(object_getClass(target))}; + r = ((BOOL (*)(struct objc_super*, SEL, SEL))objc_msgSendSuper)(&sup, sel, a0); + } else { + r = ((BOOL (*)(id, SEL, SEL))objc_msgSend)(target, sel, a0); + } + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + __ns_aot_return_bool(info, r); + return true; +} + +static bool AOT_NSObject_isKindOfClass(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 1) return false; + SEL sel = @selector(isKindOfClass:); + bool callSuper = false; + id target = __ns_aot_get_target(info, sel, &sel, &callSuper); + if (target == nil) return false; + Class a0 = Nil; + if (!__ns_aot_arg_class(info, 0, &a0)) return true; + BOOL r = NO; + @try { + if (callSuper) { + struct objc_super sup = {target, class_getSuperclass(object_getClass(target))}; + r = ((BOOL (*)(struct objc_super*, SEL, Class))objc_msgSendSuper)(&sup, sel, a0); + } else { + r = ((BOOL (*)(id, SEL, Class))objc_msgSend)(target, sel, a0); + } + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + __ns_aot_return_bool(info, r); + return true; +} + +__attribute__((visibility("default"))) +void __ns_register_aot_calls(void (*reg)(const char*, const char*, bool, NSAOTCallHandler)) { + reg("TNSBaseInterface", "baseMethod", false, AOT_TNSBaseInterface_baseMethod); + reg("TNSBaseInterface", "baseMethod", true, AOT_TNSBaseInterface_baseMethod_static); + reg("TNSBaseInterface", "methodWithParam:", false, AOT_TNSBaseInterface_methodWithParam); + reg("TNSDerivedInterface", "methodWithParam:", false, AOT_TNSBaseInterface_methodWithParam); + reg("TNSDerivedInterface", "methodWith:param:", false, AOT_TNSDerivedInterface_methodWith_param); + reg("TNSBaseInterface", "callBaseMethod:", false, AOT_TNSBaseInterface_callBaseMethod); + reg("TNSBaseInterface", "baseProperty", false, AOT_TNSBaseInterface_baseProperty); + reg("TNSBaseInterface", "setBaseProperty:", false, AOT_TNSBaseInterface_setBaseProperty); + reg("TNSBaseInterface", "baseProperty", true, AOT_TNSBaseInterface_baseProperty_static); + reg("TNSApi", "customGetter", false, AOT_TNSApi_customGetter); + reg("TNSApi", "methodThrowsException", false, AOT_TNSApi_methodThrowsException); + reg("TNSSwizzleKlass", "instanceMethod:", false, AOT_TNSSwizzleKlass_instanceMethod); + reg("TNSSwizzleKlass", "staticMethod:", true, AOT_TNSSwizzleKlass_staticMethod_static); + reg("TNSPrimitives", "methodWithChar:", false, AOT_TNSPrimitives_methodWithChar); + reg("TNSPrimitives", "methodWithUChar:", false, AOT_TNSPrimitives_methodWithUChar); + reg("TNSPrimitives", "methodWithInt:", false, AOT_TNSPrimitives_methodWithInt); + reg("TNSPrimitives", "methodWithFloat:", false, AOT_TNSPrimitives_methodWithFloat); + reg("TNSPrimitives", "methodWithDouble:", true, AOT_TNSPrimitives_methodWithDouble_static); + reg("TNSPrimitives", "methodWithBool2:", false, AOT_TNSPrimitives_methodWithBool2); + reg("TNSPrimitives", "methodWithBool3:", false, AOT_TNSPrimitives_methodWithBool3); + reg("TNSPrimitives", "methodWithSelector:", false, AOT_TNSPrimitives_methodWithSelector); + reg("TNSPrimitives", "methodWithClass:", false, AOT_TNSPrimitives_methodWithClass); + reg("TNSPrimitives", "methodWithNSNumber:", false, AOT_TNSPrimitives_methodWithNSNumber); + reg("TNSPrimitives", "methodWithId:", true, AOT_TNSPrimitives_methodWithId_static); + reg("TNSObjCTypes", "methodWithNSDate:", false, AOT_TNSObjCTypes_methodWithNSDate); + reg("TNSTestNativeCallbacks", "recordsSimpleStruct:", true, AOT_TNSTestNativeCallbacks_recordsSimpleStruct_static); + reg("TNSTestNativeCallbacks", "recordsNestedStruct:", true, AOT_TNSTestNativeCallbacks_recordsNestedStruct_static); + reg("TNSTestNativeCallbacks", "recordsStruct32:", true, AOT_TNSTestNativeCallbacks_recordsStruct32_static); + reg("TNSTestNativeCallbacks", "recordsComplexStruct:", true, AOT_TNSTestNativeCallbacks_recordsComplexStruct_static); + reg("RectClass", "getRect", false, AOT_RectClass_getRect); + reg("UIScreen", "mainScreen", true, AOT_UIScreen_mainScreen_static); + reg("UIScreen", "scale", false, AOT_UIScreen_scale); + reg("UIScreen", "nativeScale", false, AOT_UIScreen_nativeScale); + reg("UIScreen", "bounds", false, AOT_UIScreen_bounds); + reg("NSObject", "description", false, AOT_NSObject_description); + reg("NSObject", "respondsToSelector:", false, AOT_NSObject_respondsToSelector); + reg("NSObject", "isKindOfClass:", false, AOT_NSObject_isKindOfClass); +} diff --git a/TestRunner/AOT/README.md b/TestRunner/AOT/README.md new file mode 100644 index 000000000..8c1afdb67 --- /dev/null +++ b/TestRunner/AOT/README.md @@ -0,0 +1,13 @@ +# TestRunner AOT stubs + +`NativeScriptAOTStubs.m` is generated from `aot-config.json` and compiled into the TestRunner executable, where the runtime finds `__ns_register_aot_calls` with `dlsym` (the symbol is listed in `TestFixtures/exported-symbols.txt`). `AOTDirectCallsTests.js` exercises it. + +Regenerate after changing the config, the fixtures or the generator: + +```sh +python3 scripts/generate-aot.py TestRunner/AOT/aot-config.json -m /arm64 -o TestRunner/AOT/NativeScriptAOTStubs.m --report +``` + +`` is the directory the metadata generator writes when the TestRunner is built with `NS_JSON_METADATA_PATH=` set (see `metadata-generator/build-step-metadata-generator.py`). + +The config's trailing entries are deliberately unsupported (initializer, NSError out-parameter, variadic, block, typed pointer, unknown selector or class) so the generator's skip paths stay covered. diff --git a/TestRunner/AOT/aot-config.json b/TestRunner/AOT/aot-config.json new file mode 100644 index 000000000..9bc4fd5c8 --- /dev/null +++ b/TestRunner/AOT/aot-config.json @@ -0,0 +1,53 @@ +{ + "methods": [ + {"class": "TNSBaseInterface", "selector": "baseMethod"}, + {"class": "TNSBaseInterface", "selector": "baseMethod", "static": true}, + {"class": "TNSDerivedInterface", "selector": "methodWithParam:"}, + {"class": "TNSDerivedInterface", "selector": "methodWith:param:"}, + {"class": "TNSBaseInterface", "selector": "callBaseMethod:"}, + {"class": "TNSBaseInterface", "selector": "baseProperty"}, + {"class": "TNSBaseInterface", "selector": "setBaseProperty:"}, + {"class": "TNSBaseInterface", "selector": "baseProperty", "static": true}, + {"class": "TNSApi", "selector": "customGetter"}, + {"class": "TNSApi", "selector": "methodThrowsException"}, + {"class": "TNSSwizzleKlass", "selector": "instanceMethod:"}, + {"class": "TNSSwizzleKlass", "selector": "staticMethod:", "static": true}, + {"class": "TNSPrimitives", "selector": "methodWithChar:"}, + {"class": "TNSPrimitives", "selector": "methodWithUChar:"}, + {"class": "TNSPrimitives", "selector": "methodWithInt:"}, + {"class": "TNSPrimitives", "selector": "methodWithULongLong:"}, + {"class": "TNSPrimitives", "selector": "methodWithLongLong:", "static": true}, + {"class": "TNSPrimitives", "selector": "methodWithFloat:"}, + {"class": "TNSPrimitives", "selector": "methodWithDouble:", "static": true}, + {"class": "TNSPrimitives", "selector": "methodWithBool2:"}, + {"class": "TNSPrimitives", "selector": "methodWithBool3:"}, + {"class": "TNSPrimitives", "selector": "methodWithSelector:"}, + {"class": "TNSPrimitives", "selector": "methodWithClass:"}, + {"class": "TNSPrimitives", "selector": "methodWithNSNumber:"}, + {"class": "TNSPrimitives", "selector": "methodWithId:", "static": true}, + {"class": "TNSObjCTypes", "selector": "methodWithNSDate:"}, + {"class": "TNSTestNativeCallbacks", "selector": "recordsSimpleStruct:", "static": true}, + {"class": "TNSTestNativeCallbacks", "selector": "recordsNestedStruct:", "static": true}, + {"class": "TNSTestNativeCallbacks", "selector": "recordsStruct32:", "static": true}, + {"class": "TNSTestNativeCallbacks", "selector": "recordsComplexStruct:", "static": true}, + {"class": "RectClass", "selector": "getRect"}, + + {"class": "UIScreen", "selector": "mainScreen", "static": true}, + {"class": "UIScreen", "selector": "scale"}, + {"class": "UIScreen", "selector": "nativeScale"}, + {"class": "UIScreen", "selector": "bounds"}, + {"class": "NSObject", "selector": "description"}, + {"class": "NSObject", "selector": "respondsToSelector:"}, + {"class": "NSObject", "selector": "isKindOfClass:"}, + + {"class": "TNSBaseInterface", "selector": "initBaseMethod"}, + {"class": "TNSApi", "selector": "method:error:"}, + {"class": "NSArray", "selector": "arrayWithObjects:", "static": true}, + {"class": "TNSObjCTypes", "selector": "methodWithSimpleBlock:"}, + {"class": "TNSObjCTypes", "selector": "methodWithLongLongOutParameter:"}, + {"class": "TNSPrimitives", "selector": "methodWithUnichar:"}, + {"class": "TNSPrimitives", "selector": "methodWithProtocol:"}, + {"class": "TNSPrimitives", "selector": "noSuchSelector"}, + {"class": "TNSNoSuchClass", "selector": "foo"} + ] +} diff --git a/TestRunner/app/tests/AOTDirectCallsTests.js b/TestRunner/app/tests/AOTDirectCallsTests.js new file mode 100644 index 000000000..6cb7af8a2 --- /dev/null +++ b/TestRunner/app/tests/AOTDirectCallsTests.js @@ -0,0 +1,378 @@ +// Stubs come from TestRunner/AOT/aot-config.json. Members asserted as generic +// here must stay out of that config. +describe(module.id, function () { + function stats() { + return __native_call_profiler.aotStats(); + } + + function measure(fn) { + var before = stats(); + var value, error; + try { + value = fn(); + } catch (e) { + error = e; + } + var after = stats(); + return { + value: value, + error: error, + served: after.served - before.served, + declined: after.declined - before.declined + }; + } + + // The counters only move while the profiler runs. + beforeEach(function () { + __native_call_profiler.start(); + }); + + afterEach(function () { + __native_call_profiler.stop(); + TNSClearOutput(); + }); + + describe("aotStats", function () { + it("returns served and declined counters", function () { + var r = measure(function () { return TNSPrimitives.methodWithDouble(1); }); + expect(r.served).toBe(1); + var s = stats(); + expect(Object.keys(s).sort()).toEqual(["declined", "served"]); + expect(Number.isInteger(s.served)).toBe(true); + expect(Number.isInteger(s.declined)).toBe(true); + expect(s.served).toBeGreaterThan(0); + }); + + it("does not count itself", function () { + var r = measure(function () { return stats(); }); + expect(r.served).toBe(0); + expect(r.declined).toBe(0); + }); + }); + + describe("members", function () { + it("instance method", function () { + var instance = TNSBaseInterface.alloc().init(); + var r = measure(function () { instance.callBaseMethod(false); }); + expect(r.served).toBe(1); + expect(TNSGetOutput()).toBe("instance baseMethod called"); + }); + + it("inherited method on a derived instance", function () { + var instance = TNSDerivedInterface.alloc().init(); + var r = measure(function () { instance.callBaseMethod(true); }); + expect(r.served).toBe(1); + expect(TNSGetOutput()).toBe("derived overloaded instance baseMethod: called"); + }); + + it("NSObject members on a fixture instance", function () { + var instance = TNSPrimitives.alloc().init(); + var r = measure(function () { + return [ + instance.description, + instance.isKindOfClass(NSObject), + instance.isKindOfClass(NSString), + instance.respondsToSelector("methodWithInt:"), + instance.respondsToSelector("noSuchSelector") + ]; + }); + expect(r.served).toBe(5); + expect(r.value[0]).toMatch(/^$/); + expect(r.value.slice(1)).toEqual([true, false, true, false]); + }); + + it("ancestor stub binds where a protocol re-registers the member", function () { + var screen = UIScreen.mainScreen; + var r = measure(function () { return screen.respondsToSelector("scale"); }); + expect(r.served).toBe(1); + expect(r.value).toBe(true); + }); + + it("NSObject member on a derived fixture instance", function () { + var instance = TNSDerivedInterface.alloc().init(); + var r = measure(function () { return instance.description; }); + expect(r.served).toBe(1); + expect(r.value).toMatch(/^$/); + }); + + it("static method", function () { + var r = measure(function () { return TNSPrimitives.methodWithDouble(1.7976931348623157e+308); }); + expect(r.served).toBe(1); + expect(r.value).toBe(1.7976931348623157e+308); + }); + + it("instance property getter", function () { + var instance = TNSBaseInterface.alloc().init(); + var r = measure(function () { + instance.baseProperty = 1; + return instance.baseProperty; + }); + expect(r.served).toBe(1); + expect(r.value).toBe(0); + expect(TNSGetOutput()).toBe("instance setBaseProperty: calledinstance baseProperty called"); + }); + + it("custom-named property getter", function () { + var object = new TNSApi(); + object.property = 3; + var r = measure(function () { return object.property; }); + expect(r.served).toBe(1); + expect(r.value).toBe(3); + }); + + it("static property getter", function () { + var r = measure(function () { return TNSBaseInterface.baseProperty; }); + expect(r.served).toBe(1); + expect(r.value).toBe(0); + expect(TNSGetOutput()).toBe("static baseProperty called"); + }); + + it("static property getter through a derived constructor", function () { + var r = measure(function () { return TNSDerivedInterface.baseProperty; }); + expect(r.served).toBe(1); + expect(TNSGetOutput()).toBe("static baseProperty called"); + }); + + it("UIScreen class property and instance getters", function () { + var r = measure(function () { + var screen = UIScreen.mainScreen; + return { screen: screen, scale: screen.scale, nativeScale: screen.nativeScale, bounds: screen.bounds }; + }); + expect(r.served).toBe(4); + expect(r.value.screen instanceof UIScreen).toBe(true); + expect(r.value.scale).toBeGreaterThan(0); + expect(r.value.nativeScale).toBeGreaterThan(0); + expect(r.value.bounds.size.width).toBeGreaterThan(0); + expect(r.value.bounds.size.height).toBeGreaterThan(0); + }); + }); + + describe("structs", function () { + it("argument and return round trip from a literal", function () { + var r = measure(function () { return TNSTestNativeCallbacks.recordsSimpleStruct({ x: 7, y: 8 }); }); + expect(r.served).toBe(1); + expect(TNSGetOutput()).toBe("7 8"); + expect(r.value.x).toBe(7); + expect(r.value.y).toBe(8); + }); + + it("argument from a struct wrapper", function () { + var record = new TNSSimpleStruct(); + record.x = 7; + record.y = 8; + var r = measure(function () { return TNSTestNativeCallbacks.recordsSimpleStruct(record); }); + expect(r.served).toBe(1); + expect(TNSGetOutput()).toBe("7 8"); + expect(r.value.x).toBe(7); + expect(r.value.y).toBe(8); + }); + + it("nested struct round trip", function () { + var record = new TNSNestedStruct({ a: { x: 1, y: 2 }, b: { x: 3, y: 4 } }); + var r = measure(function () { return TNSTestNativeCallbacks.recordsNestedStruct(record); }); + expect(r.served).toBe(1); + expect(TNSGetOutput()).toBe("1 2 3 4"); + expect(r.value.b.y).toBe(4); + }); + + it("struct return from a protocol method", function () { + var object = RectClass.new(); + var r = measure(function () { return object.getRect(); }); + expect(r.served).toBe(1); + expect(r.value.origin.x).toBe(1); + expect(r.value.origin.y).toBe(2); + expect(r.value.size.width).toBe(3); + expect(r.value.size.height).toBe(4); + }); + }); + + describe("arguments", function () { + var primitives; + beforeEach(function () { + primitives = TNSPrimitives.alloc().init(); + }); + + it("object", function () { + var r = measure(function () { return primitives.methodWithNSNumber(0); }); + expect(r.served).toBe(1); + expect(r.value).toBe(0); + expect(TNSGetOutput()).toBe("0"); + }); + + it("object returned as the same wrapper", function () { + var object = NSObject.new(); + var r = measure(function () { return TNSPrimitives.methodWithId(object); }); + expect(r.served).toBe(1); + expect(r.value).toBe(object); + }); + + it("NSDate", function () { + var objcTypes = TNSObjCTypes.alloc().init(); + var r = measure(function () { return objcTypes.methodWithNSDate(new Date(1e12)); }); + expect(r.served).toBe(1); + expect(r.value).toEqual(new Date(1e12)); + }); + + it("SEL", function () { + var r = measure(function () { return primitives.methodWithSelector("init"); }); + expect(r.served).toBe(1); + expect(r.value).toBe("init"); + expect(TNSGetOutput()).toBe("init"); + }); + + it("Class", function () { + var r = measure(function () { return primitives.methodWithClass(NSObject); }); + expect(r.served).toBe(1); + expect(r.value).toBe(NSObject); + expect(TNSGetOutput()).toBe("NSObject"); + }); + + it("integers", function () { + var r = measure(function () { + return [primitives.methodWithChar(-128), primitives.methodWithUChar(255), + primitives.methodWithInt(-2147483648)]; + }); + expect(r.served).toBe(3); + expect(r.value).toEqual([-128, 255, -2147483648]); + expect(TNSGetOutput()).toBe("-128255-2147483648"); + }); + + it("float", function () { + var r = measure(function () { return primitives.methodWithFloat(3.40282347e+38); }); + expect(r.served).toBe(1); + expect(r.value).toBe(3.4028234663852886e+38); + }); + + it("booleans", function () { + var r = measure(function () { + return [primitives.methodWithBool2(true), primitives.methodWithBool3(false)]; + }); + expect(r.served).toBe(2); + expect(r.value).toEqual([true, false]); + expect(TNSGetOutput()).toBe("10"); + }); + }); + + describe("exceptions", function () { + it("NSException matches the generic path", function () { + var api = TNSApi.alloc().init(); + var stubbed = measure(function () { api.methodThrowsException(); }); + var generic = measure(function () { TNSApi.methodThrowsException(); }); + + expect(stubbed.served).toBe(1); + expect(generic.served + generic.declined).toBe(0); + + expect(stubbed.error instanceof Error).toBe(true); + expect(generic.error instanceof Error).toBe(true); + expect(stubbed.error.name).toBe("NSGenericException"); + expect(stubbed.error.message).toBe("No reason"); + expect(stubbed.error.name).toBe(generic.error.name); + expect(stubbed.error.message).toBe(generic.error.message); + expect(Object.keys(stubbed.error).sort()).toEqual(Object.keys(generic.error).sort()); + + var nativeException = stubbed.error.nativeException; + expect(nativeException instanceof NSException).toBe(true); + expect(nativeException.name).toBe("NSGenericException"); + expect(nativeException.reason).toBe("No reason"); + }); + }); + + describe("JS-extended classes", function () { + it("override calling super reaches the native method once", function () { + var overrideCalls = 0; + var JSBase = TNSBaseInterface.extend({ + callBaseMethod: function (withArgs) { + overrideCalls++; + return TNSBaseInterface.prototype.callBaseMethod.call(this, withArgs); + } + }); + var instance = JSBase.alloc().init(); + var r = measure(function () { instance.callBaseMethod(false); }); + expect(r.error).toBeUndefined(); + expect(overrideCalls).toBe(1); + expect(r.served).toBe(1); + expect(TNSGetOutput()).toBe("instance baseMethod called"); + }); + + it("getter override calling the base getter", function () { + var JSApi = TNSApi.extend({ + get property() { + return -Object.getOwnPropertyDescriptor(TNSApi.prototype, "property").get.call(this); + }, + set property(x) { + Object.getOwnPropertyDescriptor(TNSApi.prototype, "property").set.call(this, x * 2); + } + }); + var object = new JSApi(); + object.property = 3; + var r = measure(function () { return object.property; }); + expect(r.served).toBe(1); + expect(r.value).toBe(-6); + }); + + it("non-overridden method on an extended instance", function () { + var instance = TNSPrimitives.extend({}).alloc().init(); + var r = measure(function () { return instance.methodWithInt(5); }); + expect(r.served).toBe(1); + expect(r.value).toBe(5); + }); + }); + + describe("declines", function () { + it("missing argument throws the generic error", function () { + var primitives = TNSPrimitives.alloc().init(); + var stubbed = measure(function () { return primitives.methodWithInt(); }); + var generic = measure(function () { return primitives.methodWithShort(); }); + expect(stubbed.declined).toBe(1); + expect(stubbed.served).toBe(0); + expect(generic.served + generic.declined).toBe(0); + expect(stubbed.error).toBeDefined(); + expect(String(stubbed.error)).toBe(String(generic.error)); + }); + + it("extra argument throws the generic error", function () { + var primitives = TNSPrimitives.alloc().init(); + var stubbed = measure(function () { return primitives.methodWithInt(1, 2); }); + var generic = measure(function () { return primitives.methodWithShort(1, 2); }); + expect(stubbed.declined).toBe(1); + expect(stubbed.error).toBeDefined(); + expect(String(stubbed.error)).toBe(String(generic.error)); + }); + + it("overload with another argument count resolves on the generic path", function () { + var instance = TNSDerivedInterface.alloc().init(); + var oneArg = measure(function () { instance.methodWithParam(1); }); + expect(oneArg.declined).toBe(1); + expect(oneArg.served).toBe(0); + expect(TNSGetOutput()).toBe("instance methodWithParam: called with 1 param"); + + TNSClearOutput(); + var twoArgs = measure(function () { instance.methodWithParam(1, 2); }); + expect(twoArgs.served).toBe(1); + expect(TNSGetOutput()).toBe("instance methodWith:param: called with 2 params"); + }); + + it("getter with a non-native receiver returns undefined", function () { + var stubbedGet = Object.getOwnPropertyDescriptor(TNSApi.prototype, "property").get; + var genericGet = Object.getOwnPropertyDescriptor(TNSBaseInterface.prototype, "baseReadOnlyProperty").get; + [{}, 5].forEach(function (receiver) { + var stubbed = measure(function () { return stubbedGet.call(receiver); }); + var generic = measure(function () { return genericGet.call(receiver); }); + expect(stubbed.declined).toBe(1); + expect(stubbed.error).toBeUndefined(); + expect(stubbed.value).toBeUndefined(); + expect(generic.error).toBeUndefined(); + expect(generic.value).toBeUndefined(); + }); + }); + + it("static method with a non-class receiver", function () { + var method = TNSPrimitives.methodWithDouble; + var r = measure(function () { return method.call({}, 1.5); }); + expect(r.declined).toBe(1); + expect(r.served).toBe(0); + expect(r.error).toBeUndefined(); + expect(r.value).toBe(1.5); + }); + }); +}); diff --git a/TestRunner/app/tests/index.js b/TestRunner/app/tests/index.js index 31bfe10a5..5b9c6ee09 100644 --- a/TestRunner/app/tests/index.js +++ b/TestRunner/app/tests/index.js @@ -120,6 +120,7 @@ require("./Inheritance/ProtocolImplementationTests"); require("./Inheritance/TypeScriptTests"); // require("./MethodCallsTests"); +require("./AOTDirectCallsTests"); require("./StaleWrapperCacheTests"); //import "./FunctionsTests"; require("./VersionDiffTests"); diff --git a/v8ios.xcodeproj/project.pbxproj b/v8ios.xcodeproj/project.pbxproj index 8d4bac3ae..7d15666cc 100644 --- a/v8ios.xcodeproj/project.pbxproj +++ b/v8ios.xcodeproj/project.pbxproj @@ -273,6 +273,11 @@ C2DDEB8E229EAC8300345BFE /* Interop.h in Headers */ = {isa = PBXBuildFile; fileRef = C2DDEB65229EAC8100345BFE /* Interop.h */; }; C2DDEB8F229EAC8300345BFE /* ArgConverter.h in Headers */ = {isa = PBXBuildFile; fileRef = C2DDEB66229EAC8100345BFE /* ArgConverter.h */; }; C2DDEB90229EAC8300345BFE /* MetadataBuilder.h in Headers */ = {isa = PBXBuildFile; fileRef = C2DDEB67229EAC8100345BFE /* MetadataBuilder.h */; }; + 35550BC20A65733EB364EF65 /* AOTCalls.h in Headers */ = {isa = PBXBuildFile; fileRef = 5BDF867F4303941D505BD0D7 /* AOTCalls.h */; }; + 734C2098143923198254BB11 /* NativeScriptAOTBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9FF02F7EF762E15F74F91E42 /* NativeScriptAOTBridge.mm */; }; + 404F17E0B24AFAC78D4A86EB /* MethodCallProfiler.h in Headers */ = {isa = PBXBuildFile; fileRef = 97B4A35271DB242D64E467D1 /* MethodCallProfiler.h */; }; + 20CF1F3B8B1364F5743665BE /* MethodCallProfiler.mm in Sources */ = {isa = PBXBuildFile; fileRef = FF50F7CA10E462BE9CDEC3F6 /* MethodCallProfiler.mm */; }; + F3759DC6A4C5420F6B4BDA2F /* NativeScriptAOT.h in Headers */ = {isa = PBXBuildFile; fileRef = 1400A6C0FE41C83FF782CF6E /* NativeScriptAOT.h */; settings = {ATTRIBUTES = (Public, ); }; }; C2DDEB91229EAC8300345BFE /* FFICall.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C2DDEB68229EAC8100345BFE /* FFICall.cpp */; }; C2DDEB92229EAC8300345BFE /* WeakRef.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C2DDEB69229EAC8100345BFE /* WeakRef.cpp */; }; 4A5C201A2E2B000100000006 /* BuiltinLoader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4A5C201A2E2B000100000001 /* BuiltinLoader.cpp */; }; @@ -316,6 +321,7 @@ C2DDEBB4229EAC8300345BFE /* DictionaryAdapter.mm in Sources */ = {isa = PBXBuildFile; fileRef = C2DDEB8B229EAC8300345BFE /* DictionaryAdapter.mm */; }; C2DDEBB5229EAC8300345BFE /* Tasks.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C2DDEB8C229EAC8300345BFE /* Tasks.cpp */; }; C2DDEBDC229EB37A00345BFE /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = C2DDEBDB229EB37A00345BFE /* main.m */; }; + CA38E3620BA8910F9CA06043 /* NativeScriptAOTStubs.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D3A17968C10D5BB75B88EAB /* NativeScriptAOTStubs.m */; }; C2DDEBDE229EB4A900345BFE /* app in Resources */ = {isa = PBXBuildFile; fileRef = C2DDEBDD229EB4A900345BFE /* app */; }; C2F4D0AD232F85E20008A2EB /* SymbolIterator.mm in Sources */ = {isa = PBXBuildFile; fileRef = C2F4D0AB232F85E20008A2EB /* SymbolIterator.mm */; }; C2F4D0AE232F85E20008A2EB /* SymbolIterator.h in Headers */ = {isa = PBXBuildFile; fileRef = C2F4D0AC232F85E20008A2EB /* SymbolIterator.h */; }; @@ -792,6 +798,11 @@ C2DDEB65229EAC8100345BFE /* Interop.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Interop.h; sourceTree = ""; }; C2DDEB66229EAC8100345BFE /* ArgConverter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ArgConverter.h; sourceTree = ""; }; C2DDEB67229EAC8100345BFE /* MetadataBuilder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MetadataBuilder.h; sourceTree = ""; }; + 5BDF867F4303941D505BD0D7 /* AOTCalls.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AOTCalls.h; sourceTree = ""; }; + 9FF02F7EF762E15F74F91E42 /* NativeScriptAOTBridge.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = NativeScriptAOTBridge.mm; sourceTree = ""; }; + 97B4A35271DB242D64E467D1 /* MethodCallProfiler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MethodCallProfiler.h; sourceTree = ""; }; + FF50F7CA10E462BE9CDEC3F6 /* MethodCallProfiler.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MethodCallProfiler.mm; sourceTree = ""; }; + 1400A6C0FE41C83FF782CF6E /* NativeScriptAOT.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NativeScriptAOT.h; sourceTree = ""; }; C2DDEB68229EAC8100345BFE /* FFICall.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FFICall.cpp; sourceTree = ""; }; C2DDEB69229EAC8100345BFE /* WeakRef.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = WeakRef.cpp; sourceTree = ""; }; 4A5C201A2E2B000100000001 /* BuiltinLoader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BuiltinLoader.cpp; sourceTree = ""; }; @@ -844,6 +855,7 @@ C2DDEB8B229EAC8300345BFE /* DictionaryAdapter.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DictionaryAdapter.mm; sourceTree = ""; }; C2DDEB8C229EAC8300345BFE /* Tasks.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Tasks.cpp; sourceTree = ""; }; C2DDEBDB229EB37A00345BFE /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 1D3A17968C10D5BB75B88EAB /* NativeScriptAOTStubs.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NativeScriptAOTStubs.m; sourceTree = ""; }; C2DDEBDD229EB4A900345BFE /* app */ = {isa = PBXFileReference; lastKnownFileType = folder; path = app; sourceTree = ""; }; C2F4CBB322C60BFD0036B56F /* metadata-generator */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "metadata-generator"; sourceTree = BUILT_PRODUCTS_DIR; }; C2F4D0AB232F85E20008A2EB /* SymbolIterator.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SymbolIterator.mm; sourceTree = ""; }; @@ -1422,6 +1434,7 @@ children = ( C2DDEBDD229EB4A900345BFE /* app */, C2DDEBDA229EB37A00345BFE /* Source Files */, + D942047BA67E24E9ECF511AC /* AOT */, C2DDEB26229EA89200345BFE /* Info.plist */, C266567722AA405100EE15CC /* Default-568h@2x.png */, ); @@ -1508,6 +1521,11 @@ C2DDEB70229EAC8200345BFE /* Metadata.mm */, C2DDEB67229EAC8100345BFE /* MetadataBuilder.h */, C2DDEB71229EAC8200345BFE /* MetadataBuilder.mm */, + 5BDF867F4303941D505BD0D7 /* AOTCalls.h */, + 9FF02F7EF762E15F74F91E42 /* NativeScriptAOTBridge.mm */, + 97B4A35271DB242D64E467D1 /* MethodCallProfiler.h */, + FF50F7CA10E462BE9CDEC3F6 /* MethodCallProfiler.mm */, + 1400A6C0FE41C83FF782CF6E /* NativeScriptAOT.h */, C2DDEB89229EAC8300345BFE /* ModuleInternal.h */, AA8C47AF2E27114300649BF5 /* ModuleInternalCallbacks.h */, AA8C47B02E27114300649BF5 /* ModuleInternalCallbacks.mm */, @@ -1601,6 +1619,14 @@ path = "Source Files"; sourceTree = ""; }; + D942047BA67E24E9ECF511AC /* AOT */ = { + isa = PBXGroup; + children = ( + 1D3A17968C10D5BB75B88EAB /* NativeScriptAOTStubs.m */, + ); + path = AOT; + sourceTree = ""; + }; C2FF3016225203FD00933782 /* Frameworks */ = { isa = PBXGroup; children = ( @@ -1695,6 +1721,9 @@ 3C48F68D2F57905500C14231 /* json.hpp in Headers */, C247C17022F82842001D2CA2 /* libffi.h in Headers */, C2DDEB90229EAC8300345BFE /* MetadataBuilder.h in Headers */, + 35550BC20A65733EB364EF65 /* AOTCalls.h in Headers */, + 404F17E0B24AFAC78D4A86EB /* MethodCallProfiler.h in Headers */, + F3759DC6A4C5420F6B4BDA2F /* NativeScriptAOT.h in Headers */, C266569F22B282BA00EE15CC /* FunctionReference.h in Headers */, C2FEA17022A3C75C00A5C0FC /* InlineFunctions.h in Headers */, C2DDEBA1229EAC8300345BFE /* FFICall.h in Headers */, @@ -2289,6 +2318,7 @@ buildActionMask = 2147483647; files = ( C2DDEBDC229EB37A00345BFE /* main.m in Sources */, + CA38E3620BA8910F9CA06043 /* NativeScriptAOTStubs.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2339,6 +2369,8 @@ C298C027233C9AEA000DDF54 /* TSHelpers.cpp in Sources */, C2FEA16F22A3C75C00A5C0FC /* InlineFunctions.cpp in Sources */, C2DDEB9A229EAC8300345BFE /* MetadataBuilder.mm in Sources */, + 734C2098143923198254BB11 /* NativeScriptAOTBridge.mm in Sources */, + 20CF1F3B8B1364F5743665BE /* MethodCallProfiler.mm in Sources */, C266569722AFFFB000EE15CC /* Reference.cpp in Sources */, 6573B9EE291FE5B700B0ED7C /* JSIRuntime.m in Sources */, F6191AB229C0FCE8003F588F /* InspectorServer.mm in Sources */, From 827755a60eaf958520adfbb4b25f57a930f8f0f3 Mon Sep 17 00:00:00 2001 From: Eduardo Speroni Date: Thu, 8 Oct 2026 16:10:32 -0300 Subject: [PATCH 4/4] test(aot): stub common Foundation and UIKit calls in the TestRunner config --- TestRunner/AOT/NativeScriptAOTStubs.m | 370 ++++++++++++++++++++++++++ TestRunner/AOT/aot-config.json | 16 ++ 2 files changed, 386 insertions(+) diff --git a/TestRunner/AOT/NativeScriptAOTStubs.m b/TestRunner/AOT/NativeScriptAOTStubs.m index 9ede5f81f..47a812b1d 100644 --- a/TestRunner/AOT/NativeScriptAOTStubs.m +++ b/TestRunner/AOT/NativeScriptAOTStubs.m @@ -844,6 +844,356 @@ static bool AOT_NSObject_isKindOfClass(NSAOTCallInfo info) { return true; } +static bool AOT_NSObject_isEqual(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 1) return false; + SEL sel = @selector(isEqual:); + bool callSuper = false; + id target = __ns_aot_get_target(info, sel, &sel, &callSuper); + if (target == nil) return false; + id a0 = nil; + if (!__ns_aot_arg_object(info, 0, &a0)) return true; + BOOL r = NO; + @try { + if (callSuper) { + struct objc_super sup = {target, class_getSuperclass(object_getClass(target))}; + r = ((BOOL (*)(struct objc_super*, SEL, id))objc_msgSendSuper)(&sup, sel, a0); + } else { + r = ((BOOL (*)(id, SEL, id))objc_msgSend)(target, sel, a0); + } + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + __ns_aot_return_bool(info, r); + return true; +} + +static bool AOT_NSArray_count(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 0) return false; + SEL sel = @selector(count); + bool callSuper = false; + id target = __ns_aot_get_target(info, sel, &sel, &callSuper); + if (target == nil) return false; + unsigned long r = 0; + @try { + if (callSuper) { + struct objc_super sup = {target, class_getSuperclass(object_getClass(target))}; + r = ((unsigned long (*)(struct objc_super*, SEL))objc_msgSendSuper)(&sup, sel); + } else { + r = ((unsigned long (*)(id, SEL))objc_msgSend)(target, sel); + } + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + __ns_aot_return_uint64(info, (uint64_t)r); + return true; +} + +static bool AOT_NSArray_objectAtIndex(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 1) return false; + SEL sel = @selector(objectAtIndex:); + bool callSuper = false; + id target = __ns_aot_get_target(info, sel, &sel, &callSuper); + if (target == nil) return false; + uint64_t raw0 = 0; + if (!__ns_aot_arg_uint64(info, 0, &raw0)) return true; + unsigned long a0 = (unsigned long)raw0; + id r = nil; + @try { + if (callSuper) { + struct objc_super sup = {target, class_getSuperclass(object_getClass(target))}; + r = ((id (*)(struct objc_super*, SEL, unsigned long))objc_msgSendSuper)(&sup, sel, a0); + } else { + r = ((id (*)(id, SEL, unsigned long))objc_msgSend)(target, sel, a0); + } + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + __ns_aot_return_object(info, r, false, true); + return true; +} + +static bool AOT_NSMutableArray_addObject(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 1) return false; + SEL sel = @selector(addObject:); + bool callSuper = false; + id target = __ns_aot_get_target(info, sel, &sel, &callSuper); + if (target == nil) return false; + id a0 = nil; + if (!__ns_aot_arg_object(info, 0, &a0)) return true; + @try { + if (callSuper) { + struct objc_super sup = {target, class_getSuperclass(object_getClass(target))}; + ((void (*)(struct objc_super*, SEL, id))objc_msgSendSuper)(&sup, sel, a0); + } else { + ((void (*)(id, SEL, id))objc_msgSend)(target, sel, a0); + } + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + return true; +} + +static bool AOT_NSMutableArray_removeLastObject(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 0) return false; + SEL sel = @selector(removeLastObject); + bool callSuper = false; + id target = __ns_aot_get_target(info, sel, &sel, &callSuper); + if (target == nil) return false; + @try { + if (callSuper) { + struct objc_super sup = {target, class_getSuperclass(object_getClass(target))}; + ((void (*)(struct objc_super*, SEL))objc_msgSendSuper)(&sup, sel); + } else { + ((void (*)(id, SEL))objc_msgSend)(target, sel); + } + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + return true; +} + +static bool AOT_NSArray_indexOfObject(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 1) return false; + SEL sel = @selector(indexOfObject:); + bool callSuper = false; + id target = __ns_aot_get_target(info, sel, &sel, &callSuper); + if (target == nil) return false; + id a0 = nil; + if (!__ns_aot_arg_object(info, 0, &a0)) return true; + unsigned long r = 0; + @try { + if (callSuper) { + struct objc_super sup = {target, class_getSuperclass(object_getClass(target))}; + r = ((unsigned long (*)(struct objc_super*, SEL, id))objc_msgSendSuper)(&sup, sel, a0); + } else { + r = ((unsigned long (*)(id, SEL, id))objc_msgSend)(target, sel, a0); + } + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + __ns_aot_return_uint64(info, (uint64_t)r); + return true; +} + +static bool AOT_NSDictionary_objectForKey(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 1) return false; + SEL sel = @selector(objectForKey:); + bool callSuper = false; + id target = __ns_aot_get_target(info, sel, &sel, &callSuper); + if (target == nil) return false; + id a0 = nil; + if (!__ns_aot_arg_object(info, 0, &a0)) return true; + id r = nil; + @try { + if (callSuper) { + struct objc_super sup = {target, class_getSuperclass(object_getClass(target))}; + r = ((id (*)(struct objc_super*, SEL, id))objc_msgSendSuper)(&sup, sel, a0); + } else { + r = ((id (*)(id, SEL, id))objc_msgSend)(target, sel, a0); + } + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + __ns_aot_return_object(info, r, false, true); + return true; +} + +static bool AOT_NSString_length(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 0) return false; + SEL sel = @selector(length); + bool callSuper = false; + id target = __ns_aot_get_target(info, sel, &sel, &callSuper); + if (target == nil) return false; + unsigned long r = 0; + @try { + if (callSuper) { + struct objc_super sup = {target, class_getSuperclass(object_getClass(target))}; + r = ((unsigned long (*)(struct objc_super*, SEL))objc_msgSendSuper)(&sup, sel); + } else { + r = ((unsigned long (*)(id, SEL))objc_msgSend)(target, sel); + } + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + __ns_aot_return_uint64(info, (uint64_t)r); + return true; +} + +static bool AOT_NSString_isEqualToString(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 1) return false; + SEL sel = @selector(isEqualToString:); + bool callSuper = false; + id target = __ns_aot_get_target(info, sel, &sel, &callSuper); + if (target == nil) return false; + id a0 = nil; + if (!__ns_aot_arg_object(info, 0, &a0)) return true; + BOOL r = NO; + @try { + if (callSuper) { + struct objc_super sup = {target, class_getSuperclass(object_getClass(target))}; + r = ((BOOL (*)(struct objc_super*, SEL, id))objc_msgSendSuper)(&sup, sel, a0); + } else { + r = ((BOOL (*)(id, SEL, id))objc_msgSend)(target, sel, a0); + } + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + __ns_aot_return_bool(info, r); + return true; +} + +static bool AOT_NSNumber_numberWithInt_static(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 1) return false; + SEL sel = @selector(numberWithInt:); + Class cls = __ns_aot_get_static_class(info); + if (cls == nil) return false; + int64_t raw0 = 0; + if (!__ns_aot_arg_int64(info, 0, &raw0)) return true; + int a0 = (int)raw0; + id r = nil; + @try { + r = ((id (*)(id, SEL, int))objc_msgSend)((id)cls, sel, a0); + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + __ns_aot_return_object(info, r, false, true); + return true; +} + +static bool AOT_NSNumber_numberWithDouble_static(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 1) return false; + SEL sel = @selector(numberWithDouble:); + Class cls = __ns_aot_get_static_class(info); + if (cls == nil) return false; + double raw0 = 0; + if (!__ns_aot_arg_double(info, 0, &raw0)) return true; + double a0 = (double)raw0; + id r = nil; + @try { + r = ((id (*)(id, SEL, double))objc_msgSend)((id)cls, sel, a0); + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + __ns_aot_return_object(info, r, false, true); + return true; +} + +static bool AOT_NSNumber_doubleValue(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 0) return false; + SEL sel = @selector(doubleValue); + bool callSuper = false; + id target = __ns_aot_get_target(info, sel, &sel, &callSuper); + if (target == nil) return false; + double r = 0; + @try { + if (callSuper) { + struct objc_super sup = {target, class_getSuperclass(object_getClass(target))}; + r = ((double (*)(struct objc_super*, SEL))objc_msgSendSuper)(&sup, sel); + } else { + r = ((double (*)(id, SEL))objc_msgSend)(target, sel); + } + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + __ns_aot_return_double(info, r); + return true; +} + +static bool AOT_NSProcessInfo_processInfo_static(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 0) return false; + SEL sel = @selector(processInfo); + Class cls = __ns_aot_get_static_class(info); + if (cls == nil) return false; + id r = nil; + @try { + r = ((id (*)(id, SEL))objc_msgSend)((id)cls, sel); + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + __ns_aot_return_object(info, r, false, true); + return true; +} + +static bool AOT_UIView_setNeedsLayout(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 0) return false; + SEL sel = @selector(setNeedsLayout); + bool callSuper = false; + id target = __ns_aot_get_target(info, sel, &sel, &callSuper); + if (target == nil) return false; + @try { + if (callSuper) { + struct objc_super sup = {target, class_getSuperclass(object_getClass(target))}; + ((void (*)(struct objc_super*, SEL))objc_msgSendSuper)(&sup, sel); + } else { + ((void (*)(id, SEL))objc_msgSend)(target, sel); + } + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + return true; +} + +static bool AOT_UIColor_colorWithRed_green_blue_alpha_static(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 4) return false; + SEL sel = @selector(colorWithRed:green:blue:alpha:); + Class cls = __ns_aot_get_static_class(info); + if (cls == nil) return false; + double raw0 = 0; + if (!__ns_aot_arg_double(info, 0, &raw0)) return true; + double a0 = (double)raw0; + double raw1 = 0; + if (!__ns_aot_arg_double(info, 1, &raw1)) return true; + double a1 = (double)raw1; + double raw2 = 0; + if (!__ns_aot_arg_double(info, 2, &raw2)) return true; + double a2 = (double)raw2; + double raw3 = 0; + if (!__ns_aot_arg_double(info, 3, &raw3)) return true; + double a3 = (double)raw3; + id r = nil; + @try { + r = ((id (*)(id, SEL, double, double, double, double))objc_msgSend)((id)cls, sel, a0, a1, a2, a3); + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + __ns_aot_return_object(info, r, false, true); + return true; +} + +static bool AOT_TNSPrimitives_methodWithInt_static(NSAOTCallInfo info) { + if (__ns_aot_arg_count(info) != 1) return false; + SEL sel = @selector(methodWithInt:); + Class cls = __ns_aot_get_static_class(info); + if (cls == nil) return false; + int64_t raw0 = 0; + if (!__ns_aot_arg_int64(info, 0, &raw0)) return true; + int a0 = (int)raw0; + int r = 0; + @try { + r = ((int (*)(id, SEL, int))objc_msgSend)((id)cls, sel, a0); + } @catch (NSException* e) { + __ns_aot_throw_exception(info, e); + return true; + } + __ns_aot_return_int64(info, (int64_t)r); + return true; +} + __attribute__((visibility("default"))) void __ns_register_aot_calls(void (*reg)(const char*, const char*, bool, NSAOTCallHandler)) { reg("TNSBaseInterface", "baseMethod", false, AOT_TNSBaseInterface_baseMethod); @@ -883,4 +1233,24 @@ void __ns_register_aot_calls(void (*reg)(const char*, const char*, bool, NSAOTCa reg("NSObject", "description", false, AOT_NSObject_description); reg("NSObject", "respondsToSelector:", false, AOT_NSObject_respondsToSelector); reg("NSObject", "isKindOfClass:", false, AOT_NSObject_isKindOfClass); + reg("NSObject", "isEqual:", false, AOT_NSObject_isEqual); + reg("NSArray", "count", false, AOT_NSArray_count); + reg("NSMutableArray", "count", false, AOT_NSArray_count); + reg("NSArray", "objectAtIndex:", false, AOT_NSArray_objectAtIndex); + reg("NSMutableArray", "objectAtIndex:", false, AOT_NSArray_objectAtIndex); + reg("NSMutableArray", "addObject:", false, AOT_NSMutableArray_addObject); + reg("NSMutableArray", "removeLastObject", false, AOT_NSMutableArray_removeLastObject); + reg("NSArray", "indexOfObject:", false, AOT_NSArray_indexOfObject); + reg("NSMutableArray", "indexOfObject:", false, AOT_NSArray_indexOfObject); + reg("NSDictionary", "objectForKey:", false, AOT_NSDictionary_objectForKey); + reg("NSMutableDictionary", "objectForKey:", false, AOT_NSDictionary_objectForKey); + reg("NSString", "length", false, AOT_NSString_length); + reg("NSString", "isEqualToString:", false, AOT_NSString_isEqualToString); + reg("NSNumber", "numberWithInt:", true, AOT_NSNumber_numberWithInt_static); + reg("NSNumber", "numberWithDouble:", true, AOT_NSNumber_numberWithDouble_static); + reg("NSNumber", "doubleValue", false, AOT_NSNumber_doubleValue); + reg("NSProcessInfo", "processInfo", true, AOT_NSProcessInfo_processInfo_static); + reg("UIView", "setNeedsLayout", false, AOT_UIView_setNeedsLayout); + reg("UIColor", "colorWithRed:green:blue:alpha:", true, AOT_UIColor_colorWithRed_green_blue_alpha_static); + reg("TNSPrimitives", "methodWithInt:", true, AOT_TNSPrimitives_methodWithInt_static); } diff --git a/TestRunner/AOT/aot-config.json b/TestRunner/AOT/aot-config.json index 9bc4fd5c8..9832ca9c7 100644 --- a/TestRunner/AOT/aot-config.json +++ b/TestRunner/AOT/aot-config.json @@ -39,6 +39,22 @@ {"class": "NSObject", "selector": "description"}, {"class": "NSObject", "selector": "respondsToSelector:"}, {"class": "NSObject", "selector": "isKindOfClass:"}, + {"class": "NSObject", "selector": "isEqual:"}, + {"class": "NSMutableArray", "selector": "count"}, + {"class": "NSMutableArray", "selector": "objectAtIndex:"}, + {"class": "NSMutableArray", "selector": "addObject:"}, + {"class": "NSMutableArray", "selector": "removeLastObject"}, + {"class": "NSMutableArray", "selector": "indexOfObject:"}, + {"class": "NSMutableDictionary", "selector": "objectForKey:"}, + {"class": "NSString", "selector": "length"}, + {"class": "NSString", "selector": "isEqualToString:"}, + {"class": "NSNumber", "selector": "numberWithInt:", "static": true}, + {"class": "NSNumber", "selector": "numberWithDouble:", "static": true}, + {"class": "NSNumber", "selector": "doubleValue"}, + {"class": "NSProcessInfo", "selector": "processInfo", "static": true}, + {"class": "UIView", "selector": "setNeedsLayout"}, + {"class": "UIColor", "selector": "colorWithRed:green:blue:alpha:", "static": true}, + {"class": "TNSPrimitives", "selector": "methodWithInt:", "static": true}, {"class": "TNSBaseInterface", "selector": "initBaseMethod"}, {"class": "TNSApi", "selector": "method:error:"},