70 lines
3.5 KiB
Python
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')
|