Files
MycLib/Src/AST/Myc.Ast.Compiler.Specializer.pas
T
Michael Schimmel 22674b962b Generic Visitors
2026-01-03 19:14:18 +01:00

274 lines
8.7 KiB
ObjectPascal

unit Myc.Ast.Compiler.Specializer;
interface
uses
System.SysUtils,
System.Generics.Collections,
System.Generics.Defaults,
Myc.Data.Value,
Myc.Ast,
Myc.Ast.Types,
Myc.Ast.Nodes,
Myc.Ast.Visitor,
Myc.Ast.Scope,
Myc.Ast.RTL,
Myc.Ast.Compiler.Binder;
type
// Exception specific to specialization errors
ESpecializerException = class(EAstException);
IAstSpecializer = interface(IAstVisitor)
function Execute(const RootNode: IAstNode): IAstNode;
end;
// --- Monomorphization Cache Definitions ---
TMonoCacheKey = record
public
Address: TResolvedAddress;
ArgTypes: TArray<IStaticType>;
Func: TDataValue.TFunc; // Usually nil in key, but part of structure if needed
constructor Create(const AAddress: TResolvedAddress; const AArgTypes: TArray<IStaticType>);
end;
IMonomorphCache = interface
function TryGetFunction(const Key: TMonoCacheKey; out Func: TSpecializedMethod): Boolean;
procedure Add(const Key: TMonoCacheKey; const Func: TSpecializedMethod);
end;
// This transformer runs *after* TypeChecker.
// It specializes all statically resolvable function calls.
TStaticSpecializer = class(TAstTransformer, IAstSpecializer)
public
type
TCompileFunc = reference to function(const Node: IFunctionDefinition; const ArgTypes: TArray<IStaticType>): TCompiledFunction;
private
FMonomorphCache: IMonomorphCache;
FFunctionRegistry: IFunctionDefinitionRegistry;
FCompileFunc: TCompileFunc;
function GetStaticRtlFunction(const AName: string; const AArgTypes: TArray<IStaticType>): TSpecializedMethod;
strict private
// Specialization Handler (IAstNode signature)
function VisitFunctionCall(const Node: IAstNode): IAstNode;
protected
procedure SetupHandlers; override;
public
constructor Create(
const AMonomorphCache: IMonomorphCache;
const AFunctionRegistry: IFunctionDefinitionRegistry;
const ACompileFunc: TCompileFunc
);
function Execute(const RootNode: IAstNode): IAstNode;
class function Specialize(
const RootNode: IAstNode;
const AMonomorphCache: IMonomorphCache;
const AFunctionRegistry: IFunctionDefinitionRegistry;
const ACompileFunc: TCompileFunc
): IAstNode; static;
end;
implementation
uses
System.Hash;
{ TStaticSpecializer }
constructor TStaticSpecializer.Create(
const AMonomorphCache: IMonomorphCache;
const AFunctionRegistry: IFunctionDefinitionRegistry;
const ACompileFunc: TCompileFunc
);
begin
inherited Create;
if not Assigned(AMonomorphCache) then
raise ESpecializerException.Create('MonomorphCache cannot be nil.');
if not Assigned(AFunctionRegistry) then
raise ESpecializerException.Create('FunctionRegistry cannot be nil.');
FMonomorphCache := AMonomorphCache;
FFunctionRegistry := AFunctionRegistry;
FCompileFunc := ACompileFunc;
end;
procedure TStaticSpecializer.SetupHandlers;
begin
inherited SetupHandlers; // Load default transformations
// Override FunctionCall logic
Register(akFunctionCall, VisitFunctionCall);
end;
class function TStaticSpecializer.Specialize(
const RootNode: IAstNode;
const AMonomorphCache: IMonomorphCache;
const AFunctionRegistry: IFunctionDefinitionRegistry;
const ACompileFunc: TCompileFunc
): IAstNode;
begin
var specializer := TStaticSpecializer.Create(AMonomorphCache, AFunctionRegistry, ACompileFunc) as IAstSpecializer;
Result := specializer.Execute(RootNode);
end;
function TStaticSpecializer.Execute(const RootNode: IAstNode): IAstNode;
begin
Result := Accept(RootNode);
if not Assigned(Result) then
Result := TAst.Block([], nil);
end;
function TStaticSpecializer.GetStaticRtlFunction(const AName: string; const AArgTypes: TArray<IStaticType>): TSpecializedMethod;
begin
Result := TRtlRegistry.GetStaticSpecialization(AName, AArgTypes);
end;
function TStaticSpecializer.VisitFunctionCall(const Node: IAstNode): IAstNode;
var
C: IFunctionCallNode;
newCall: IFunctionCallNode;
newCallee: IAstNode;
newArgsList: IArgumentList;
i: Integer;
calleeIdent: IIdentifierNode;
argTypes: TArray<IStaticType>;
allTypesKnown: Boolean;
funcName: string;
key: TMonoCacheKey;
specializedMethod: TSpecializedMethod;
funcDef: IFunctionDefinition;
begin
C := Node.AsFunctionCall;
// 1. Specialize children first (bottom-up) by calling inherited
// inherited VisitFunctionCall returns an IAstNode (which is a new IFunctionCallNode if changed)
newCall := inherited VisitFunctionCall(Node).AsFunctionCall;
newCallee := newCall.Callee;
newArgsList := newCall.Arguments;
// 2. Check if this call is a candidate for specialization
if newCallee.Kind <> akIdentifier then
begin
Result := newCall;
exit;
end;
calleeIdent := newCallee.AsIdentifier;
funcName := calleeIdent.Name;
// 3. Check if all argument types are statically known
allTypesKnown := True;
SetLength(argTypes, newArgsList.Count);
for i := 0 to newArgsList.Count - 1 do
begin
argTypes[i] := newArgsList[i].AsTypedNode.StaticType;
if argTypes[i].Kind = stUnknown then
begin
allTypesKnown := False;
break;
end;
end;
if not allTypesKnown then
begin
Result := newCall;
exit;
end;
// --- At this point, the call is statically resolvable ---
// 4. Check the Environment (Instance) Cache
key := TMonoCacheKey.Create(calleeIdent.Address, argTypes);
if FMonomorphCache.TryGetFunction(key, specializedMethod) then
begin
// 4a. Cache Hit (Environment)
Result :=
TAst.FunctionCall(
Node.Identity,
newCallee,
newArgsList,
specializedMethod.ReturnType,
C.IsTailCall,
specializedMethod.Target,
specializedMethod.IsPure
);
exit;
end;
// 5. Check the RTL (Global) Bootstrap Cache
specializedMethod := GetStaticRtlFunction(funcName, argTypes);
if Assigned(specializedMethod.Target) then
begin
// 5a. Cache Hit (RTL)
FMonomorphCache.Add(key, specializedMethod);
Result :=
TAst.FunctionCall(
Node.Identity,
newCallee,
newArgsList,
specializedMethod.ReturnType,
C.IsTailCall,
specializedMethod.Target,
specializedMethod.IsPure
);
exit;
end;
// 6. Cache Miss (User Code)
funcDef := FFunctionRegistry.Resolve(calleeIdent.Address);
if (funcDef <> nil) then
begin
// Cannot specialize closures safely without more complex analysis if they have state
if funcDef.Kind = akLambdaExpression then
begin
var lambdaDef := funcDef.AsLambdaExpression;
if (Length(lambdaDef.Upvalues) > 0) or (lambdaDef.HasNestedLambdas) then
begin
Result := newCall;
exit;
end;
end;
// 6a. Compile func with KNOWN TYPES
if not Assigned(FCompileFunc) then
raise ESpecializerException.Create('Cannot specialize user function: Compiler callback is missing.');
var compiled := FCompileFunc(funcDef, argTypes);
// Safety check: The recursive compiler MUST produce a valid function pointer
if not Assigned(compiled.Func) then
raise ESpecializerException.CreateFmt('Internal Error: Failed to compile specialization for "%s"', [funcName]);
// 6b. Store in cache
var returnType := compiled.StaticType.Signatures[0].ReturnType;
specializedMethod := TSpecializedMethod.Create(compiled.Func, returnType, compiled.IsPure);
FMonomorphCache.Add(key, specializedMethod);
// 6c. Return the new node
Result := TAst.FunctionCall(Node.Identity, newCallee, newArgsList, returnType, C.IsTailCall, compiled.Func, compiled.IsPure);
exit;
end;
// 7. Fallback: Not RTL, Not User-Code -> Dynamic
Result := newCall;
end;
{ TMonoCacheKey }
constructor TMonoCacheKey.Create(const AAddress: TResolvedAddress; const AArgTypes: TArray<IStaticType>);
begin
Address := AAddress;
ArgTypes := AArgTypes;
Func := nil;
end;
end.