Initial commit of source code.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
This commit is contained in:
Kevin O'Connor 2016-05-25 11:37:40 -04:00
parent 37a91e9c10
commit f582a36e4d
71 changed files with 9950 additions and 0 deletions

212
scripts/avrsim.py Executable file
View file

@ -0,0 +1,212 @@
#!/usr/bin/env python
# Script to interact with simulavr by simulating a serial port.
#
# Copyright (C) 2015 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import sys, optparse, os, pty, select, fcntl, termios, traceback, errno
import pysimulavr
SERIALBITS = 10 # 8N1 = 1 start, 8 data, 1 stop
# Class to read serial data from AVR serial transmit pin.
class SerialRxPin(pysimulavr.PySimulationMember, pysimulavr.Pin):
def __init__(self, baud):
pysimulavr.Pin.__init__(self)
pysimulavr.PySimulationMember.__init__(self)
self.sc = pysimulavr.SystemClock.Instance()
self.delay = 10**9 / baud
self.current = 0
self.pos = -1
self.queue = ""
def SetInState(self, pin):
pysimulavr.Pin.SetInState(self, pin)
self.state = pin.outState
if self.pos < 0 and pin.outState == pin.LOW:
self.pos = 0
self.sc.Add(self)
def DoStep(self, trueHwStep):
ishigh = self.state == self.HIGH
self.current |= ishigh << self.pos
self.pos += 1
if self.pos == 1:
return int(self.delay * 1.5)
if self.pos >= SERIALBITS:
self.queue += chr((self.current >> 1) & 0xff)
self.pos = -1
self.current = 0
return -1
return self.delay
def popChars(self):
d = self.queue
self.queue = ""
return d
# Class to send serial data to AVR serial receive pin.
class SerialTxPin(pysimulavr.PySimulationMember, pysimulavr.Pin):
MAX_QUEUE = 64
def __init__(self, baud):
pysimulavr.Pin.__init__(self)
pysimulavr.PySimulationMember.__init__(self)
self.SetPin('H')
self.sc = pysimulavr.SystemClock.Instance()
self.delay = 10**9 / baud
self.current = 0
self.pos = 0
self.queue = ""
def DoStep(self, trueHwStep):
if not self.pos:
if not self.queue:
return -1
self.current = (ord(self.queue[0]) << 1) | 0x200
self.queue = self.queue[1:]
newstate = 'L'
if self.current & (1 << self.pos):
newstate = 'H'
self.SetPin(newstate)
self.pos += 1
if self.pos >= SERIALBITS:
self.pos = 0
return self.delay
def needChars(self):
if len(self.queue) > self.MAX_QUEUE / 2:
return 0
return self.MAX_QUEUE - len(self.queue)
def pushChars(self, c):
queueEmpty = not self.queue
self.queue += c
if queueEmpty:
self.sc.Add(self)
# Support for creating VCD trace files
class Tracing:
def __init__(self, filename, signals):
self.filename = filename
self.signals = signals
if not signals:
self.dman = None
return
self.dman = pysimulavr.DumpManager.Instance()
self.dman.SetSingleDeviceApp()
def show_help(self):
ostr = pysimulavr.ostringstream()
self.dman.save(ostr)
sys.stdout.write(ostr.str())
sys.exit(1)
def load_options(self):
if self.dman is None:
return
if self.signals.strip() == '?':
self.show_help()
sigs = "\n".join(["+ " + s for s in self.signals.split(',')])
self.dman.addDumpVCD(self.filename, sigs, "ns", False, False)
def start(self):
if self.dman is not None:
self.dman.start()
def finish(self):
if self.dman is not None:
self.dman.stopApplication()
# Support for creating a pseudo-tty for emulating a serial port
def create_pty(ptyname):
mfd, sfd = pty.openpty()
try:
os.unlink(ptyname)
except os.error:
pass
os.symlink(os.ttyname(sfd), ptyname)
fcntl.fcntl(mfd, fcntl.F_SETFL
, fcntl.fcntl(mfd, fcntl.F_GETFL) | os.O_NONBLOCK)
old = termios.tcgetattr(mfd)
old[3] = old[3] & ~termios.ECHO
termios.tcsetattr(mfd, termios.TCSADRAIN, old)
return mfd
def main():
usage = "%prog [options] <program.elf>"
opts = optparse.OptionParser(usage)
opts.add_option("-m", "--machine", type="string", dest="machine",
default="atmega644", help="type of AVR machine to simulate")
opts.add_option("-s", "--speed", type="int", dest="speed", default=8000000,
help="machine speed")
opts.add_option("-b", "--baud", type="int", dest="baud", default=38400,
help="baud rate of the emulated serial port")
opts.add_option("-t", "--trace", type="string", dest="trace",
help="signals to trace (? for help)")
opts.add_option("-p", "--port", type="string", dest="port",
default="/tmp/pseudoserial",
help="pseudo-tty device to create for serial port")
deffile = os.path.splitext(os.path.basename(sys.argv[0]))[0] + ".vcd"
opts.add_option("-f", "--tracefile", type="string", dest="tracefile",
default=deffile, help="filename to write signal trace to")
options, args = opts.parse_args()
if len(args) != 1:
opts.error("Incorrect number of arguments")
elffile = args[0]
proc = options.machine
ptyname = options.port
speed = options.speed
baud = options.baud
# launch simulator
sc = pysimulavr.SystemClock.Instance()
trace = Tracing(options.tracefile, options.trace)
dev = pysimulavr.AvrFactory.instance().makeDevice(proc)
dev.Load(elffile)
dev.SetClockFreq(10**9 / speed)
sc.Add(dev)
trace.load_options()
# Setup rx pin
rxpin = SerialRxPin(baud)
net = pysimulavr.Net()
net.Add(rxpin)
net.Add(dev.GetPin("D1"))
# Setup tx pin
txpin = SerialTxPin(baud)
net2 = pysimulavr.Net()
net2.Add(dev.GetPin("D0"))
net2.Add(txpin)
# Display start banner
msg = "Starting AVR simulation: machine=%s speed=%d\n" % (proc, speed)
msg += "Serial: port=%s baud=%d\n" % (ptyname, baud)
if options.trace:
msg += "Trace file: %s\n" % (options.tracefile,)
sys.stdout.write(msg)
sys.stdout.flush()
# Create terminal device
fd = create_pty(ptyname)
# Run loop
try:
trace.start()
while 1:
starttime = sc.GetCurrentTime()
r = sc.RunTimeRange(speed/1000)
endtime = sc.GetCurrentTime()
if starttime == endtime:
break
d = rxpin.popChars()
if d:
os.write(fd, d)
txsize = txpin.needChars()
if txsize:
res = select.select([fd], [], [], 0)
if res[0]:
try:
d = os.read(fd, txsize)
except os.error, e:
if e.errno in (errno.EAGAIN, errno.EWOULDBLOCK):
continue
break
txpin.pushChars(d)
trace.finish()
finally:
os.unlink(ptyname)
if __name__ == '__main__':
main()

