Files
StaticLink.Avalonia/build/linux-toolchain/x86-thunks.py
T

70 lines
3.5 KiB
Python

#!/usr/bin/env python3
"""Generate cdecl -> stdcall adapters using the pinned managed P/Invoke ABI."""
import argparse,re,subprocess,json
from pathlib import Path
p=argparse.ArgumentParser();p.add_argument('--source',type=Path,required=True);p.add_argument('--toolchain',type=Path,required=True);p.add_argument('--libraries',type=Path,required=True);a=p.parse_args()
files=[a.source/'binding/SkiaSharp/SkiaApi.generated.cs',a.source/'binding/HarfBuzzSharp/HarfBuzzApi.generated.cs']
text='\n'.join(x.read_text() if x.is_file() else subprocess.check_output(['git','-C',str(a.source),'show','HEAD:'+str(x.relative_to(a.source))],text=True) for x in files)
structs={}
# Generated structs use public fields; omit properties and methods.
for match in re.finditer(r'\bstruct\s+(\w+)\s*\{',text):
pos=match.end();depth=1;end=pos
while depth:
if text[end]=='{':depth+=1
if text[end]=='}':depth-=1
end+=1
block=text[pos:end-1]
structs[match[1]]=[m[1].strip() for m in re.finditer(r'^\s*public\s+(.+?)\s+\w+;\s*$',block,re.M)]
sizes={}
def layout(t):
t=re.sub(r'\b(?:ref|out|in|readonly)\b','',t).strip()
if '*' in t or t.endswith(('[]','Delegate')):return 4,4
if t in sizes:return sizes[t],4
if t in structs:raise KeyError(t)
if t in ['Int64','UInt64','long','ulong','Double','double']:return 8,4
if t in ['Int16','UInt16','short','ushort']:return 2,2
if t in ['Byte','SByte','byte','sbyte','Boolean','bool']:return 1,1
return 4,4 # enums and generated opaque pointer aliases
pending=set(structs)
while pending:
done=set()
for name in sorted(pending):
offset=0;align=1
try:
for t in structs[name]:
n,k=layout(t);offset=(offset+k-1)//k*k+n;align=max(align,k)
except KeyError:continue
sizes[name]=(offset+align-1)//align*align;done.add(name)
if not done:raise SystemExit('Unresolved managed struct layout: '+str(pending))
pending-=done
symbols=set()
for path in a.libraries.glob('lib*.a'):
result=subprocess.run(['nm','--defined-only',str(path)],capture_output=True,text=True,check=True)
symbols.update(re.findall(r'\bT\s+(_(?:sk|gr|hb)_\w+)\s*$',result.stdout,re.M))
thunks={}
def parameters(s):
result=[];depth=0;start=0
for i,c in enumerate(s):
if c in '[<(':depth+=1
elif c in ']> )'.replace(' ',''):depth-=1
elif c==',' and depth==0:result.append(s[start:i]);start=i+1
result.append(s[start:]);return [v.strip() for v in result if v.strip()]
for m in re.finditer(r'internal static (?:extern|partial)\s+.+?\s+((?:sk|gr|hb)_\w+)\s*\((.*?)\);',text,re.S):
symbol='_'+m[1]
if symbol not in symbols:continue
count=0
for param in parameters(m[2]):
param=re.sub(r'/\*.*?\*/|\[[^\]]+\]','',param).strip();typ=param.rsplit(' ',1)[0]
n,_=layout(typ);count+=(n+3)//4*4
thunks[symbol+'@'+str(count)]=(symbol,count)
if not thunks:raise SystemExit('No x86 exported APIs matched pinned managed bindings')
assembly=['.text','.set "@feat.00", 1']
for name,(symbol,count) in sorted(thunks.items()):
assembly+=['.globl "'+name+'"','"'+name+'":']+['pushl '+str(count)+'(%esp)']*(count//4)
assembly+=['calll '+symbol,'addl $'+str(count)+', %esp','retl $'+str(count)]
path=a.libraries/'skia_x86_stdcall_thunks.s';path.write_text('\n'.join(assembly)+'\n');obj=path.with_suffix('.o')
subprocess.run([str(a.toolchain/'cc'),'-c',str(path),'-o',str(obj)],check=True)
subprocess.run([str(a.toolchain/'ar'),'rcs',str(a.libraries/'skia_x86_stdcall_thunks.lib'),str(obj)],check=True)
(a.libraries/'x86-thunks.json').write_text(json.dumps({'count':len(thunks),'layouts':sizes,'thunks':thunks},indent=2)+'\n')
print('Generated',len(thunks),'x86 stdcall thunks')