forked from sampsyo/bril
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
514 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
Tracing JIT with Profile Guided AOT Setting | ||
========================================================= | ||
|
||
This Python based implementation supports the optimization based on the profiling file: <br /> | ||
As you can see from the tests folder, the example named "branch.bril" is the test source code. And the file named "branch_profile.json" is the profiling file which contains the branch instruction and the cooresponding first instructions of the branch destination block. <br/> | ||
The example named "branch.bril" is manually written code, whereas the "branch_profile.json" is generated by a modified brili interpreter. <br/> | ||
|
||
|
||
## Run the code | ||
```javascript | ||
// without tracing based JIT optimization | ||
$ bril2json < tests/branch.bril | brili -p 3 | ||
// with tracing based JIT optimization | ||
$ bril2json < tests/branch.bril | python tracing_JIT.py | brili -p 3 | ||
// with tracing based JIT optimization and DCE optimization | ||
$ bril2json < tests/branch.bril | python tracing_JIT.py | brili -p 3 | ||
``` | ||
|
||
## Sample Outputs | ||
```javascript | ||
// without tracing based JIT optimization | ||
$ bril2json < tests/branch.bril | brili -p 3 | ||
|
||
total_dyn_inst: 10 | ||
|
||
// with tracing based JIT optimization | ||
$ bril2json < tests/branch.bril | python tracing_JIT.py | brili -p 3 | ||
|
||
total_dyn_inst: 12 | ||
|
||
// with tracing based JIT optimization and DCE optimization | ||
$ bril2json < tests/branch.bril | python tracing_JIT.py | brili -p 3 | ||
|
||
total_dyn_inst: 10 | ||
``` | ||
|
||
We evaluate the optimization by leveraging the total number of dynamic instruction count offered by the "-p" flag. For this example, without JIT optimication ,the total cycle count for the input "3" is 10. Then with the JIT optimization, the total cycle count is even larger, i.e., 12. The reason is that we are adding three more instructions (speculate, guard, and commit) in the path and only delete one branch instruction. Thus the total cycle count is 12. <br/> | ||
But if we apply other optimizations, like the DCE optimizaion from previous task, the dead code are eliminated by speculating the branch path. Thus, the total cycle count for the third experiment is 10. As for more complicated examples, this profiling based JIT optimization will save more cycles combined with other optimizing techniques like LVN, LICM, etc. <br/> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
from collections import OrderedDict | ||
from util import fresh | ||
from form_blocks import TERMINATORS | ||
|
||
|
||
def block_map(blocks): | ||
"""Given a sequence of basic blocks, which are lists of instructions, | ||
produce a `OrderedDict` mapping names to blocks. | ||
The name of the block comes from the label it starts with, if any. | ||
Anonymous blocks, which don't start with a label, get an | ||
automatically generated name. Blocks in the mapping have their | ||
labels removed. | ||
""" | ||
by_name = OrderedDict() | ||
|
||
for block in blocks: | ||
# Generate a name for the block. | ||
if 'label' in block[0]: | ||
# The block has a label. Remove the label but use it for the | ||
# block's name. | ||
name = block[0]['label'] | ||
block = block[1:] | ||
else: | ||
# Make up a new name for this anonymous block. | ||
name = fresh('b', by_name) | ||
|
||
# Add the block to the mapping. | ||
by_name[name] = block | ||
|
||
return by_name | ||
|
||
|
||
def successors(instr): | ||
"""Get the list of jump target labels for an instruction. | ||
Raises a ValueError if the instruction is not a terminator (jump, | ||
branch, or return). | ||
""" | ||
if instr['op'] in ('jmp', 'br'): | ||
return instr['labels'] | ||
elif instr['op'] == 'ret': | ||
return [] # No successors to an exit block. | ||
else: | ||
raise ValueError('{} is not a terminator'.format(instr['op'])) | ||
|
||
|
||
def add_terminators(blocks): | ||
"""Given an ordered block map, modify the blocks to add terminators | ||
to all blocks (avoiding "fall-through" control flow transfers). | ||
""" | ||
for i, block in enumerate(blocks.values()): | ||
if not block: | ||
dest = list(blocks.keys())[i + 1] | ||
block.append({'op': 'jmp', 'labels': [dest]}) | ||
elif block[-1]['op'] not in TERMINATORS: | ||
if i == len(blocks) - 1: | ||
# In the last block, return. | ||
block.append({'op': 'ret', 'args': []}) | ||
else: | ||
# Otherwise, jump to the next block. | ||
dest = list(blocks.keys())[i + 1] | ||
block.append({'op': 'jmp', 'labels': [dest]}) | ||
|
||
|
||
def edges(blocks): | ||
"""Given a block map containing blocks complete with terminators, | ||
generate two mappings: predecessors and successors. Both map block | ||
names to lists of block names. | ||
""" | ||
preds = {name: [] for name in blocks} | ||
succs = {name: [] for name in blocks} | ||
for name, block in blocks.items(): | ||
for succ in successors(block[-1]): | ||
succs[name].append(succ) | ||
preds[succ].append(name) | ||
return preds, succs |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
"""Create and print out the basic blocks in a Bril function. | ||
""" | ||
|
||
import json | ||
import sys | ||
|
||
# Instructions that terminate a basic block. | ||
TERMINATORS = 'br', 'jmp', 'ret' | ||
|
||
|
||
def form_blocks(instrs): | ||
"""Given a list of Bril instructions, generate a sequence of | ||
instruction lists representing the basic blocks in the program. | ||
Every instruction in `instr` will show up in exactly one block. Jump | ||
and branch instructions may only appear at the end of a block, and | ||
control can transfer only to the top of a basic block---so labels | ||
can only appear at the *start* of a basic block. Basic blocks may | ||
not be empty. | ||
""" | ||
|
||
# Start with an empty block. | ||
cur_block = [] | ||
|
||
for instr in instrs: | ||
if 'op' in instr: # It's an instruction. | ||
# Add the instruction to the currently-being-formed block. | ||
cur_block.append(instr) | ||
|
||
# If this is a terminator (branching instruction), it's the | ||
# last instruction in the block. Finish this block and | ||
# start a new one. | ||
if instr['op'] in TERMINATORS: | ||
yield cur_block | ||
cur_block = [] | ||
|
||
else: # It's a label. | ||
# End the block here (if it contains anything). | ||
if cur_block: | ||
yield cur_block | ||
|
||
# Start a new block with the label. | ||
cur_block = [instr] | ||
|
||
# Produce the final block, if any. | ||
if cur_block: | ||
yield cur_block | ||
|
||
|
||
def print_blocks(bril): | ||
"""Given a Bril program, print out its basic blocks. | ||
""" | ||
import briltxt | ||
|
||
func = bril['functions'][0] # We only process one function. | ||
for block in form_blocks(func['instrs']): | ||
# Mark the block. | ||
leader = block[0] | ||
if 'label' in leader: | ||
print('block "{}":'.format(leader['label'])) | ||
block = block[1:] # Hide the label, for concision. | ||
else: | ||
print('anonymous block:') | ||
|
||
# Print the instructions. | ||
for instr in block: | ||
print(' {}'.format(briltxt.instr_to_string(instr))) | ||
|
||
|
||
if __name__ == '__main__': | ||
print_blocks(json.load(sys.stdin)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
# input: .JSON stream | ||
# output: .JSON stream - optimized with DCE, removes the unused duplicated assignments | ||
|
||
import sys | ||
import json | ||
from form_blocks import form_blocks | ||
from util import flatten | ||
|
||
def opt_dce(func): | ||
|
||
# global optimization | ||
blocks = list(form_blocks(func['instrs'])) | ||
|
||
# collect useful variable list | ||
used_dest = [] | ||
for block in blocks: | ||
for element in block: | ||
if "args" in element: | ||
for op in element["args"]: | ||
used_dest.append(op) | ||
# remove duplicated element | ||
used_dest = list(dict.fromkeys(used_dest)) | ||
|
||
# iterate all blocks | ||
# delete unused assignment | ||
for block in blocks: | ||
opt_done = False | ||
while(not opt_done): | ||
num_instr = len(block) | ||
# check all instructions and delete the unused ones | ||
for element in block: | ||
del_instr = True | ||
if "dest" in element: | ||
for op in used_dest: | ||
if op == element["dest"]: | ||
del_instr = False | ||
else: | ||
del_instr = False | ||
# delete the useless instructions | ||
if del_instr: | ||
#print("+++"+str(element)) | ||
block.remove(element) | ||
# if no changes, optimization done | ||
if num_instr == len(block): | ||
opt_done = True | ||
|
||
# iterate all blocks | ||
# delete duplicated assignment | ||
# remove duplicated assignment for each block | ||
for block in blocks: | ||
opt_done = False | ||
while(not opt_done): | ||
num_instr = len(block) | ||
# build the assigned but not used dictionary | ||
assigned_not_used = {} | ||
element_ptr = 0 | ||
for element in block: | ||
# check the assigned list | ||
if "dest" in element: | ||
for key,value in assigned_not_used.items(): | ||
if element["dest"] == key: | ||
# delete the old assignment | ||
#print("==="+str(block[value])) | ||
del block[value] | ||
break | ||
# add new assignment | ||
if "dest" in element: | ||
assigned_not_used[element["dest"]] = element_ptr | ||
# update the assigned but not used dicionary | ||
if "args" in element: | ||
for op in element["args"]: | ||
keys = [] | ||
for key,value in assigned_not_used.items(): | ||
if op == key: | ||
keys.append(key) | ||
break | ||
for key in keys: | ||
del assigned_not_used[key] | ||
# update element pointer | ||
element_ptr = element_ptr + 1 | ||
|
||
# if no changes, optimization done | ||
if num_instr == len(block): | ||
opt_done = True | ||
|
||
# Reassemble the function. | ||
func['instrs'] = flatten(blocks) | ||
|
||
def opt_func(func): | ||
opt_dce(func) | ||
|
||
def local_opts(): | ||
# copied from example | ||
# Apply the change to all the functions in the input program. | ||
bril = json.load(sys.stdin) | ||
# iterate all functions | ||
for func in bril['functions']: | ||
opt_func(func) | ||
json.dump(bril, sys.stdout, indent=2, sort_keys=True) | ||
|
||
# "real" main function | ||
if __name__ == "__main__": | ||
local_opts() | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
# Branch example | ||
# For evaluating a tracing JIT | ||
|
||
# pseudo code | ||
# main(x) { | ||
# a = 1; | ||
# if (x < 100) { | ||
# y = y + 1; | ||
# a = 100; | ||
# } | ||
# else { | ||
# y = y - 1; | ||
# } | ||
# print y; | ||
# } | ||
|
||
@main (x: int) { | ||
# const | ||
vc0: int = const 1; | ||
vc1: int = const 100; | ||
# take two input ops, first iteration | ||
v0: int = id x; | ||
v1: int = id vc0; | ||
v2: bool = lt v0 vc1; | ||
br v2 .if.1 .else.1; | ||
.if.1: | ||
v3: int = add v0 vc0; | ||
v1: int = id vc1; | ||
jmp .program.end; | ||
.else.1: | ||
v3: int = sub v0 vc0; | ||
# print out the results | ||
.program.end: | ||
print v3; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ "profile" : [ | ||
{"args":["v2"],"labels":["if.1","else.1"],"op":"br"}, | ||
{"args":["v0","vc0"],"dest":"v3","op":"add","type":"int"}, | ||
{"args":["v0","vc0"],"dest":"v3","op":"sub","type":"int"} | ||
] } |
Oops, something went wrong.