313
scripts/buildcommands.py Normal file
View file

@ -0,0 +1,313 @@
#!/usr/bin/env python
# Script to handle build time requests embedded in C code.
#
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import sys, os, subprocess, optparse, logging, shlex, socket, time
import json, zlib
sys.path.append('./klippy')
import msgproto
FILEHEADER = """
/* DO NOT EDIT! This is an autogenerated file. See scripts/buildcommands.py. */
#include "board/pgm.h"
#include "command.h"
"""
def error(msg):
sys.stderr.write(msg + "\n")
sys.exit(-1)
# Parser for constants in simple C header files.
def scan_config(file):
f = open(file, 'r')
opts = {}
for l in f.readlines():
parts = l.split()
if len(parts) != 3:
continue
if parts[0] != '#define':
continue
value = parts[2]
if value.isdigit() or (value.startswith('0x') and value[2:].isdigit()):
value = int(value, 0)
elif value.startswith('"'):
value = value[1:-1]
opts[parts[1]] = value
f.close()
return opts
######################################################################
# Command and output parser generation
######################################################################
def build_parser(parser, iscmd, all_param_types):
if parser.name == "#empty":
return "\n // Empty message\n .max_size=0,"
if parser.name == "#output":
comment = "Output: " + parser.msgformat
else:
comment = parser.msgformat
params = '0'
types = tuple([t.__class__.__name__ for t in parser.param_types])
if types:
paramid = all_param_types.get(types)
if paramid is None:
paramid = len(all_param_types)
all_param_types[types] = paramid
params = 'command_parameters%d' % (paramid,)
out = """
// %s
.msg_id=%d,
.num_params=%d,
.param_types = %s,
""" % (comment, parser.msgid, len(types), params)
if iscmd:
num_args = (len(types) + types.count('PT_progmem_buffer')
+ types.count('PT_buffer'))
out += " .num_args=%d," % (num_args,)
else:
max_size = min(msgproto.MESSAGE_MAX
, 1 + sum([t.max_length for t in parser.param_types]))
out += " .max_size=%d," % (max_size,)
return out
def build_parsers(parsers, msg_to_id, all_param_types):
pcode = []
for msgname, msg in parsers:
msgid = msg_to_id[msg]
if msgname is None:
parser = msgproto.OutputFormat(msgid, msg)
else:
parser = msgproto.MessageFormat(msgid, msg)
parsercode = build_parser(parser, 0, all_param_types)
pcode.append("{%s\n}, " % (parsercode,))
fmt = """
const struct command_encoder command_encoders[] PROGMEM = {
%s
};
"""
return fmt % ("".join(pcode).strip(),)
def build_param_types(all_param_types):
sorted_param_types = sorted([(i, a) for a, i in all_param_types.items()])
params = ['']
for paramid, argtypes in sorted_param_types:
params.append(
'static const uint8_t command_parameters%d[] PROGMEM = {\n'
' %s };' % (
paramid, ', '.join(argtypes),))
params.append('')
return "\n".join(params)
def build_commands(cmd_by_id, messages_by_name, all_param_types):
max_cmd_msgid = max(cmd_by_id.keys())
index = []
parsers = []
externs = {}
for msgid in range(max_cmd_msgid+1):
if msgid not in cmd_by_id:
index.append(" 0,")
continue
funcname, flags, msgname = cmd_by_id[msgid]
msg = messages_by_name[msgname]
externs[funcname] = 1
parsername = 'parser_%s' % (funcname,)
index.append(" &%s," % (parsername,))
parser = msgproto.MessageFormat(msgid, msg)
parsercode = build_parser(parser, 1, all_param_types)
parsers.append("const struct command_parser %s PROGMEM = {"
" %s\n .flags=%s,\n .func=%s\n};" % (
parsername, parsercode, flags, funcname))
index = "\n".join(index)
externs = "\n".join(["extern void "+funcname+"(uint32_t*);"
for funcname in sorted(externs)])
fmt = """
%s
%s
const struct command_parser * const command_index[] PROGMEM = {
%s
};
const uint8_t command_index_size PROGMEM = ARRAY_SIZE(command_index);
"""
return fmt % (externs, '\n'.join(parsers), index)
######################################################################
# Identify data dictionary generation
######################################################################
def build_identify(cmd_by_id, msg_to_id, responses, static_strings
, config, version):
#commands, messages, static_strings
messages = dict((msgid, msg) for msg, msgid in msg_to_id.items())
data = {}
data['messages'] = messages
data['commands'] = sorted(cmd_by_id.keys())
data['responses'] = sorted(responses)
data['static_strings'] = static_strings
configlist = ['MCU', 'CLOCK_FREQ']
data['config'] = dict((i, config['CONFIG_'+i]) for i in configlist
if 'CONFIG_'+i in config)
data['version'] = version
# Format compressed info into C code
data = json.dumps(data)
zdata = zlib.compress(data, 9)
out = []
for i in range(len(zdata)):
if i % 8 == 0:
out.append('\n ')
out.append(" 0x%02x," % (ord(zdata[i]),))
fmt = """
const uint8_t command_identify_data[] PROGMEM = {%s
};
// Identify size = %d (%d uncompressed)
const uint32_t command_identify_size PROGMEM = ARRAY_SIZE(command_identify_data);
"""
return fmt % (''.join(out), len(zdata), len(data))
######################################################################
# Version generation
######################################################################
# Run program and return the specified output
def check_output(prog):
logging.debug("Running %s" % (repr(prog),))
try:
process = subprocess.Popen(shlex.split(prog), stdout=subprocess.PIPE)
output = process.communicate()[0]
retcode = process.poll()
except OSError:
logging.debug("Exception on run: %s" % (traceback.format_exc(),))
return ""
logging.debug("Got (code=%s): %s" % (retcode, repr(output)))
if retcode:
return ""
try:
return output.decode()
except UnicodeError:
logging.debug("Exception on decode: %s" % (traceback.format_exc(),))
return ""
# Obtain version info from "git" program
def git_version():
if not os.path.exists('.git'):
logging.debug("No '.git' file/directory found")
return ""
ver = check_output("git describe --tags --long --dirty").strip()
logging.debug("Got git version: %s" % (repr(ver),))
return ver
def build_version(extra):
version = git_version()
if not version:
version = "?"
btime = time.strftime("%Y%m%d_%H%M%S")
hostname = socket.gethostname()
version = "%s-%s-%s%s" % (version, btime, hostname, extra)
return version
######################################################################
# Main code
######################################################################
def main():
usage = "%prog [options] <cmd section file> <autoconf.h> <output.c>"
opts = optparse.OptionParser(usage)
opts.add_option("-e", "--extra", dest="extra", default="",
help="extra version string to append to version")
opts.add_option("-v", action="store_true", dest="verbose",
help="enable debug messages")
options, args = opts.parse_args()
if len(args) != 3:
opts.error("Incorrect arguments")
incmdfile, inheader, outcfile = args
if options.verbose:
logging.basicConfig(level=logging.DEBUG)
# Setup
commands = {}
messages_by_name = dict((m.split()[0], m)
for m in msgproto.DefaultMessages.values())
parsers = []
static_strings = []
# Parse request file
f = open(incmdfile, 'rb')
data = f.read()
f.close()
for req in data.split('\0'):
req = req.lstrip()
parts = req.split()
if not parts:
continue
cmd = parts[0]
msg = req[len(cmd)+1:]
if cmd == '_DECL_COMMAND':
funcname, flags, msgname = parts[1:4]
if msgname in commands:
error("Multiple definitions for command '%s'" % msgname)
commands[msgname] = (funcname, flags, msgname)
msg = req.split(None, 3)[3]
m = messages_by_name.get(msgname)
if m is not None and m != msg:
error("Conflicting definition for command '%s'" % msgname)
messages_by_name[msgname] = msg
elif cmd == '_DECL_PARSER':
if len(parts) == 1:
msgname = msg = "#empty"
else:
msgname = parts[1]
m = messages_by_name.get(msgname)
if m is not None and m != msg:
error("Conflicting definition for message '%s'" % msgname)
messages_by_name[msgname] = msg
parsers.append((msgname, msg))
elif cmd == '_DECL_OUTPUT':
parsers.append((None, msg))
elif cmd == '_DECL_STATIC_STR':
static_strings.append(req[17:])
else:
error("Unknown build time command '%s'" % cmd)
# Create unique ids for each message type
msgid = max(msgproto.DefaultMessages.keys())
msg_to_id = dict((m, i) for i, m in msgproto.DefaultMessages.items())
for msgname in commands.keys() + [m for n, m in parsers]:
msg = messages_by_name.get(msgname, msgname)
if msg not in msg_to_id:
msgid += 1
msg_to_id[msg] = msgid
# Create message definitions
all_param_types = {}
parsercode = build_parsers(parsers, msg_to_id, all_param_types)
# Create command definitions
cmd_by_id = dict((msg_to_id[messages_by_name.get(msgname, msgname)], cmd)
for msgname, cmd in commands.items())
cmdcode = build_commands(cmd_by_id, messages_by_name, all_param_types)
paramcode = build_param_types(all_param_types)
# Create identify information
config = scan_config(inheader)
version = build_version(options.extra)
sys.stdout.write("Version: %s\n" % (version,))
responses = [msg_to_id[msg] for msgname, msg in messages_by_name.items()
if msgname not in commands]
icode = build_identify(cmd_by_id, msg_to_id, responses, static_strings
, config, version)
# Write output
f = open(outcfile, 'wb')
f.write(FILEHEADER + paramcode + parsercode + cmdcode + icode)
f.close()
if __name__ == '__main__':
main()

