Files
MycLib/Src/AST/Myc.Ast.Compiler.Specializer.pas
T
2025-11-22 14:49:24 +01:00

204 lines
6.8 KiB
ObjectPascal

unit Myc.Ast.Compiler.Specializer;
interface
uses
System.SysUtils,
System.Generics.Collections,
Myc.Data.Value,
Myc.Ast,
Myc.Ast.Types,
Myc.Ast.Nodes,
Myc.Ast.Visitor,
Myc.Ast.Scope,
Myc.Ast.Environment,
Myc.Ast.RTL;
type
IAstSpecializer = interface(IAstVisitor)
function Execute(const RootNode: IAstNode): IAstNode;
end;
// This transformer runs *after* TypeChecker.
// It specializes all statically resolvable function calls (RTL and user-defined)
// by replacing them with nodes that have a direct StaticTarget.
// It propagates Purity information but does NOT perform Constant Folding yet.
TStaticSpecializer = class(TAstTransformer, IAstSpecializer)
private
FEnvironment: IEnvironment;
function GetStaticRtlFunction(const AName: string; const AArgTypes: TArray<IStaticType>): TSpecializedMethod;
protected
function VisitFunctionCall(const Node: IFunctionCallNode): IAstNode; override;
public
constructor Create(const AEnvironment: IEnvironment);
function Execute(const RootNode: IAstNode): IAstNode;
class function Specialize(const AEnvironment: IEnvironment; const RootNode: IAstNode): IAstNode; static;
end;
implementation
{ TStaticSpecializer }
constructor TStaticSpecializer.Create(const AEnvironment: IEnvironment);
begin
inherited Create;
Assert(Assigned(AEnvironment));
FEnvironment := AEnvironment;
end;
class function TStaticSpecializer.Specialize(const AEnvironment: IEnvironment; const RootNode: IAstNode): IAstNode;
begin
var specializer := TStaticSpecializer.Create(AEnvironment) 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([]);
end;
function TStaticSpecializer.GetStaticRtlFunction(const AName: string; const AArgTypes: TArray<IStaticType>): TSpecializedMethod;
begin
Result := TRtlRegistry.GetStaticSpecialization(AName, AArgTypes);
end;
function TStaticSpecializer.VisitFunctionCall(const Node: IFunctionCallNode): IAstNode;
var
newCallee: IAstNode;
newArgs: TArray<IAstNode>;
i: Integer;
calleeIdent: IIdentifierNode;
argTypes: TArray<IStaticType>;
allTypesKnown: Boolean;
funcName: string;
key: TMonoCacheKey;
specializedMethod: TSpecializedMethod;
funcDef: IFunctionDefinition;
begin
// 1. Specialize children first (bottom-up)
newCallee := Accept(Node.Callee);
newArgs := AcceptNodes(Node.Arguments);
// 2. Check if this call is a candidate for specialization
if newCallee.Kind <> akIdentifier then
begin
if (newCallee = Node.Callee) and (newArgs = Node.Arguments) then
Result := Node
else
Result := TAst.FunctionCall(newCallee, newArgs, Node.StaticType, Node.IsTailCall, nil);
exit;
end;
calleeIdent := newCallee.AsIdentifier;
funcName := calleeIdent.Name;
// 3. Check if all argument types are statically known
allTypesKnown := True;
SetLength(argTypes, Length(newArgs));
for i := 0 to High(newArgs) do
begin
argTypes[i] := newArgs[i].AsTypedNode.StaticType;
if argTypes[i].Kind = stUnknown then
begin
allTypesKnown := False;
break;
end;
end;
if not allTypesKnown then
begin
if (newCallee = Node.Callee) and (newArgs = Node.Arguments) then
Result := Node
else
Result := TAst.FunctionCall(newCallee, newArgs, Node.StaticType, Node.IsTailCall, nil);
exit;
end;
// --- At this point, the call is statically resolvable ---
// 4. Check the Environment (Instance) Cache
key := TMonoCacheKey.Create(calleeIdent.Address, argTypes);
if FEnvironment.MonomorphCache.TryGetValue(key, specializedMethod) then
begin
// 4a. Cache Hit (Environment)
// Propagate IsPure flag from cache to AST node
Result :=
TAst.FunctionCall(
newCallee,
newArgs,
specializedMethod.ReturnType,
Node.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)
FEnvironment.MonomorphCache.Add(key, specializedMethod);
// Propagate IsPure flag from RTL definition to AST node
Result :=
TAst.FunctionCall(
newCallee,
newArgs,
specializedMethod.ReturnType,
Node.IsTailCall,
specializedMethod.Target,
specializedMethod.IsPure
);
exit;
end;
// 6. Cache Miss (User Code)
funcDef := FEnvironment.FunctionRegistry.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 := TAst.FunctionCall(newCallee, newArgs, Node.StaticType, Node.IsTailCall, nil);
exit;
end;
end;
// 6a. Compile func with KNOWN TYPES
// This recursively triggers Bind -> Check -> Specialize -> Purity Inference for the callee body!
var compiled := FEnvironment.Compile(funcDef, argTypes);
// 6b. Store in cache
// Get the return type from the *full function type* returned by Compile
var returnType := compiled.StaticType.Signatures[0].ReturnType;
specializedMethod := TSpecializedMethod.Create(compiled.Func, returnType, compiled.IsPure);
FEnvironment.MonomorphCache.Add(key, specializedMethod);
// 6c. Return the new node
// Propagate the inferred IsPure flag to the AST node
Result := TAst.FunctionCall(newCallee, newArgs, returnType, Node.IsTailCall, compiled.Func, compiled.IsPure);
exit;
end;
// 7. Fallback: Not RTL, Not User-Code -> Dynamic
if (newCallee = Node.Callee) and (newArgs = Node.Arguments) then
Result := Node
else
Result := TAst.FunctionCall(newCallee, newArgs, Node.StaticType, Node.IsTailCall, nil);
end;
end.