diff --git a/typed_python/__init__.py b/typed_python/__init__.py index d7da9e8a7..9214addca 100644 --- a/typed_python/__init__.py +++ b/typed_python/__init__.py @@ -42,7 +42,6 @@ from typed_python.hash import sha_hash from typed_python.SerializationContext import SerializationContext from typed_python.type_filter import TypeFilter -from typed_python.compiler.typeof import TypeOf from typed_python._types import ( Forward, TupleOf, ListOf, Tuple, NamedTuple, OneOf, ConstDict, SubclassOf, Alternative, Value, serialize, deserialize, serializeStream, deserializeStream, @@ -78,6 +77,7 @@ from typed_python.lib.map import map # noqa from typed_python.lib.pmap import pmap # noqa from typed_python.lib.reduce import reduce # noqa +from typed_python.compiler.typeof import TypeOf # noqa _types.initializeGlobalStatics() diff --git a/typed_python/compiler/compiler_input.py b/typed_python/compiler/compiler_input.py new file mode 100644 index 000000000..813c93179 --- /dev/null +++ b/typed_python/compiler/compiler_input.py @@ -0,0 +1,197 @@ +import types + +import typed_python + +from typing import List +from typed_python.compiler.python_object_representation import typedPythonTypeToTypeWrapper +from typed_python.compiler.type_wrappers.python_typed_function_wrapper import ( + PythonTypedFunctionWrapper, + CannotBeDetermined, + NoReturnTypeSpecified, +) +from typed_python.compiler.type_wrappers.typed_tuple_masquerading_as_tuple_wrapper import ( + TypedTupleMasqueradingAsTuple, +) +from typed_python.compiler.type_wrappers.named_tuple_masquerading_as_dict_wrapper import ( + NamedTupleMasqueradingAsDict, +) +from typed_python.compiler.conversion_level import ConversionLevel +from typed_python import Function, Value +from typed_python.internals import FunctionOverloadArg +from typed_python.type_function import TypeFunction + +import typed_python.compiler.python_to_native_converter as python_to_native_converter + +typeWrapper = lambda t: python_to_native_converter.typedPythonTypeToTypeWrapper(t) + + +class CompilerInput: + """ + Represents a parcel of input code and its input types + closure, containing everything the + compiler needs in order to do the compilation. Typed_python supports function overloading, + so everything here is specific to a given 'overload' - a function for a given set of input + types, and inferred output type. + """ + def __init__(self, overload, closure_type, input_wrappers): + self._overload = overload + self.closure_type = closure_type + self._input_wrappers = input_wrappers + + # cached properties + self._realized_input_wrappers = None + self._return_type = None + self._return_type_calculated = False + + @property + def realized_input_wrappers(self): + if self._realized_input_wrappers is None: + self._realized_input_wrappers = self._compute_realized_input_wrappers() + assert self._realized_input_wrappers is not None + + return self._realized_input_wrappers + + @property + def return_type(self): + if not self._return_type_calculated: + self._return_type = self._compute_return_type() + self._return_type_calculated = True + return self._return_type + + @property + def args(self): + return self._overload.args + + @property + def name(self): + return self._overload.name + + @property + def functionCode(self): + return self._overload.functionCode + + @property + def realizedGlobals(self): + return self._overload.realizedGlobals + + @property + def functionGlobals(self): + return self._overload.functionGlobals + + @property + def funcGlobalsInCells(self): + return self._overload.funcGlobalsInCells + + @property + def closureVarLookups(self): + return self._overload.closureVarLookups + + def _compute_realized_input_wrappers(self) -> List: + """ + Extend the list of wrappers (representing the input args) using the free variables in + the function closure. + """ + res = [] + for closure_var_path in self.closureVarLookups.values(): + res.append( + typedPythonTypeToTypeWrapper( + PythonTypedFunctionWrapper.closurePathToCellType(closure_var_path, self.closure_type) + ) + ) + res.extend(self._input_wrappers) + return res + + def _compute_return_type(self): + """Determine the return type, if possible.""" + res = PythonTypedFunctionWrapper.computeFunctionOverloadReturnType( + self._overload, self._input_wrappers, {} + ) + + if res is CannotBeDetermined: + res = object + + elif res is NoReturnTypeSpecified: + res = None + + return res + + def install_native_pointer(self, fp, returnType, argumentTypes) -> None: + return self._overload._installNativePointer(fp, returnType, argumentTypes) + + @staticmethod + def make(functionType, overloadIx, arguments, argumentsAreTypes): + """Generate a CompilerInput packet for a given overload + input argument.""" + overload = functionType.overloads[overloadIx] + + if len(arguments) != len(overload.args): + raise ValueError( + "CompilerInput mismatch: overload has %s args, but we were given " + "%s arguments" % (len(overload.args), len(arguments)) + ) + + inputWrappers = [] + + for i in range(len(arguments)): + specialization = pick_specialization_type_for( + overload.args[i], arguments[i], argumentsAreTypes + ) + + if specialization is None: + return None + + inputWrappers.append(specialization) + + return CompilerInput(overload, functionType.ClosureType, inputWrappers) + + +def pick_specialization_type_for(overloadArg: FunctionOverloadArg, argValue, argumentsAreTypes): + """Compute the typeWrapper we'll use for this particular argument based on 'argValue'. + + Args: + overloadArg: the internals.FunctionOverloadArg instance representing this argument. + This tells us whether we're dealing with a normal positional/keyword argument or + a *arg / **kwarg, where the typeFilter applies to the items of the tuple but + not the tuple itself. + argValue: the value being passed for this argument. If 'argumentsAreTypes' is true, + then this is the actual type, not the value. + + Returns: + the Wrapper or type instance to use for this argument. + """ + if not argumentsAreTypes: + if overloadArg.isStarArg: + argType = TypedTupleMasqueradingAsTuple( + typed_python.Tuple(*[passingTypeForValue(v) for v in argValue]) + ) + elif overloadArg.isKwarg: + argType = NamedTupleMasqueradingAsDict( + typed_python.NamedTuple( + **{k: passingTypeForValue(v) for k, v in argValue.items()} + ) + ) + else: + argType = typeWrapper(passingTypeForValue(argValue)) + else: + argType = typeWrapper(argValue) + + resType = PythonTypedFunctionWrapper.pickSpecializationTypeFor(overloadArg, argType) + + if argType.can_convert_to_type(resType, ConversionLevel.Implicit) is False: + return None + + if (overloadArg.isStarArg or overloadArg.isKwarg) and resType != argType: + return None + + return resType + + +def passingTypeForValue(arg): + if isinstance(arg, types.FunctionType): + return type(Function(arg)) + + elif isinstance(arg, type) and issubclass(arg, TypeFunction) and len(arg.MRO) == 2: + return Value(arg) + + elif isinstance(arg, type): + return Value(arg) + + return type(arg) diff --git a/typed_python/compiler/python_to_native_converter.py b/typed_python/compiler/python_to_native_converter.py index 561000b3d..0119fa332 100644 --- a/typed_python/compiler/python_to_native_converter.py +++ b/typed_python/compiler/python_to_native_converter.py @@ -19,24 +19,18 @@ from types import ModuleType import typed_python.python_ast as python_ast import typed_python._types as _types -import typed_python.compiler import typed_python.compiler.native_ast as native_ast from typed_python.compiler.native_function_pointer import NativeFunctionPointer from sortedcontainers import SortedSet +from typed_python.compiler.compiler_input import CompilerInput from typed_python.compiler.directed_graph import DirectedGraph from typed_python.compiler.type_wrappers.wrapper import Wrapper from typed_python.compiler.type_wrappers.class_wrapper import ClassWrapper from typed_python.compiler.python_object_representation import typedPythonTypeToTypeWrapper from typed_python.compiler.function_conversion_context import FunctionConversionContext, FunctionOutput, FunctionYield from typed_python.compiler.native_function_conversion_context import NativeFunctionConversionContext -from typed_python.compiler.type_wrappers.python_typed_function_wrapper import ( - PythonTypedFunctionWrapper, CannotBeDetermined, NoReturnTypeSpecified -) from typed_python.compiler.typed_call_target import TypedCallTarget -typeWrapper = lambda t: typed_python.compiler.python_object_representation.typedPythonTypeToTypeWrapper(t) - - VALIDATE_FUNCTION_DEFINITIONS_STABLE = False @@ -343,8 +337,8 @@ def defineNativeFunction(self, name, identity, input_types, output_type, generat returns a TypedCallTarget. 'generatingFunction' may call this recursively if it wants. """ - output_type = typeWrapper(output_type) - input_types = [typeWrapper(x) for x in input_types] + output_type = typedPythonTypeToTypeWrapper(output_type) + input_types = [typedPythonTypeToTypeWrapper(x) for x in input_types] identity = ( Hash.from_integer(2) + @@ -419,7 +413,7 @@ def generator(context, out, *args): "demasquerade_" + callTarget.name, ("demasquerade", callTarget.name), callTarget.input_types, - typeWrapper(callTarget.output_type.interpreterTypeRepresentation), + typedPythonTypeToTypeWrapper(callTarget.output_type.interpreterTypeRepresentation), generator ) @@ -593,7 +587,7 @@ def compileSingleClassDispatch(self, interfaceClass, implementingClass, slotInde _types.installClassMethodDispatch(interfaceClass, implementingClass, slotIndex, fp.fp) def compileClassDestructor(self, cls): - typedCallTarget = typeWrapper(cls).compileDestructor(self) + typedCallTarget = typedPythonTypeToTypeWrapper(cls).compileDestructor(self) assert typedCallTarget is not None @@ -620,43 +614,16 @@ def functionPointerByName(self, linkerName) -> NativeFunctionPointer: # compiler cache has all the pointers. return self.compilerCache.function_pointer_by_name(linkerName) - def convertTypedFunctionCall(self, functionType, overloadIx, inputWrappers, assertIsRoot=False): - overload = functionType.overloads[overloadIx] - - realizedInputWrappers = [] - - closureType = functionType.ClosureType - - for closureVarName, closureVarPath in overload.closureVarLookups.items(): - realizedInputWrappers.append( - typeWrapper( - PythonTypedFunctionWrapper.closurePathToCellType(closureVarPath, closureType) - ) - ) - - realizedInputWrappers.extend(inputWrappers) - - returnType = PythonTypedFunctionWrapper.computeFunctionOverloadReturnType( - overload, - inputWrappers, - {} - ) - - if returnType is CannotBeDetermined: - returnType = object - - if returnType is NoReturnTypeSpecified: - returnType = None - + def convertTypedFunctionCall(self, compiler_input: CompilerInput, assertIsRoot=False): return self.convert( - overload.name, - overload.functionCode, - overload.realizedGlobals, - overload.functionGlobals, - overload.funcGlobalsInCells, - list(overload.closureVarLookups), - realizedInputWrappers, - returnType, + compiler_input.name, + compiler_input.functionCode, + compiler_input.realizedGlobals, + compiler_input.functionGlobals, + compiler_input.funcGlobalsInCells, + list(compiler_input.closureVarLookups), + compiler_input.realized_input_wrappers, + compiler_input.return_type, assertIsRoot=assertIsRoot ) diff --git a/typed_python/compiler/runtime.py b/typed_python/compiler/runtime.py index ba5011ac2..7ff21598e 100644 --- a/typed_python/compiler/runtime.py +++ b/typed_python/compiler/runtime.py @@ -15,16 +15,12 @@ import threading import os import time -import types import typed_python.compiler.python_to_native_converter as python_to_native_converter import typed_python.compiler.llvm_compiler as llvm_compiler import typed_python from typed_python.compiler.runtime_lock import runtimeLock -from typed_python.compiler.conversion_level import ConversionLevel from typed_python.compiler.compiler_cache import CompilerCache -from typed_python.type_function import TypeFunction -from typed_python.compiler.type_wrappers.typed_tuple_masquerading_as_tuple_wrapper import TypedTupleMasqueradingAsTuple -from typed_python.compiler.type_wrappers.named_tuple_masquerading_as_dict_wrapper import NamedTupleMasqueradingAsDict +from typed_python.compiler.compiler_input import CompilerInput, typeWrapper from typed_python.compiler.type_wrappers.python_typed_function_wrapper import PythonTypedFunctionWrapper, NoReturnTypeSpecified from typed_python import Function, _types, Value from typed_python.compiler.merge_type_wrappers import mergeTypeWrappers @@ -32,8 +28,6 @@ _singleton = [None] _singletonLock = threading.RLock() -typeWrapper = lambda t: python_to_native_converter.typedPythonTypeToTypeWrapper(t) - _resultTypeCache = {} @@ -192,60 +186,6 @@ def addEventVisitor(self, visitor: RuntimeEventVisitor): def removeEventVisitor(self, visitor: RuntimeEventVisitor): self.converter.removeVisitor(visitor) - @staticmethod - def passingTypeForValue(arg): - if isinstance(arg, types.FunctionType): - return type(Function(arg)) - - elif isinstance(arg, type) and issubclass(arg, TypeFunction) and len(arg.MRO) == 2: - return Value(arg) - - elif isinstance(arg, type): - return Value(arg) - - return type(arg) - - @staticmethod - def pickSpecializationTypeFor(overloadArg, argValue, argumentsAreTypes=False): - """Compute the typeWrapper we'll use for this particular argument based on 'argValue'. - - Args: - overloadArg - the internals.FunctionOverloadArg instance representing this argument. - This tells us whether we're dealing with a normal positional/keyword argument or - a *arg / **kwarg, where the typeFilter applies to the items of the tuple but - not the tuple itself. - argValue - the value being passed for this argument. If 'argumentsAreTypes' is true, - then this is the actual type, not the value. - - Returns: - the Wrapper or type instance to use for this argument. - """ - if not argumentsAreTypes: - if overloadArg.isStarArg: - argType = TypedTupleMasqueradingAsTuple( - typed_python.Tuple(*[Runtime.passingTypeForValue(v) for v in argValue]) - ) - elif overloadArg.isKwarg: - argType = NamedTupleMasqueradingAsDict( - typed_python.NamedTuple( - **{k: Runtime.passingTypeForValue(v) for k, v in argValue.items()} - ) - ) - else: - argType = typeWrapper(Runtime.passingTypeForValue(argValue)) - else: - argType = typeWrapper(argValue) - - resType = PythonTypedFunctionWrapper.pickSpecializationTypeFor(overloadArg, argType) - - if argType.can_convert_to_type(resType, ConversionLevel.Implicit) is False: - return None - - if (overloadArg.isStarArg or overloadArg.isKwarg) and resType != argType: - return None - - return resType - def compileFunctionOverload(self, functionType, overloadIx, arguments, argumentsAreTypes=False): """Attempt to compile typedFunc.overloads[overloadIx]' with the given arguments. @@ -261,9 +201,6 @@ def compileFunctionOverload(self, functionType, overloadIx, arguments, arguments None if it is not possible to match this overload with these arguments or a TypedCallTarget. """ - overload = functionType.overloads[overloadIx] - - assert len(arguments) == len(overload.args) try: t0 = time.time() @@ -272,23 +209,18 @@ def compileFunctionOverload(self, functionType, overloadIx, arguments, arguments defCount = self.converter.getDefinitionCount() with self.lock: - inputWrappers = [] - - for i in range(len(arguments)): - inputWrappers.append( - self.pickSpecializationTypeFor(overload.args[i], arguments[i], argumentsAreTypes) - ) + # generate the parcel of code corresponding to an input to the compiler + compiler_input = CompilerInput.make( + functionType, overloadIx, arguments, argumentsAreTypes + ) - if any(x is None for x in inputWrappers): - # this signature is unmatchable with these arguments. + if compiler_input is None: return None self.timesCompiled += 1 callTarget = self.converter.convertTypedFunctionCall( - functionType, - overloadIx, - inputWrappers, + compiler_input, assertIsRoot=True ) @@ -304,7 +236,7 @@ def compileFunctionOverload(self, functionType, overloadIx, arguments, arguments fp = self.converter.functionPointerByName(wrappingCallTargetName) - overload._installNativePointer( + compiler_input.install_native_pointer( fp.fp, callTarget.output_type.typeRepresentation if callTarget.output_type is not None else type(None), [i.typeRepresentation for i in callTarget.input_types] diff --git a/typed_python/compiler/typeof.py b/typed_python/compiler/typeof.py index 270f13ff8..9343d13a8 100644 --- a/typed_python/compiler/typeof.py +++ b/typed_python/compiler/typeof.py @@ -1,8 +1,8 @@ import typed_python -from typed_python.compiler.type_wrappers.type_sets import SubclassOf, Either - -typeWrapper = lambda t: typed_python.compiler.python_to_native_converter.typedPythonTypeToTypeWrapper(t) +from typed_python.compiler.compiler_input import CompilerInput +from typed_python.compiler.type_wrappers.type_sets import SubclassOf, Either +from typed_python.compiler.python_object_representation import typedPythonTypeToTypeWrapper class TypeOf: @@ -47,8 +47,8 @@ def __call__(self, *args, **kwargs): def resultTypeForCall(self, argTypes, kwargTypes): funcObj = typed_python._types.prepareArgumentToBePassedToCompiler(self.F) - argTypes = [typeWrapper(a) for a in argTypes] - kwargTypes = {k: typeWrapper(v) for k, v in kwargTypes.items()} + argTypes = [typedPythonTypeToTypeWrapper(a) for a in argTypes] + kwargTypes = {k: typedPythonTypeToTypeWrapper(v) for k, v in kwargTypes.items()} overload = funcObj.overloads[0] @@ -61,10 +61,14 @@ def resultTypeForCall(self, argTypes, kwargTypes): converter = typed_python.compiler.runtime.Runtime.singleton().converter + compiler_input = CompilerInput.make(funcObj, + overloadIx=0, + arguments=argumentSignature, + argumentsAreTypes=True + ) + callTarget = converter.convertTypedFunctionCall( - type(funcObj), - 0, - argumentSignature, + compiler_input, assertIsRoot=False )