300 lines
9.9 KiB
Python
300 lines
9.9 KiB
Python
|
#
|
||
|
# Copyright (c) 2017 Intel Corporation
|
||
|
# SPDX-License-Identifier: BSD-2-Clause
|
||
|
#
|
||
|
|
||
|
import numba
|
||
|
import numba.parfors.parfor
|
||
|
from numba import njit
|
||
|
from numba.core import ir_utils
|
||
|
from numba.core import types, ir, compiler
|
||
|
from numba.core.registry import cpu_target
|
||
|
from numba.core.ir_utils import (copy_propagate, apply_copy_propagate,
|
||
|
get_name_var_table, remove_dels, remove_dead,
|
||
|
remove_call_handlers, alias_func_extensions)
|
||
|
from numba.core.typed_passes import type_inference_stage
|
||
|
from numba.core.compiler_machinery import FunctionPass, register_pass, PassManager
|
||
|
from numba.core.untyped_passes import (ExtractByteCode, TranslateByteCode, FixupArgs,
|
||
|
IRProcessing, DeadBranchPrune,
|
||
|
RewriteSemanticConstants, GenericRewrites,
|
||
|
WithLifting, PreserveIR, InlineClosureLikes)
|
||
|
|
||
|
from numba.core.typed_passes import (NopythonTypeInference, AnnotateTypes,
|
||
|
NopythonRewrites, PreParforPass, ParforPass,
|
||
|
DumpParforDiagnostics, NativeLowering,
|
||
|
IRLegalization, NoPythonBackend, NativeLowering)
|
||
|
import numpy as np
|
||
|
from numba.tests.support import skip_parfors_unsupported, needs_blas
|
||
|
import unittest
|
||
|
|
||
|
|
||
|
def test_will_propagate(b, z, w):
|
||
|
x1 = 3
|
||
|
x = x1
|
||
|
if b > 0:
|
||
|
y = z + w
|
||
|
else:
|
||
|
y = 0
|
||
|
a = 2 * x
|
||
|
return a < b
|
||
|
|
||
|
def null_func(a,b,c,d):
|
||
|
False
|
||
|
|
||
|
@numba.njit
|
||
|
def dummy_aliased_func(A):
|
||
|
return A
|
||
|
|
||
|
def alias_ext_dummy_func(lhs_name, args, alias_map, arg_aliases):
|
||
|
ir_utils._add_alias(lhs_name, args[0].name, alias_map, arg_aliases)
|
||
|
|
||
|
def findLhsAssign(func_ir, var):
|
||
|
for label, block in func_ir.blocks.items():
|
||
|
for i, inst in enumerate(block.body):
|
||
|
if isinstance(inst, ir.Assign) and inst.target.name==var:
|
||
|
return True
|
||
|
|
||
|
return False
|
||
|
|
||
|
class TestRemoveDead(unittest.TestCase):
|
||
|
|
||
|
_numba_parallel_test_ = False
|
||
|
|
||
|
def compile_parallel(self, func, arg_types):
|
||
|
return njit(arg_types, parallel=True, fastmath=True)(func)
|
||
|
|
||
|
def test1(self):
|
||
|
typingctx = cpu_target.typing_context
|
||
|
targetctx = cpu_target.target_context
|
||
|
test_ir = compiler.run_frontend(test_will_propagate)
|
||
|
|
||
|
typingctx.refresh()
|
||
|
targetctx.refresh()
|
||
|
args = (types.int64, types.int64, types.int64)
|
||
|
typemap, _, calltypes, _ = type_inference_stage(typingctx, targetctx, test_ir, args, None)
|
||
|
remove_dels(test_ir.blocks)
|
||
|
in_cps, out_cps = copy_propagate(test_ir.blocks, typemap)
|
||
|
apply_copy_propagate(test_ir.blocks, in_cps, get_name_var_table(test_ir.blocks), typemap, calltypes)
|
||
|
|
||
|
remove_dead(test_ir.blocks, test_ir.arg_names, test_ir)
|
||
|
self.assertFalse(findLhsAssign(test_ir, "x"))
|
||
|
|
||
|
def test2(self):
|
||
|
def call_np_random_seed():
|
||
|
np.random.seed(2)
|
||
|
|
||
|
def seed_call_exists(func_ir):
|
||
|
for inst in func_ir.blocks[0].body:
|
||
|
if (isinstance(inst, ir.Assign) and
|
||
|
isinstance(inst.value, ir.Expr) and
|
||
|
inst.value.op == 'call' and
|
||
|
func_ir.get_definition(inst.value.func).attr == 'seed'):
|
||
|
return True
|
||
|
return False
|
||
|
|
||
|
test_ir = compiler.run_frontend(call_np_random_seed)
|
||
|
remove_dead(test_ir.blocks, test_ir.arg_names, test_ir)
|
||
|
self.assertTrue(seed_call_exists(test_ir))
|
||
|
|
||
|
def run_array_index_test(self, func):
|
||
|
A1 = np.arange(6).reshape(2,3)
|
||
|
A2 = A1.copy()
|
||
|
i = 0
|
||
|
pfunc = self.compile_parallel(func, (numba.typeof(A1), numba.typeof(i)))
|
||
|
|
||
|
func(A1, i)
|
||
|
pfunc(A2, i)
|
||
|
np.testing.assert_array_equal(A1, A2)
|
||
|
|
||
|
def test_alias_ravel(self):
|
||
|
def func(A, i):
|
||
|
B = A.ravel()
|
||
|
B[i] = 3
|
||
|
|
||
|
self.run_array_index_test(func)
|
||
|
|
||
|
def test_alias_flat(self):
|
||
|
def func(A, i):
|
||
|
B = A.flat
|
||
|
B[i] = 3
|
||
|
|
||
|
self.run_array_index_test(func)
|
||
|
|
||
|
def test_alias_transpose1(self):
|
||
|
def func(A, i):
|
||
|
B = A.T
|
||
|
B[i,0] = 3
|
||
|
|
||
|
self.run_array_index_test(func)
|
||
|
|
||
|
def test_alias_transpose2(self):
|
||
|
def func(A, i):
|
||
|
B = A.transpose()
|
||
|
B[i,0] = 3
|
||
|
|
||
|
self.run_array_index_test(func)
|
||
|
|
||
|
def test_alias_transpose3(self):
|
||
|
def func(A, i):
|
||
|
B = np.transpose(A)
|
||
|
B[i,0] = 3
|
||
|
|
||
|
self.run_array_index_test(func)
|
||
|
|
||
|
@skip_parfors_unsupported
|
||
|
@needs_blas
|
||
|
def test_alias_ctypes(self):
|
||
|
# use xxnrm2 to test call a C function with ctypes
|
||
|
from numba.np.linalg import _BLAS
|
||
|
xxnrm2 = _BLAS().numba_xxnrm2(types.float64)
|
||
|
|
||
|
def remove_dead_xxnrm2(rhs, lives, call_list):
|
||
|
if call_list == [xxnrm2]:
|
||
|
return rhs.args[4].name not in lives
|
||
|
return False
|
||
|
|
||
|
# adding this handler has no-op effect since this function won't match
|
||
|
# anything else but it's a bit cleaner to save the state and recover
|
||
|
old_remove_handlers = remove_call_handlers[:]
|
||
|
remove_call_handlers.append(remove_dead_xxnrm2)
|
||
|
|
||
|
def func(ret):
|
||
|
a = np.ones(4)
|
||
|
xxnrm2(100, 4, a.ctypes, 1, ret.ctypes)
|
||
|
|
||
|
A1 = np.zeros(1)
|
||
|
A2 = A1.copy()
|
||
|
|
||
|
try:
|
||
|
pfunc = self.compile_parallel(func, (numba.typeof(A1),))
|
||
|
numba.njit(func)(A1)
|
||
|
pfunc(A2)
|
||
|
finally:
|
||
|
# recover global state
|
||
|
remove_call_handlers[:] = old_remove_handlers
|
||
|
|
||
|
self.assertEqual(A1[0], A2[0])
|
||
|
|
||
|
def test_alias_reshape1(self):
|
||
|
def func(A, i):
|
||
|
B = np.reshape(A, (3,2))
|
||
|
B[i,0] = 3
|
||
|
|
||
|
self.run_array_index_test(func)
|
||
|
|
||
|
def test_alias_reshape2(self):
|
||
|
def func(A, i):
|
||
|
B = A.reshape(3,2)
|
||
|
B[i,0] = 3
|
||
|
|
||
|
self.run_array_index_test(func)
|
||
|
|
||
|
def test_alias_func_ext(self):
|
||
|
def func(A, i):
|
||
|
B = dummy_aliased_func(A)
|
||
|
B[i, 0] = 3
|
||
|
|
||
|
# save global state
|
||
|
old_ext_handlers = alias_func_extensions.copy()
|
||
|
try:
|
||
|
alias_func_extensions[('dummy_aliased_func',
|
||
|
'numba.tests.test_remove_dead')] = alias_ext_dummy_func
|
||
|
self.run_array_index_test(func)
|
||
|
finally:
|
||
|
# recover global state
|
||
|
ir_utils.alias_func_extensions = old_ext_handlers
|
||
|
|
||
|
def test_rm_dead_rhs_vars(self):
|
||
|
"""make sure lhs variable of assignment is considered live if used in
|
||
|
rhs (test for #6715).
|
||
|
"""
|
||
|
def func():
|
||
|
for i in range(3):
|
||
|
a = (lambda j: j)(i)
|
||
|
a = np.array(a)
|
||
|
return a
|
||
|
|
||
|
self.assertEqual(func(), numba.njit(func)())
|
||
|
|
||
|
@skip_parfors_unsupported
|
||
|
def test_alias_parfor_extension(self):
|
||
|
"""Make sure aliases are considered in remove dead extension for
|
||
|
parfors.
|
||
|
"""
|
||
|
def func():
|
||
|
n = 11
|
||
|
numba.parfors.parfor.init_prange()
|
||
|
A = np.empty(n)
|
||
|
B = A # create alias to A
|
||
|
for i in numba.prange(n):
|
||
|
A[i] = i
|
||
|
|
||
|
return B
|
||
|
|
||
|
@register_pass(analysis_only=False, mutates_CFG=True)
|
||
|
class LimitedParfor(FunctionPass):
|
||
|
_name = "limited_parfor"
|
||
|
|
||
|
def __init__(self):
|
||
|
FunctionPass.__init__(self)
|
||
|
|
||
|
def run_pass(self, state):
|
||
|
parfor_pass = numba.parfors.parfor.ParforPass(
|
||
|
state.func_ir,
|
||
|
state.typemap,
|
||
|
state.calltypes,
|
||
|
state.return_type,
|
||
|
state.typingctx,
|
||
|
state.flags.auto_parallel,
|
||
|
state.flags,
|
||
|
state.metadata,
|
||
|
state.parfor_diagnostics
|
||
|
)
|
||
|
remove_dels(state.func_ir.blocks)
|
||
|
parfor_pass.array_analysis.run(state.func_ir.blocks)
|
||
|
parfor_pass._convert_loop(state.func_ir.blocks)
|
||
|
remove_dead(state.func_ir.blocks,
|
||
|
state.func_ir.arg_names,
|
||
|
state.func_ir,
|
||
|
state.typemap)
|
||
|
numba.parfors.parfor.get_parfor_params(state.func_ir.blocks,
|
||
|
parfor_pass.options.fusion,
|
||
|
parfor_pass.nested_fusion_info)
|
||
|
return True
|
||
|
|
||
|
class TestPipeline(compiler.Compiler):
|
||
|
"""Test pipeline that just converts prange() to parfor and calls
|
||
|
remove_dead(). Copy propagation can replace B in the example code
|
||
|
which this pipeline avoids.
|
||
|
"""
|
||
|
def define_pipelines(self):
|
||
|
name = 'test parfor aliasing'
|
||
|
pm = PassManager(name)
|
||
|
pm.add_pass(TranslateByteCode, "analyzing bytecode")
|
||
|
pm.add_pass(FixupArgs, "fix up args")
|
||
|
pm.add_pass(IRProcessing, "processing IR")
|
||
|
pm.add_pass(WithLifting, "Handle with contexts")
|
||
|
# pre typing
|
||
|
if not self.state.flags.no_rewrites:
|
||
|
pm.add_pass(GenericRewrites, "nopython rewrites")
|
||
|
pm.add_pass(RewriteSemanticConstants, "rewrite semantic constants")
|
||
|
pm.add_pass(DeadBranchPrune, "dead branch pruning")
|
||
|
pm.add_pass(InlineClosureLikes,
|
||
|
"inline calls to locally defined closures")
|
||
|
# typing
|
||
|
pm.add_pass(NopythonTypeInference, "nopython frontend")
|
||
|
|
||
|
# lower
|
||
|
pm.add_pass(NativeLowering, "native lowering")
|
||
|
pm.add_pass(NoPythonBackend, "nopython mode backend")
|
||
|
pm.finalize()
|
||
|
return [pm]
|
||
|
|
||
|
test_res = numba.jit(pipeline_class=TestPipeline)(func)()
|
||
|
py_res = func()
|
||
|
np.testing.assert_array_equal(test_res, py_res)
|
||
|
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
unittest.main()
|