be5bdb0640
Change-Id: Ie557177625914565bb08acfa9b2410b81156b282
525 linhas
19 KiB
Python
Arquivo Executável
525 linhas
19 KiB
Python
Arquivo Executável
#!/usr/bin/env python3
|
|
import sys
|
|
if sys.version_info[0] < 3:
|
|
raise Exception("Must be using Python 3")
|
|
|
|
import os
|
|
import argparse
|
|
from pathlib import Path
|
|
from struct import *
|
|
from ctypes import *
|
|
import ctypes
|
|
from copy import deepcopy
|
|
from trace_view import view_trace
|
|
import sys
|
|
import glob
|
|
import numpy as np
|
|
import matplotlib.pyplot as plt
|
|
import json
|
|
|
|
class PerfEvent(ctypes.Structure):
|
|
_fields_ = [
|
|
('time', c_uint64),
|
|
('event0', c_uint16),
|
|
('event1', c_uint16),
|
|
('event2', c_uint16),
|
|
('event3', c_uint16),
|
|
('cu', c_uint8),
|
|
('bank', c_uint8),
|
|
]
|
|
def toTuple(self):
|
|
return (int(self.time), int(self.event0), int(self.event1),
|
|
int(self.event2), int(self.event3), int(self.cu), int(self.bank))
|
|
|
|
|
|
class CodeWrapped(ctypes.Structure):
|
|
""" Matches CodeWrapped on the python side """
|
|
_fields_ = [('line', ctypes.c_char_p),
|
|
('loc', ctypes.c_char_p),
|
|
('value', ctypes.c_int),
|
|
('to_line', ctypes.c_int),
|
|
('index', ctypes.c_int),
|
|
('line_num', ctypes.c_int)]
|
|
|
|
|
|
class KvPair(ctypes.Structure):
|
|
""" Matches pair<int, int> = (key, value) on the python side """
|
|
_fields_ = [('key', ctypes.c_int),
|
|
('value', ctypes.c_int)]
|
|
|
|
|
|
class ReturnAssemblyInfo(ctypes.Structure):
|
|
""" Matches ReturnAssemblyInfo on the python side """
|
|
_fields_ = [('code', POINTER(CodeWrapped)),
|
|
('jumps', POINTER(KvPair)),
|
|
('code_len', ctypes.c_int),
|
|
('jumps_len', ctypes.c_int)]
|
|
|
|
|
|
class Wave(ctypes.Structure):
|
|
_fields_ = [
|
|
('simd', ctypes.c_uint64),
|
|
('wave_id', ctypes.c_uint64),
|
|
('begin_time', ctypes.c_uint64), # Begin and end cycle
|
|
('end_time', ctypes.c_uint64),
|
|
|
|
# total VMEM/FLAT/LDS/SMEM instructions issued
|
|
# total issued memory instructions
|
|
('num_mem_instrs', ctypes.c_uint64),
|
|
# total issued instructions (compute + memory)
|
|
('num_issued_instrs', ctypes.c_uint64),
|
|
('num_valu_instrs', ctypes.c_uint64),
|
|
('num_valu_stalls', ctypes.c_uint64),
|
|
# VMEM Pipeline: instrs and stalls
|
|
('num_vmem_instrs', ctypes.c_uint64),
|
|
('num_vmem_stalls', ctypes.c_uint64),
|
|
# FLAT instrs and stalls
|
|
('num_flat_instrs', ctypes.c_uint64),
|
|
('num_flat_stalls', ctypes.c_uint64),
|
|
|
|
# LDS instr and stalls
|
|
('num_lds_instrs', ctypes.c_uint64),
|
|
('num_lds_stalls', ctypes.c_uint64),
|
|
|
|
# SCA instrs stalls
|
|
('num_salu_instrs', ctypes.c_uint64),
|
|
('num_smem_instrs', ctypes.c_uint64),
|
|
('num_salu_stalls', ctypes.c_uint64),
|
|
('num_smem_stalls', ctypes.c_uint64),
|
|
|
|
# Branch
|
|
('num_branch_instrs', ctypes.c_uint64),
|
|
('num_branch_taken_instrs', ctypes.c_uint64),
|
|
('num_branch_stalls', ctypes.c_uint64),
|
|
|
|
('timeline_string', ctypes.c_char_p),
|
|
('instructions_string', ctypes.c_char_p)]
|
|
|
|
|
|
class ReturnInfo(ctypes.Structure):
|
|
_fields_ = [('num_waves', ctypes.c_uint64),
|
|
('wavedata', POINTER(Wave)),
|
|
('num_events', ctypes.c_uint64),
|
|
('perfevents', POINTER(PerfEvent))]
|
|
|
|
rocprofv2_att_lib = os.getenv('ROCPROFV2_ATT_LIB_PATH')
|
|
try: # For build dir
|
|
path_to_parser = os.path.abspath(rocprofv2_att_lib)
|
|
SO = CDLL(path_to_parser)
|
|
except: # For installed dir
|
|
path_to_parser = os.path.abspath('/opt/rocm/lib/hsa-amd-aqlprofile/librocprofv2_att.so')
|
|
SO = CDLL(path_to_parser)
|
|
|
|
SO.AnalyseBinary.restype = ReturnInfo
|
|
SO.AnalyseBinary.argtypes = [ctypes.c_char_p, ctypes.c_int, ctypes.c_bool]
|
|
SO.wrapped_parse_binary.argtypes = [ctypes.c_char_p, ctypes.c_char_p]
|
|
SO.wrapped_parse_binary.restype = ReturnAssemblyInfo
|
|
|
|
def parse_binary(filename, kernel=None):
|
|
if kernel is None or kernel == '':
|
|
kernel = ctypes.c_char_p(0)
|
|
print('Parsing all kernels')
|
|
else:
|
|
with open(glob.glob(kernel)[0], 'r') as file:
|
|
kernel = file.readlines()
|
|
print('Parsing kernel:', kernel[0].split(': ')[0])
|
|
kernel = kernel[0].split(': ')[1].split('.kd')[0]
|
|
kernel = str(kernel).encode('utf-8')
|
|
filename = os.path.abspath(str(filename))
|
|
info = SO.wrapped_parse_binary(str(filename).encode('utf-8'), kernel)
|
|
|
|
code = []
|
|
for k in range(info.code_len):
|
|
code_entry = info.code[k]
|
|
|
|
# copy string memory from C++
|
|
line = deepcopy(code_entry.line.decode("utf-8"))
|
|
loc = deepcopy(code_entry.loc.decode("utf-8"))
|
|
|
|
# Transform empty entries back to python's None
|
|
to_line = int(code_entry.to_line) if (code_entry.to_line >= 0) else None
|
|
loc = loc if len(loc) > 0 else None
|
|
|
|
code.append((line, int(code_entry.value), to_line, loc,
|
|
int(code_entry.index), int(code_entry.line_num)))
|
|
|
|
jumps = {}
|
|
for k in range(info.jumps_len):
|
|
jumps[info.jumps[k].key] = info.jumps[k].value
|
|
|
|
return code, jumps
|
|
|
|
|
|
def getWaves(filename, target_cu, verbose):
|
|
filename = os.path.abspath(str(filename))
|
|
info = SO.AnalyseBinary(filename.encode('utf-8'), target_cu, verbose)
|
|
|
|
waves = [info.wavedata[k] for k in range(info.num_waves)]
|
|
events = [deepcopy(info.perfevents[k]) for k in range(info.num_events)]
|
|
|
|
for wave in waves:
|
|
wave.timeline = deepcopy(wave.timeline_string.decode("utf-8"))
|
|
wave.instructions = deepcopy(wave.instructions_string.decode("utf-8"))
|
|
|
|
return waves, events
|
|
|
|
|
|
def persist(output_ui, trace_file, SIMD):
|
|
trace = Path(trace_file).name
|
|
simds, waves = [], []
|
|
begin_time, end_time, timeline, instructions = [], [], [], []
|
|
mem_ins, issued_ins, valu_ins, valu_stalls = [], [], [], []
|
|
vmem_ins, vmem_stalls, flat_ins, flat_stalls = [], [], [], []
|
|
lds_ins, lds_stalls, salu_ins, salu_stalls = [], [], [], []
|
|
smem_ins, smem_stalls, br_ins, br_taken_ins, br_stalls = [], [], [], [], []
|
|
|
|
for wave in SIMD:
|
|
simds.append(wave.simd)
|
|
waves.append(wave.wave_id)
|
|
begin_time.append(wave.begin_time)
|
|
end_time.append(wave.end_time)
|
|
mem_ins.append(wave.num_mem_instrs)
|
|
issued_ins.append(wave.num_issued_instrs)
|
|
valu_ins.append(wave.num_valu_instrs)
|
|
valu_stalls.append(wave.num_valu_stalls)
|
|
vmem_ins.append(wave.num_vmem_instrs)
|
|
vmem_stalls.append(wave.num_vmem_stalls)
|
|
flat_ins.append(wave.num_flat_instrs)
|
|
flat_stalls.append(wave.num_flat_stalls)
|
|
lds_ins.append(wave.num_lds_instrs)
|
|
lds_stalls.append(wave.num_lds_stalls)
|
|
salu_ins.append(wave.num_salu_instrs)
|
|
salu_stalls.append(wave.num_salu_stalls)
|
|
smem_ins.append(wave.num_smem_instrs)
|
|
smem_stalls.append(wave.num_smem_stalls)
|
|
br_ins.append(wave.num_branch_instrs)
|
|
br_taken_ins.append(wave.num_branch_taken_instrs)
|
|
br_stalls.append(wave.num_branch_stalls)
|
|
timeline.append(wave.timeline)
|
|
instructions.append(wave.instructions)
|
|
|
|
#df = pd.DataFrame({
|
|
df = {
|
|
'name': [trace for _ in range(len(begin_time))],
|
|
'id': [i for i in range(len(begin_time))],
|
|
'simd': simds,
|
|
'wave_slot': waves,
|
|
'begin_time': begin_time,
|
|
'end_time': end_time,
|
|
'mem_ins': mem_ins,
|
|
'issued_ins': issued_ins,
|
|
'valu_ins': valu_ins,
|
|
'valu_stalls': valu_stalls,
|
|
'vmem_ins': vmem_ins,
|
|
'vmem_stalls': vmem_stalls,
|
|
'flat_ins': flat_ins,
|
|
'flat_stalls': flat_stalls,
|
|
'lds_ins': lds_ins,
|
|
'lds_stalls': lds_stalls,
|
|
'salu_ins': salu_ins,
|
|
'salu_stalls': salu_stalls,
|
|
'smem_ins': smem_ins,
|
|
'smem_stalls': smem_stalls,
|
|
'br_ins': br_ins,
|
|
'br_taken_ins': br_taken_ins,
|
|
'br_stalls': br_stalls,
|
|
'timeline': timeline,
|
|
'instructions': instructions,
|
|
}#)
|
|
#[print(d) for c, d in df.iterrows()]; quit()
|
|
return df
|
|
|
|
|
|
def mem_max(array):
|
|
mem_dict = {}
|
|
for SE in array:
|
|
for wave in SE:
|
|
for inst in wave:
|
|
try:
|
|
mem_dict[inst[0]][0] = max(mem_dict[inst[0]][0], inst[1])
|
|
except:
|
|
mem_dict[inst[0]] = inst[1:]
|
|
assert(mem_dict[inst[0]][1] == inst[2])
|
|
|
|
return mem_dict
|
|
|
|
def lgk(count):
|
|
return 'lgkmcnt({0})'.format(count)
|
|
def vmc(count):
|
|
return 'vmcnt({0})'.format(count)
|
|
def both_cnt(count):
|
|
return lgk(count)+' '+vmc(count)
|
|
|
|
def insert_waitcnt(flight_count, assembly_code):
|
|
flight_count = mem_max(flight_count)
|
|
for key in sorted(flight_count):
|
|
line_n = key
|
|
issue_amount, waitcnt_amount, = flight_count[key]
|
|
if 'vmcnt' in assembly_code[line_n] and 'lgkmcnt' in assembly_code[line_n]:
|
|
counter_type = both_cnt
|
|
elif 'vmcnt' in assembly_code[line_n]:
|
|
counter_type = vmc
|
|
elif 'lgkmcnt' in assembly_code[line_n]:
|
|
counter_type = lgk
|
|
else:
|
|
print('Error: Line mismatch')
|
|
exit(-1)
|
|
|
|
for count in range(waitcnt_amount+1, issue_amount):
|
|
print('Inserted line: '+str(line_n))
|
|
as_index = line_n - count/(issue_amount+1)
|
|
assembly_code[as_index] = \
|
|
'\ts_waitcnt {0}\t\t; Timing analysis.'.format(counter_type(count))
|
|
as_index += 0.5/(issue_amount+1)
|
|
assembly_code[as_index] = '\ts_nop 0\t\t\t\t\t\t; Counters: '+str(issue_amount)
|
|
|
|
return assembly_code
|
|
|
|
|
|
def Copy_Files(output_ui):
|
|
curpath = os.path.dirname(os.path.abspath(__file__))
|
|
outpath = output_ui+'/ui/'
|
|
|
|
os.makedirs(outpath, exist_ok=True)
|
|
os.system('cp '+curpath+'/ui/* '+outpath)
|
|
|
|
|
|
def get_delta_time(events):
|
|
try:
|
|
CUS = [[e.time for e in events if e.cu==k and e.bank==0] for k in range(16)]
|
|
CUS = [np.asarray(c).astype(np.int64) for c in CUS if len(c) > 2]
|
|
return np.min([np.min(abs(c[1:]-c[:-1])) for c in CUS])
|
|
except:
|
|
return 1
|
|
|
|
def draw_wave_metrics(selections, normalize):
|
|
global PIC_SAVE_FOLDER
|
|
global EVENTS
|
|
global EVENT_NAMES
|
|
|
|
#event_names = ['Busy CUs', 'Occupancy', 'Eligible waves', 'Waves waiting']
|
|
with open(os.path.join(PIC_SAVE_FOLDER,'counters.json'), 'w') as f:
|
|
f.write(json.dumps({"counters": EVENT_NAMES}))
|
|
|
|
plt.figure(figsize=(15,3))
|
|
|
|
delta_time = max(1,int(0.5+np.min([get_delta_time(events) for events in EVENTS])))
|
|
maxtime = np.max([np.max([e.time for e in events]) for events in EVENTS])+1
|
|
event_timeline = np.zeros((16, maxtime), dtype=np.int32)
|
|
print('Delta:', delta_time)
|
|
print('Max_cycles:', maxtime)
|
|
|
|
kernsize = 2*(delta_time//14)+1
|
|
trim = max(maxtime//5000,1)
|
|
cycles = 4*np.arange(maxtime)[::trim]
|
|
|
|
kernel = np.asarray([np.exp(-abs(k/kernsize)**2) for k in range(-kernsize*3,kernsize*3+1)])
|
|
kernel /= np.sum(kernel)*len(EVENTS)*delta_time
|
|
|
|
for events in EVENTS:
|
|
for e in range(len(events)-1):
|
|
bk = events[e].bank*4
|
|
start = events[e].time
|
|
end = start+delta_time
|
|
event_timeline[bk:bk+4, start:end] += np.asarray(events[e].toTuple()[1:5])[:, None]
|
|
start = events[-1].time
|
|
event_timeline[bk:bk+4, start:start+delta_time] += \
|
|
np.asarray(events[-1].toTuple()[1:5])[:, None]
|
|
|
|
|
|
event_timeline = [np.convolve(e, kernel)[3*kernsize:-3*kernsize] for e in event_timeline]
|
|
|
|
if normalize:
|
|
event_timeline = [100*e/max(e.max(), 1E-5) for e in event_timeline]
|
|
|
|
colors = ['blue', 'green', 'gray', 'red', 'orange', 'cyan', 'black', 'darkviolet',
|
|
'yellow', 'darkred', 'pink', 'lime', 'gold', 'tan', 'aqua', 'olive']
|
|
[plt.plot(cycles, e[::trim], '-', label=n, color=c)
|
|
for e, n, c, sel in zip(event_timeline, EVENT_NAMES, colors, selections) if sel]
|
|
|
|
plt.legend()
|
|
if normalize:
|
|
plt.ylabel('As % of maximum')
|
|
else:
|
|
plt.ylabel('Value')
|
|
plt.subplots_adjust(left=0.05, right=1, top=1, bottom=0.07)
|
|
plt.savefig(os.path.join(PIC_SAVE_FOLDER,'timeline.png'), dpi=150)
|
|
#plt.show()
|
|
|
|
|
|
def draw_wave_states(selections, normalize):
|
|
global TIMELINES
|
|
global PIC_SAVE_FOLDER
|
|
plot_indices = [1, 2, 3, 4]
|
|
STATES = [['Empty', 'Idle', 'Exec', 'Wait', 'Stall'][k] for k in plot_indices]
|
|
colors = [['gray', 'orange', 'green', 'red', 'blue'][k] for k in plot_indices]
|
|
|
|
plt.figure(figsize=(15,3))
|
|
|
|
maxtime = max([np.max((TIMELINES[k]!=0)*np.arange(0,TIMELINES[k].size)) for k in plot_indices])
|
|
timelines = [deepcopy(TIMELINES[k][:maxtime]) for k in plot_indices]
|
|
timelines = [np.pad(t, [0, maxtime-t.size]) for t in timelines]
|
|
|
|
if normalize:
|
|
timelines = np.array(timelines) / np.maximum(np.sum(timelines,0)*1E-2,1E-7)
|
|
|
|
kernsize = maxtime//150+1
|
|
trim = max(maxtime//5000,1)
|
|
cycles = np.arange(timelines[0].size)[::trim]
|
|
|
|
kernel = np.asarray([np.exp(-abs(10*k/kernsize)) for k in range(-kernsize//2,kernsize//2+1)])
|
|
kernel /= np.sum(kernel)
|
|
|
|
timelines = [np.convolve(time, kernel)[kernsize//2:-kernsize//2][::trim] if len(time) > 0 else cycles*0 for time in timelines]
|
|
|
|
with open(os.path.join(PIC_SAVE_FOLDER,'counters.json'), 'w') as f:
|
|
f.write(json.dumps({"counters": STATES}))
|
|
|
|
[plt.plot(cycles, t, label='State '+s, linewidth=1.1, color=c)
|
|
for t, s, c, sel in zip(timelines, STATES, colors, selections) if sel]
|
|
|
|
plt.legend()
|
|
if normalize:
|
|
plt.ylabel('Waves state %')
|
|
else:
|
|
plt.ylabel('Waves state total')
|
|
plt.ylim(-1)
|
|
plt.xlim(-maxtime//200, maxtime+maxtime//200+1)
|
|
plt.subplots_adjust(left=0.05, right=1, top=1, bottom=0.07)
|
|
plt.savefig(os.path.join(PIC_SAVE_FOLDER,'timeline.png'), dpi=150)
|
|
|
|
|
|
def GeneratePIC(selections=[True for k in range(16)], normalize=True, bScounter=True):
|
|
if bScounter and len(EVENTS) > 0 and np.sum([len(e) for e in EVENTS]) > 32:
|
|
draw_wave_metrics(selections, normalize)
|
|
else:
|
|
draw_wave_states(selections, normalize)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
pathenv = os.getenv('OUTPUT_PATH')
|
|
if pathenv is None:
|
|
pathenv = "."
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("assembly_code", help="Path of the assembly code")
|
|
parser.add_argument("--trace_file", help="Filter for trace files", default=None, type=str)
|
|
parser.add_argument("-o", "--output_ui", help="Output Folder", default='.')
|
|
parser.add_argument("-k", "--att_kernel", help="Kernel file", type=str, default=pathenv+'/*_kernel.txt')
|
|
parser.add_argument("-p", "--ports", help="Server and websocket ports, default: 8000,18000")
|
|
parser.add_argument("--target_cu", help="Collected target CU id{0-15}", type=int, default=None)
|
|
parser.add_argument("-g", "--genasm",
|
|
help="Generate post-processed asm file at this path", type=str, default="")
|
|
args = parser.parse_args()
|
|
|
|
global EVENT_NAMES
|
|
with open(os.getenv("COUNTERS_PATH"), 'r') as f:
|
|
lines = [l.split('//')[0] for l in f.readlines()]
|
|
|
|
EVENT_NAMES = []
|
|
clean = lambda x: x.split('=')[1].split(' ')[0].split('\n')[0]
|
|
for line in lines:
|
|
if 'PERFCOUNTER_ID=' in line:
|
|
EVENT_NAMES += ['id: '+clean(line)]
|
|
elif args.target_cu is None and 'att: TARGET_CU' in line:
|
|
args.target_cu = int(clean(line))
|
|
print('Target CU set to:', args.target_cu)
|
|
for line in lines:
|
|
if 'PERFCOUNTER=' in line:
|
|
EVENT_NAMES += [clean(line).split('SQ_')[1].lower()]
|
|
|
|
if args.target_cu is None:
|
|
args.target_cu = 1
|
|
|
|
# Assembly parsing
|
|
path = Path(args.assembly_code)
|
|
if not path.is_file():
|
|
print("Invalid assembly_code('{0}')!".format(args.assembly_code))
|
|
sys.exit(1)
|
|
|
|
att_kernel = glob.glob(args.att_kernel)
|
|
|
|
if len(att_kernel) == 0:
|
|
print('Could not find att output kernel:', args.att_kernel)
|
|
exit(1)
|
|
elif len(att_kernel) > 1:
|
|
print('Found multiple kernel matching given filters:')
|
|
for n, k in enumerate(att_kernel):
|
|
print('\t', n, '->', k)
|
|
|
|
bValid = False
|
|
while bValid == False:
|
|
try:
|
|
args.att_kernel = att_kernel[int(input("Please select number: "))]
|
|
bValid = True
|
|
except KeyboardInterrupt:
|
|
exit(0)
|
|
except:
|
|
print('Invalid option.')
|
|
else:
|
|
args.att_kernel = att_kernel[0]
|
|
|
|
print('Att kernel:', args.att_kernel)
|
|
code, jumps = parse_binary(args.assembly_code, args.att_kernel)
|
|
|
|
# Trace Parsing
|
|
if args.trace_file is None:
|
|
filenames = glob.glob(args.att_kernel.split('_kernel.txt')[0]+'*.att')
|
|
assert(len(filenames) > 0)
|
|
else:
|
|
filenames = glob.glob(args.trace_file)
|
|
|
|
print('Trace filenames:', filenames)
|
|
|
|
Copy_Files(args.output_ui)
|
|
DBFILES = []
|
|
global TIMELINES
|
|
global EVENTS
|
|
TIMELINES = [np.zeros(int(1E4),dtype=np.int32) for k in range(5)]
|
|
EVENTS = []
|
|
|
|
analysed_filenames = []
|
|
for name in filenames:
|
|
SIMD, perfevents = getWaves(name, args.target_cu, False)
|
|
if len(SIMD) == 0:
|
|
print("Error parsing ", name)
|
|
continue
|
|
analysed_filenames.append(name)
|
|
EVENTS.append(perfevents)
|
|
DBFILES.append( persist(args.output_ui, name, SIMD) )
|
|
for wave in SIMD:
|
|
time_acc = 0
|
|
tuples1 = wave.timeline.split('(')
|
|
tuples2 = [t.split(')')[0].split(',') for t in tuples1 if t != '']
|
|
tuples3 = [(int(t[0]),int(t[1])) for t in tuples2]
|
|
|
|
for state in tuples3:
|
|
if state[1] > 1E7:
|
|
print('Warning: Time limit reached for ',state[0], state[1])
|
|
break
|
|
|
|
if time_acc+state[1] > TIMELINES[state[0]].size:
|
|
TIMELINES[state[0]] = np.hstack([
|
|
TIMELINES[state[0]],
|
|
np.zeros_like(TIMELINES[state[0]])
|
|
])
|
|
TIMELINES[state[0]][time_acc:time_acc+state[1]] += 1
|
|
time_acc += state[1]
|
|
|
|
global PIC_SAVE_FOLDER
|
|
PIC_SAVE_FOLDER = os.path.abspath(os.path.join(args.output_ui, 'ui'))
|
|
|
|
if args.genasm and len(args.genasm) > 0:
|
|
flight_count = view_trace(args, 0, code, jumps, DBFILES, analysed_filenames, True, None)
|
|
|
|
with open(args.assembly_code, 'r') as file:
|
|
lines = file.readlines()
|
|
assembly_code = {l+1.0: lines[l][:-1] for l in range(len(lines))}
|
|
assembly_code = insert_waitcnt(flight_count, assembly_code)
|
|
|
|
with open(args.genasm, 'w') as file:
|
|
keys = sorted(assembly_code.keys())
|
|
for k in keys:
|
|
file.write(assembly_code[k]+'\n')
|
|
else:
|
|
view_trace(args, 0, code, jumps, DBFILES, analysed_filenames, False, GeneratePIC)
|