From f3b3e20559d9f3a2bfdb6a5679be30d66ed51074 Mon Sep 17 00:00:00 2001 From: cgyurgyik Date: Tue, 5 Jan 2021 21:35:09 -0500 Subject: [PATCH 1/5] Nonlocal variables~ --- examples/lvn.py | 64 +++++++++++++++++++ .../test/lvn/nonlocal-variable-reuse-2.bril | 10 +++ .../test/lvn/nonlocal-variable-reuse-2.out | 7 ++ .../test/lvn/nonlocal-variable-reuse-3.bril | 13 ++++ .../test/lvn/nonlocal-variable-reuse-3.out | 11 ++++ .../test/lvn/nonlocal-variable-reuse.bril | 9 +++ examples/test/lvn/nonlocal-variable-reuse.out | 7 ++ 7 files changed, 121 insertions(+) create mode 100644 examples/test/lvn/nonlocal-variable-reuse-2.bril create mode 100644 examples/test/lvn/nonlocal-variable-reuse-2.out create mode 100644 examples/test/lvn/nonlocal-variable-reuse-3.bril create mode 100644 examples/test/lvn/nonlocal-variable-reuse-3.out create mode 100644 examples/test/lvn/nonlocal-variable-reuse.bril create mode 100644 examples/test/lvn/nonlocal-variable-reuse.out diff --git a/examples/lvn.py b/examples/lvn.py index cf7f834bc..0fdc34724 100644 --- a/examples/lvn.py +++ b/examples/lvn.py @@ -64,6 +64,56 @@ def read_first(instrs): return read +def rename_nonlocal_duplicates(block): + """Renames any variables that + (1) share a name with an argument in an `id` op where the argument + is not defined in the current block, and + (2) appear after the `id` op. + This avoids a wrong copy propagation. For example, + 1 @main { + 2 a: int = const 42; + 3 .lbl: + 4 b: int = id a; + 5 a: int = const 5; + 6 print b; + 7 } + + Here, we replace line 5 with: + _a: int = const 5; + to ensure that the copy propagation leads to the correct value of `a`. + """ + all_vars = set(instr['dest'] for instr in block if 'dest' in instr) + current_vars = set() + + def fresh_id(v): + while v in all_vars: + v = '_{}'.format(v) + return v + + def rename_until_next_assign(old_var, new_var, index, instrs): + for i in range(index, len(instrs)): + instr = instrs[i] + if 'args' in instr and old_var in instr['args']: + instr['args'] = [new_var if x == old_var else x for x in instr['args']] + if instr.get('dest') == old_var: + return + + id2line = {} + for index, instr in enumerate(block): + if instr.get('op') == 'id': + id_arg = instr['args'][0] + if id_arg not in current_vars and id_arg != instr['dest']: + id2line[id_arg] = index + if 'dest' not in instr: + continue + dest = instr['dest'] + current_vars.add(dest) + if dest in id2line and id2line[dest] < index: + old_var = instr['dest'] + instr['dest'] = fresh_id(dest) + rename_until_next_assign(old_var, instr['dest'], index, block) + + def lvn_block(block, lookup, canonicalize, fold): """Use local value numbering to optimize a basic block. Modify the instructions in place. @@ -96,6 +146,9 @@ def lvn_block(block, lookup, canonicalize, fold): # Track constant values for values assigned with `const`. num2const = {} + # Update names to variables that are used in `id`, yet defined outside the local scope. + rename_nonlocal_duplicates(block) + # Initialize the table with numbers for input variables. These # variables are their own canonical source. for var in read_first(block): @@ -199,6 +252,9 @@ def _lookup(value2num, value): 'le': lambda a, b: a <= b, 'ne': lambda a, b: a != b, 'eq': lambda a, b: a == b, + 'or': lambda a, b: a or b, + 'and': lambda a, b: a and b, + 'not': lambda a: not a } @@ -212,6 +268,14 @@ def _fold(num2const, value): # Equivalent arguments may be evaluated for equality. # E.g. `eq x x`, where `x` is not a constant evaluates to `true`. return value.op != 'ne' + + if value.op in {'and', 'or'} and any(v in num2const for v in value.args): + # Short circuiting the logical operators `and` and `or` for two cases: + # (1) `and x c0` -> false, where `c0` a constant that evaluates to `false`. + # (2) `or x c1` -> true, where `c1` a constant that evaluates to `true`. + const_val = num2const[value.args[0] if value.args[0] in num2const else value.args[1]] + if (value.op == 'and' and not const_val) or (value.op == 'or' and const_val): + return const_val return None except ZeroDivisionError: # If we hit a dynamic error, bail! return None diff --git a/examples/test/lvn/nonlocal-variable-reuse-2.bril b/examples/test/lvn/nonlocal-variable-reuse-2.bril new file mode 100644 index 000000000..9e6c41f20 --- /dev/null +++ b/examples/test/lvn/nonlocal-variable-reuse-2.bril @@ -0,0 +1,10 @@ +# CMD: bril2json < {filename} | python3 ../../lvn.py -p | bril2txt +@main { + a: int = const 42; +.lbl: + # While `a` is outside the local scope, we essentially + # have a no-op here, so nonlocal re-naming isn't necessary. + a: int = id a; + a: int = const 5; + print a; +} diff --git a/examples/test/lvn/nonlocal-variable-reuse-2.out b/examples/test/lvn/nonlocal-variable-reuse-2.out new file mode 100644 index 000000000..f73fc3f0f --- /dev/null +++ b/examples/test/lvn/nonlocal-variable-reuse-2.out @@ -0,0 +1,7 @@ +@main { + a: int = const 42; +.lbl: + a: int = id a; + a: int = const 5; + print a; +} diff --git a/examples/test/lvn/nonlocal-variable-reuse-3.bril b/examples/test/lvn/nonlocal-variable-reuse-3.bril new file mode 100644 index 000000000..da07a1302 --- /dev/null +++ b/examples/test/lvn/nonlocal-variable-reuse-3.bril @@ -0,0 +1,13 @@ +# CMD: bril2json < {filename} | python3 ../../lvn.py -p | bril2txt +@main { + # This should re-name within local scope only with `lvn`. + a: int = const 3; + b: int = id a; + a: int = id b; + print b; + print a; + a: int = const 4; + print a; + a: int = const 5; + print a; +} diff --git a/examples/test/lvn/nonlocal-variable-reuse-3.out b/examples/test/lvn/nonlocal-variable-reuse-3.out new file mode 100644 index 000000000..793ff37e1 --- /dev/null +++ b/examples/test/lvn/nonlocal-variable-reuse-3.out @@ -0,0 +1,11 @@ +@main { + lvn.0: int = const 3; + b: int = const 3; + a: int = const 3; + print lvn.0; + print lvn.0; + lvn.1: int = const 4; + print lvn.1; + a: int = const 5; + print a; +} diff --git a/examples/test/lvn/nonlocal-variable-reuse.bril b/examples/test/lvn/nonlocal-variable-reuse.bril new file mode 100644 index 000000000..e42e798ff --- /dev/null +++ b/examples/test/lvn/nonlocal-variable-reuse.bril @@ -0,0 +1,9 @@ +# CMD: bril2json < {filename} | python3 ../../lvn.py -p | bril2txt +@main { + a: int = const 42; +.lbl: + b: int = id a; + # This should be re-named since `a` is defined outside of the local scope. + a: int = const 5; + print b; +} diff --git a/examples/test/lvn/nonlocal-variable-reuse.out b/examples/test/lvn/nonlocal-variable-reuse.out new file mode 100644 index 000000000..f5a556077 --- /dev/null +++ b/examples/test/lvn/nonlocal-variable-reuse.out @@ -0,0 +1,7 @@ +@main { + a: int = const 42; +.lbl: + b: int = id a; + _a: int = const 5; + print a; +} From b894d30872f98f89ccb3171d8b3e3cb672e564fc Mon Sep 17 00:00:00 2001 From: cgyurgyik Date: Wed, 6 Jan 2021 08:17:07 -0500 Subject: [PATCH 2/5] Cleaner iters --- examples/lvn.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/lvn.py b/examples/lvn.py index 0fdc34724..5756e8b3d 100644 --- a/examples/lvn.py +++ b/examples/lvn.py @@ -90,15 +90,15 @@ def fresh_id(v): v = '_{}'.format(v) return v - def rename_until_next_assign(old_var, new_var, index, instrs): - for i in range(index, len(instrs)): - instr = instrs[i] + def rename_until_next_assign(old_var, new_var, instrs): + for instr in instrs: if 'args' in instr and old_var in instr['args']: - instr['args'] = [new_var if x == old_var else x for x in instr['args']] + instr['args'] = [new_var if a == old_var else a for a in instr['args']] if instr.get('dest') == old_var: return id2line = {} + block_len = len(block) for index, instr in enumerate(block): if instr.get('op') == 'id': id_arg = instr['args'][0] @@ -111,7 +111,7 @@ def rename_until_next_assign(old_var, new_var, index, instrs): if dest in id2line and id2line[dest] < index: old_var = instr['dest'] instr['dest'] = fresh_id(dest) - rename_until_next_assign(old_var, instr['dest'], index, block) + rename_until_next_assign(old_var, instr['dest'], block[index + 1:block_len]) def lvn_block(block, lookup, canonicalize, fold): From 30f4efbcdcf2b6fa4cd90889a5def4d4dec0b646 Mon Sep 17 00:00:00 2001 From: cgyurgyik Date: Wed, 6 Jan 2021 08:43:51 -0500 Subject: [PATCH 3/5] Comments, cleanup --- examples/lvn.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/examples/lvn.py b/examples/lvn.py index 5756e8b3d..75a6c9c5a 100644 --- a/examples/lvn.py +++ b/examples/lvn.py @@ -83,22 +83,28 @@ def rename_nonlocal_duplicates(block): to ensure that the copy propagation leads to the correct value of `a`. """ all_vars = set(instr['dest'] for instr in block if 'dest' in instr) - current_vars = set() def fresh_id(v): + """Appends `_` to `v` until it is uniquely named in the local scope.""" while v in all_vars: v = '_{}'.format(v) return v def rename_until_next_assign(old_var, new_var, instrs): + """Renames all instances of `old_var` to `new_var` until the next + assignment of `old_var`. + """ for instr in instrs: if 'args' in instr and old_var in instr['args']: instr['args'] = [new_var if a == old_var else a for a in instr['args']] if instr.get('dest') == old_var: return + # A running set to track which variables have been defined locally. + current_vars = set() + # Mapping from `id` op of a non-local variable to its line number. id2line = {} - block_len = len(block) + for index, instr in enumerate(block): if instr.get('op') == 'id': id_arg = instr['args'][0] @@ -111,7 +117,7 @@ def rename_until_next_assign(old_var, new_var, instrs): if dest in id2line and id2line[dest] < index: old_var = instr['dest'] instr['dest'] = fresh_id(dest) - rename_until_next_assign(old_var, instr['dest'], block[index + 1:block_len]) + rename_until_next_assign(old_var, instr['dest'], block[index + 1:len(block)]) def lvn_block(block, lookup, canonicalize, fold): From 8dc24a5511e416b5ed3c3069d92f9c12b283fe0e Mon Sep 17 00:00:00 2001 From: cgyurgyik Date: Wed, 6 Jan 2021 08:49:19 -0500 Subject: [PATCH 4/5] Comments. --- examples/lvn.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/examples/lvn.py b/examples/lvn.py index 75a6c9c5a..4747c24f5 100644 --- a/examples/lvn.py +++ b/examples/lvn.py @@ -100,7 +100,7 @@ def rename_until_next_assign(old_var, new_var, instrs): if instr.get('dest') == old_var: return - # A running set to track which variables have been defined locally. + # A running set to track which variables have been assigned locally. current_vars = set() # Mapping from `id` op of a non-local variable to its line number. id2line = {} @@ -109,15 +109,19 @@ def rename_until_next_assign(old_var, new_var, instrs): if instr.get('op') == 'id': id_arg = instr['args'][0] if id_arg not in current_vars and id_arg != instr['dest']: + # Argument of this `id` op is not defined locally. id2line[id_arg] = index if 'dest' not in instr: continue dest = instr['dest'] current_vars.add(dest) if dest in id2line and id2line[dest] < index: + # Local assignment shares a name with + # previous `id` of a non-local variable. old_var = instr['dest'] instr['dest'] = fresh_id(dest) - rename_until_next_assign(old_var, instr['dest'], block[index + 1:len(block)]) + rename_until_next_assign(old_var, instr['dest'], + block[index + 1:len(block)]) def lvn_block(block, lookup, canonicalize, fold): From ac7f889753e23b1934c6e8ed102670c1eeeee418 Mon Sep 17 00:00:00 2001 From: cgyurgyik Date: Wed, 6 Jan 2021 08:51:22 -0500 Subject: [PATCH 5/5] eob --- examples/lvn.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/lvn.py b/examples/lvn.py index 4747c24f5..e7b52bc41 100644 --- a/examples/lvn.py +++ b/examples/lvn.py @@ -120,8 +120,7 @@ def rename_until_next_assign(old_var, new_var, instrs): # previous `id` of a non-local variable. old_var = instr['dest'] instr['dest'] = fresh_id(dest) - rename_until_next_assign(old_var, instr['dest'], - block[index + 1:len(block)]) + rename_until_next_assign(old_var, instr['dest'], block[index + 1:]) def lvn_block(block, lookup, canonicalize, fold):