238
scripts/checkstack.py Executable file
View file

@ -0,0 +1,238 @@
#!/usr/bin/env python
# Script that tries to find how much stack space each function in an
# object is using.
#
# Copyright (C) 2015 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
# Usage:
# avr-objdump -d out/klipper.elf | scripts/checkstack.py
import sys
import re
# Functions that change stacks
STACKHOP = []
# List of functions we can assume are never called.
IGNORE = []
OUTPUTDESC = """
#funcname1[preamble_stack_usage,max_usage_with_callers]:
# insn_addr:called_function [usage_at_call_point+caller_preamble,total_usage]
#
#funcname2[p,m,max_usage_to_yield_point]:
# insn_addr:called_function [u+c,t,usage_to_yield_point]
"""
class function:
def __init__(self, funcaddr, funcname):
self.funcaddr = funcaddr
self.funcname = funcname
self.basic_stack_usage = 0
self.max_stack_usage = None
self.yield_usage = -1
self.max_yield_usage = None
self.total_calls = 0
# called_funcs = [(insnaddr, calladdr, stackusage), ...]
self.called_funcs = []
self.subfuncs = {}
# Update function info with a found "yield" point.
def noteYield(self, stackusage):
if self.yield_usage < stackusage:
self.yield_usage = stackusage
# Update function info with a found "call" point.
def noteCall(self, insnaddr, calladdr, stackusage):
if (calladdr, stackusage) in self.subfuncs:
# Already noted a nearly identical call - ignore this one.
return
self.called_funcs.append((insnaddr, calladdr, stackusage))
self.subfuncs[(calladdr, stackusage)] = 1
# Find out maximum stack usage for a function
def calcmaxstack(info, funcs):
if info.max_stack_usage is not None:
return
info.max_stack_usage = max_stack_usage = info.basic_stack_usage
info.max_yield_usage = max_yield_usage = info.yield_usage
total_calls = 0
seenbefore = {}
# Find max of all nested calls.
for insnaddr, calladdr, usage in info.called_funcs:
callinfo = funcs.get(calladdr)
if callinfo is None:
continue
calcmaxstack(callinfo, funcs)
if callinfo.funcname not in seenbefore:
seenbefore[callinfo.funcname] = 1
total_calls += callinfo.total_calls + 1
funcnameroot = callinfo.funcname.split('.')[0]
if funcnameroot in IGNORE:
# This called function is ignored - don't contribute it to
# the max stack.
continue
totusage = usage + callinfo.max_stack_usage
totyieldusage = usage + callinfo.max_yield_usage
if funcnameroot in STACKHOP:
# Don't count children of this function
totusage = totyieldusage = usage
if totusage > max_stack_usage:
max_stack_usage = totusage
if callinfo.max_yield_usage >= 0 and totyieldusage > max_yield_usage:
max_yield_usage = totyieldusage
info.max_stack_usage = max_stack_usage
info.max_yield_usage = max_yield_usage
info.total_calls = total_calls
# Try to arrange output so that functions that call each other are
# near each other.
def orderfuncs(funcaddrs, availfuncs):
l = [(availfuncs[funcaddr].total_calls
, availfuncs[funcaddr].funcname, funcaddr)
for funcaddr in funcaddrs if funcaddr in availfuncs]
l.sort()
l.reverse()
out = []
while l:
count, name, funcaddr = l.pop(0)
info = availfuncs.get(funcaddr)
if info is None:
continue
calladdrs = [calls[1] for calls in info.called_funcs]
del availfuncs[funcaddr]
out = out + orderfuncs(calladdrs, availfuncs) + [info]
return out
hex_s = r'[0-9a-f]+'
re_func = re.compile(r'^(?P<funcaddr>' + hex_s + r') <(?P<func>.*)>:$')
re_asm = re.compile(
r'^[ ]*(?P<insnaddr>' + hex_s
+ r'):\t[^\t]*\t(?P<insn>[^\t]+?)(?P<params>\t[^;]*)?'
+ r'[ ]*(; (?P<calladdr>0x' + hex_s
+ r') <(?P<ref>.*)>)?$')
def main():
unknownfunc = function(None, "<unknown>")
indirectfunc = function(-1, '<indirect>')
unknownfunc.max_stack_usage = indirectfunc.max_stack_usage = 0
unknownfunc.max_yield_usage = indirectfunc.max_yield_usage = -1
funcs = {-1: indirectfunc}
funcaddr = None
datalines = {}
cur = None
atstart = 0
stackusage = 0
# Parse input lines
for line in sys.stdin.readlines():
m = re_func.match(line)
if m is not None:
# Found function
funcaddr = int(m.group('funcaddr'), 16)
funcs[funcaddr] = cur = function(funcaddr, m.group('func'))
stackusage = 0
atstart = 1
continue
m = re_asm.match(line)
if m is None:
if funcaddr not in datalines:
datalines[funcaddr] = line.split()
#print("other", repr(line))
continue
insn = m.group('insn')
if insn == 'push':
stackusage += 1
continue
if insn == 'rcall' and m.group('params').strip() == '.+0':
stackusage += 2
continue
if atstart:
if insn in ['in', 'eor']:
continue
cur.basic_stack_usage = stackusage
atstart = 0
insnaddr = m.group('insnaddr')
calladdr = m.group('calladdr')
if calladdr is None:
if insn == 'ijmp':
# Indirect tail call
cur.noteCall(insnaddr, -1, 0)
elif insn == 'icall':
cur.noteCall(insnaddr, -1, stackusage + 2)
else:
# misc instruction
continue
else:
# Jump or call insn
calladdr = int(calladdr, 16)
ref = m.group('ref')
if '+' in ref:
# Inter-function jump.
pass
elif insn in ('rjmp', 'jmp', 'brne', 'brcs'):
# Tail call
cur.noteCall(insnaddr, calladdr, 0)
elif insn in ('rcall', 'call'):
cur.noteCall(insnaddr, calladdr, stackusage + 2)
else:
print("unknown call", ref)
cur.noteCall(insnaddr, calladdr, stackusage)
# Reset stack usage to preamble usage
stackusage = cur.basic_stack_usage
# Update for known indirect functions
funcsbyname = {}
for info in funcs.values():
funcnameroot = info.funcname.split('.')[0]
funcsbyname[funcnameroot] = info
mainfunc = funcsbyname.get('sched_main')
cmdfunc = funcsbyname.get('command_task')
eventfunc = funcsbyname.get('__vector_13')
for funcnameroot, info in funcsbyname.items():
if (funcnameroot.startswith('_DECL_taskfuncs_')
or funcnameroot.startswith('_DECL_initfuncs_')
or funcnameroot.startswith('_DECL_shutdownfuncs_')):
funcname = funcnameroot[funcnameroot.index('_', 7)+1:]
f = funcsbyname[funcname]
mainfunc.noteCall(0, f.funcaddr, mainfunc.basic_stack_usage + 2)
if funcnameroot.startswith('parser_'):
f = funcsbyname.get(funcnameroot[7:])
if f is not None:
numparams = int(datalines[info.funcaddr][2], 16)
stackusage = cmdfunc.basic_stack_usage + 2 + numparams * 4
cmdfunc.noteCall(0, f.funcaddr, stackusage)
if funcnameroot.endswith('_event'):
eventfunc.noteCall(0, info.funcaddr, eventfunc.basic_stack_usage + 2)
# Calculate maxstackusage
for info in funcs.values():
calcmaxstack(info, funcs)
# Sort functions for output
funcinfos = orderfuncs(funcs.keys(), funcs.copy())
# Show all functions
print(OUTPUTDESC)
for info in funcinfos:
if info.max_stack_usage == 0 and info.max_yield_usage < 0:
continue
yieldstr = ""
if info.max_yield_usage >= 0:
yieldstr = ",%d" % info.max_yield_usage
print("\n%s[%d,%d%s]:" % (info.funcname, info.basic_stack_usage
, info.max_stack_usage, yieldstr))
for insnaddr, calladdr, stackusage in info.called_funcs:
callinfo = funcs.get(calladdr, unknownfunc)
yieldstr = ""
if callinfo.max_yield_usage >= 0:
yieldstr = ",%d" % (stackusage + callinfo.max_yield_usage)
print(" %04s:%-40s [%d+%d,%d%s]" % (
insnaddr, callinfo.funcname, stackusage
, callinfo.basic_stack_usage
, stackusage+callinfo.max_stack_usage, yieldstr))
if __name__ == '__main__':
main()