From 24bc03f1dc16641fa5719b3bec547d0deeb8a231 Mon Sep 17 00:00:00 2001 From: Siyi-Hu Date: Wed, 22 Oct 2025 20:22:29 +0000 Subject: [PATCH 01/13] Switched to new branch in finch-tensor-lite repository. Branches in previous forked repository are all obsolete. --- src/finchlite/codegen/c.py | 52 +++++++++++++++++++ src/finchlite/codegen/numba_backend.py | 12 +++++ tests/test_codegen.py | 72 ++++++++++++++++++++++++++ 3 files changed, 136 insertions(+) diff --git a/src/finchlite/codegen/c.py b/src/finchlite/codegen/c.py index 45294f98c..9614993e3 100644 --- a/src/finchlite/codegen/c.py +++ b/src/finchlite/codegen/c.py @@ -559,6 +559,40 @@ def c_type(t): } +ctype_print_fmt: dict[Any, str] = { + ctypes.c_bool: "%u", + ctypes.c_char: "%d", + ctypes.c_wchar: "%d", + ctypes.c_byte: "%d", + ctypes.c_ubyte: "%d", + ctypes.c_int8: "%d", + ctypes.c_int16: "%d", + ctypes.c_int32: "%ld", + ctypes.c_int64: "%lld", + ctypes.c_uint8: "%u", + ctypes.c_uint16: "%u", + ctypes.c_uint32: "%lu", + ctypes.c_uint64: "%llu", + # use standard types instead of aliases + # ctypes.c_short: "", + # ctypes.c_ushort: "", + # ctypes.c_int: "", + # ctypes.c_uint: "", + # ctypes.c_long: "", + # ctypes.c_ulong: "", + # ctypes.c_longlong: "", + # ctypes.c_ulonglong: "", + # ctypes.c_size_t: "", + # ctypes.c_ssize_t: "", + ctypes.c_float: "%f", + ctypes.c_double: "%f", + # ctypes.c_char_p: "", + # ctypes.c_wchar_p: "", + # ctypes.c_void_p: "", + # ctypes.py_object: "", +} + + class CGenerator: def __call__(self, prgm: asm.AssemblyNode): ctx = CContext() @@ -906,6 +940,24 @@ def __call__(self, prgm: asm.AssemblyNode): ) self(func) return None + case asm.Print(args): + self.add_header("#include ") + for arg in args: + match arg: + case asm.Variable(name, t): + var_ctype = c_type(t) + if var_ctype in ctype_print_fmt: + fmt_str = f'"{ctype_print_fmt[var_ctype]} "' + arg_str = f"{name}" + else: + t_name = self.ctype_name(var_ctype) + fmt_str = '"%s "' + arg_str = f'"{t_name}"' + self.exec(f"{feed}printf({fmt_str}, {arg_str});") + case _: + self.exec(f'{feed}printf("%s ", "UnknownType");') + self.exec(f'{feed}printf("\\n");') + return None case _: raise NotImplementedError( f"Unrecognized assembly node type: {type(prgm)}" diff --git a/src/finchlite/codegen/numba_backend.py b/src/finchlite/codegen/numba_backend.py index e70b96942..d7fa8843d 100644 --- a/src/finchlite/codegen/numba_backend.py +++ b/src/finchlite/codegen/numba_backend.py @@ -629,6 +629,18 @@ def __call__(self, prgm: asm.AssemblyNode): ) self(func) return None + case asm.Print(args): + args_str = "" + for arg in args: + match arg: + case asm.Variable(name, t): + args_str = ( + args_str + f"{name}:{self.full_name(numba_type(t))} " + ) + case _: + args_str = args_str + "UnknownType " + self.exec(f'{feed}print("{args_str}")') + return None case node: raise NotImplementedError(f"Unrecognized node: {node}") diff --git a/tests/test_codegen.py b/tests/test_codegen.py index 202c406e4..835f0ef5e 100644 --- a/tests/test_codegen.py +++ b/tests/test_codegen.py @@ -925,3 +925,75 @@ def test_e2e_numba(): assert_equal(result, a @ b) finchlite.set_default_scheduler(ctx=ctx) + + +@pytest.mark.parametrize( + "compiler", + [ + CCompiler(), + NumbaCompiler(), + ], +) +def test_print(compiler, capsys, file_regression): + # TODO: Test all types of variables + Point = namedtuple("Point", ["x", "y"]) + p = Point(np.float64(1.0), np.float64(2.0)) + x = (np.int64(1), np.int64(4)) + + p_var = asm.Variable("p", ftype(p)) + x_var = asm.Variable("x", ftype(x)) + res_var = asm.Variable("res", np.float64) + prgm = compiler( + asm.Module( + ( + asm.Function( + asm.Variable("simple_struct", np.float64), + (p_var, x_var), + asm.Block( + ( + asm.Print((p_var,)), + asm.Print((x_var,)), + asm.Print((p_var, x_var)), + asm.Assign( + res_var, + asm.Call( + asm.Literal(operator.mul), + ( + asm.GetAttr(p_var, asm.Literal("x")), + asm.GetAttr(x_var, asm.Literal("element_0")), + ), + ), + ), + asm.Assign( + res_var, + asm.Call( + asm.Literal(operator.add), + ( + res_var, + asm.Call( + asm.Literal(operator.mul), + ( + asm.GetAttr(p_var, asm.Literal("y")), + asm.GetAttr( + x_var, asm.Literal("element_1") + ), + ), + ), + ), + ), + ), + asm.Print((res_var,)), + asm.Print((p_var, x_var, res_var)), + asm.Return(res_var), + ) + ), + ), + ), + ) + ) + + result = prgm.simple_struct(p, x) + assert result == np.float64(9.0) + + capture = capsys.readouterr().out + file_regression.check(capture, extension=".txt") From 2c4034fdf901317bc47462ad46aed41b91078d54 Mon Sep 17 00:00:00 2001 From: Siyi-Hu Date: Wed, 22 Oct 2025 21:47:47 +0000 Subject: [PATCH 02/13] Added print statement to C++ and Numba backends. --- src/finchlite/codegen/c.py | 5 +++-- src/finchlite/codegen/numba_backend.py | 5 +++-- tests/reference/test_print_compiler0_.txt | 0 tests/reference/test_print_compiler1_.txt | 5 +++++ tests/test_codegen.py | 1 + 5 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 tests/reference/test_print_compiler0_.txt create mode 100644 tests/reference/test_print_compiler1_.txt diff --git a/src/finchlite/codegen/c.py b/src/finchlite/codegen/c.py index 9614993e3..90fea5508 100644 --- a/src/finchlite/codegen/c.py +++ b/src/finchlite/codegen/c.py @@ -941,17 +941,18 @@ def __call__(self, prgm: asm.AssemblyNode): self(func) return None case asm.Print(args): + # TODO: formatting? self.add_header("#include ") for arg in args: match arg: case asm.Variable(name, t): var_ctype = c_type(t) if var_ctype in ctype_print_fmt: - fmt_str = f'"{ctype_print_fmt[var_ctype]} "' + fmt_str = f'"{ctype_print_fmt[var_ctype]} "' arg_str = f"{name}" else: t_name = self.ctype_name(var_ctype) - fmt_str = '"%s "' + fmt_str = '"%s "' arg_str = f'"{t_name}"' self.exec(f"{feed}printf({fmt_str}, {arg_str});") case _: diff --git a/src/finchlite/codegen/numba_backend.py b/src/finchlite/codegen/numba_backend.py index d7fa8843d..1abeff947 100644 --- a/src/finchlite/codegen/numba_backend.py +++ b/src/finchlite/codegen/numba_backend.py @@ -630,15 +630,16 @@ def __call__(self, prgm: asm.AssemblyNode): self(func) return None case asm.Print(args): + # TODO: print out the value of variables in simple types args_str = "" for arg in args: match arg: case asm.Variable(name, t): args_str = ( - args_str + f"{name}:{self.full_name(numba_type(t))} " + args_str + f"{name}:{self.full_name(numba_type(t))} " ) case _: - args_str = args_str + "UnknownType " + args_str = args_str + "UnknownType " self.exec(f'{feed}print("{args_str}")') return None case node: diff --git a/tests/reference/test_print_compiler0_.txt b/tests/reference/test_print_compiler0_.txt new file mode 100644 index 000000000..e69de29bb diff --git a/tests/reference/test_print_compiler1_.txt b/tests/reference/test_print_compiler1_.txt new file mode 100644 index 000000000..85f144e5c --- /dev/null +++ b/tests/reference/test_print_compiler1_.txt @@ -0,0 +1,5 @@ +p:Numba_Point +x:Numba_tuple +p:Numba_Point x:Numba_tuple +res:float64 +p:Numba_Point x:Numba_tuple res:float64 diff --git a/tests/test_codegen.py b/tests/test_codegen.py index 835f0ef5e..688908bfa 100644 --- a/tests/test_codegen.py +++ b/tests/test_codegen.py @@ -936,6 +936,7 @@ def test_e2e_numba(): ) def test_print(compiler, capsys, file_regression): # TODO: Test all types of variables + # TODO: Capture compiler0(C++)'s output since it runs in another process than pytest one? Point = namedtuple("Point", ["x", "y"]) p = Point(np.float64(1.0), np.float64(2.0)) x = (np.int64(1), np.int64(4)) From d09bc6e77481eb0499b2cd9a5db7dbf0fcaa1458 Mon Sep 17 00:00:00 2001 From: Siyi-Hu Date: Wed, 5 Nov 2025 23:59:10 +0000 Subject: [PATCH 03/13] Adds print statement to C++ and Numba backends --- src/finchlite/codegen/numba_backend.py | 27 ++++++++++++++----- tests/reference/test_print_compiler1_.txt | 33 +++++++++++++++++++---- tests/test_codegen.py | 30 +++++++++++++++++++-- 3 files changed, 76 insertions(+), 14 deletions(-) diff --git a/src/finchlite/codegen/numba_backend.py b/src/finchlite/codegen/numba_backend.py index 1abeff947..22ed80e26 100644 --- a/src/finchlite/codegen/numba_backend.py +++ b/src/finchlite/codegen/numba_backend.py @@ -21,6 +21,18 @@ numba_structnames = Namespace() numba_globals: dict[str, Any] = {} +numba_simple_types = [ + np.int8, + np.int16, + np.int32, + np.int64, + np.float16, + np.float32, + np.float64, + np.float128, + np.bool_, + np.str_, +] def numba_type(t): """ @@ -338,6 +350,7 @@ def __init__(self, ctx=None): def __call__(self, prgm: asm.Module): numba_code = self.ctx(prgm) + print(numba_code) logger.info(f"Executing Numba code:\n{numba_code}") _globals = globals() _globals |= numba_globals @@ -630,17 +643,17 @@ def __call__(self, prgm: asm.AssemblyNode): self(func) return None case asm.Print(args): - # TODO: print out the value of variables in simple types - args_str = "" for arg in args: match arg: case asm.Variable(name, t): - args_str = ( - args_str + f"{name}:{self.full_name(numba_type(t))} " - ) + if t in numba_simple_types: + self.exec(f'{feed}print({name})') + else: + self.exec( + f'{feed}print("{self.full_name(numba_type(t))}")' + ) case _: - args_str = args_str + "UnknownType " - self.exec(f'{feed}print("{args_str}")') + self.exec(f'{feed}print("UnknownType")') return None case node: raise NotImplementedError(f"Unrecognized node: {node}") diff --git a/tests/reference/test_print_compiler1_.txt b/tests/reference/test_print_compiler1_.txt index 85f144e5c..4d216db1d 100644 --- a/tests/reference/test_print_compiler1_.txt +++ b/tests/reference/test_print_compiler1_.txt @@ -1,5 +1,28 @@ -p:Numba_Point -x:Numba_tuple -p:Numba_Point x:Numba_tuple -res:float64 -p:Numba_Point x:Numba_tuple res:float64 +import _operator, builtins +from numba import njit +import numpy +from numpy import int64, float64 + + +@njit +def simple_struct(p: Numba_Point, x: Numba_tuple) -> float64: + print("Numba_Point") + print("Numba_tuple") + print("Numba_Point") + print("Numba_tuple") + res: float64 = _operator.mul(p.x, x.element_0) + res = _operator.add(res, _operator.mul(p.y, x.element_1)) + print(res) + print("Numba_Point") + print("Numba_tuple") + print(res) + return res + +Numba_Point +Numba_tuple +Numba_Point +Numba_tuple +9.0 +Numba_Point +Numba_tuple +9.0 diff --git a/tests/test_codegen.py b/tests/test_codegen.py index 688908bfa..cebc8563e 100644 --- a/tests/test_codegen.py +++ b/tests/test_codegen.py @@ -935,15 +935,32 @@ def test_e2e_numba(): ], ) def test_print(compiler, capsys, file_regression): - # TODO: Test all types of variables - # TODO: Capture compiler0(C++)'s output since it runs in another process than pytest one? Point = namedtuple("Point", ["x", "y"]) p = Point(np.float64(1.0), np.float64(2.0)) x = (np.int64(1), np.int64(4)) + # int16_var = np.int16(32767) + # float16_var = np.float16(1.0) + # int32_var = np.int32(65536) + # float32_var = np.float32(1.0) + # int64_var = np.int64(65536) + # float64_var = np.float64(1.0) + # bool_var = np.bool_(True) + # str_var = np.str_("Test String") p_var = asm.Variable("p", ftype(p)) x_var = asm.Variable("x", ftype(x)) + + # i16_var = asm.Variable("int16_var", np.int16) + # f16_var = asm.Variable("float16_var", np.float16) + # i32_var = asm.Variable("int32_var", np.int32) + # f32_var = asm.Variable("float32_var", np.float32) + # i64_var = asm.Variable("int64_var", np.int64) + # f64_var = asm.Variable("float64_var", np.float64) + # b_var = asm.Variable("bool_var", np.bool_) + # s_var = asm.Variable("str_var", np.str_) + res_var = asm.Variable("res", np.float64) + prgm = compiler( asm.Module( ( @@ -952,6 +969,15 @@ def test_print(compiler, capsys, file_regression): (p_var, x_var), asm.Block( ( + # asm.Print((i16_var,)), + # asm.Print((f16_var,)), + # asm.Print((i32_var,)), + # asm.Print((f32_var,)), + # asm.Print((i64_var,)), + # asm.Print((f64_var,)), + # asm.Print((b_var,)), + # asm.Print((s_var,)), + asm.Print((p_var,)), asm.Print((x_var,)), asm.Print((p_var, x_var)), From bdf01b4e060cae601921faedbdbd71424dee0dd2 Mon Sep 17 00:00:00 2001 From: siyi-hu Date: Thu, 6 Nov 2025 07:59:53 +0000 Subject: [PATCH 04/13] Adds Print statement for C++ and Numba backends --- src/finchlite/codegen/c.py | 11 ++--- src/finchlite/codegen/numba_backend.py | 2 - tests/reference/test_print_compiler1_.txt | 24 ++--------- tests/test_codegen.py | 52 +++++++++++------------ 4 files changed, 34 insertions(+), 55 deletions(-) diff --git a/src/finchlite/codegen/c.py b/src/finchlite/codegen/c.py index 90fea5508..54c941c9b 100644 --- a/src/finchlite/codegen/c.py +++ b/src/finchlite/codegen/c.py @@ -567,12 +567,14 @@ def c_type(t): ctypes.c_ubyte: "%d", ctypes.c_int8: "%d", ctypes.c_int16: "%d", - ctypes.c_int32: "%ld", - ctypes.c_int64: "%lld", + ctypes.c_int32: "%d", + ctypes.c_int64: "%ld", ctypes.c_uint8: "%u", ctypes.c_uint16: "%u", - ctypes.c_uint32: "%lu", - ctypes.c_uint64: "%llu", + ctypes.c_uint32: "%u", + ctypes.c_uint64: "%lu", + ctypes.c_char_p: "%s", + ctypes.c_wchar_p: "%s", # use standard types instead of aliases # ctypes.c_short: "", # ctypes.c_ushort: "", @@ -941,7 +943,6 @@ def __call__(self, prgm: asm.AssemblyNode): self(func) return None case asm.Print(args): - # TODO: formatting? self.add_header("#include ") for arg in args: match arg: diff --git a/src/finchlite/codegen/numba_backend.py b/src/finchlite/codegen/numba_backend.py index 22ed80e26..fe26e441f 100644 --- a/src/finchlite/codegen/numba_backend.py +++ b/src/finchlite/codegen/numba_backend.py @@ -29,7 +29,6 @@ np.float16, np.float32, np.float64, - np.float128, np.bool_, np.str_, ] @@ -350,7 +349,6 @@ def __init__(self, ctx=None): def __call__(self, prgm: asm.Module): numba_code = self.ctx(prgm) - print(numba_code) logger.info(f"Executing Numba code:\n{numba_code}") _globals = globals() _globals |= numba_globals diff --git a/tests/reference/test_print_compiler1_.txt b/tests/reference/test_print_compiler1_.txt index 4d216db1d..b423f6a97 100644 --- a/tests/reference/test_print_compiler1_.txt +++ b/tests/reference/test_print_compiler1_.txt @@ -1,23 +1,7 @@ -import _operator, builtins -from numba import njit -import numpy -from numpy import int64, float64 - - -@njit -def simple_struct(p: Numba_Point, x: Numba_tuple) -> float64: - print("Numba_Point") - print("Numba_tuple") - print("Numba_Point") - print("Numba_tuple") - res: float64 = _operator.mul(p.x, x.element_0) - res = _operator.add(res, _operator.mul(p.y, x.element_1)) - print(res) - print("Numba_Point") - print("Numba_tuple") - print(res) - return res - +32767 +65536 +65536 +3.0 Numba_Point Numba_tuple Numba_Point diff --git a/tests/test_codegen.py b/tests/test_codegen.py index cebc8563e..3535ae5fe 100644 --- a/tests/test_codegen.py +++ b/tests/test_codegen.py @@ -934,33 +934,24 @@ def test_e2e_numba(): NumbaCompiler(), ], ) -def test_print(compiler, capsys, file_regression): +def test_print(compiler, capfd, file_regression): Point = namedtuple("Point", ["x", "y"]) p = Point(np.float64(1.0), np.float64(2.0)) x = (np.int64(1), np.int64(4)) - # int16_var = np.int16(32767) - # float16_var = np.float16(1.0) - # int32_var = np.int32(65536) - # float32_var = np.float32(1.0) - # int64_var = np.int64(65536) - # float64_var = np.float64(1.0) - # bool_var = np.bool_(True) - # str_var = np.str_("Test String") p_var = asm.Variable("p", ftype(p)) x_var = asm.Variable("x", ftype(x)) - - # i16_var = asm.Variable("int16_var", np.int16) - # f16_var = asm.Variable("float16_var", np.float16) - # i32_var = asm.Variable("int32_var", np.int32) - # f32_var = asm.Variable("float32_var", np.float32) - # i64_var = asm.Variable("int64_var", np.int64) - # f64_var = asm.Variable("float64_var", np.float64) - # b_var = asm.Variable("bool_var", np.bool_) - # s_var = asm.Variable("str_var", np.str_) - res_var = asm.Variable("res", np.float64) + i16_var = asm.Variable("i16_var", np.int16) + i32_var = asm.Variable("i32_var", np.int32) + i64_var = asm.Variable("i64_var", np.int64) + # f16_var = asm.Variable("f16_var", np.float16) + # f32_var = asm.Variable("f32_var", np.float32) + f64_var = asm.Variable("f64_var", np.float64) + # bool_var = asm.Variable("bool_var", np.bool_) + str_var = asm.Variable("str_var", np.str_) + prgm = compiler( asm.Module( ( @@ -969,14 +960,19 @@ def test_print(compiler, capsys, file_regression): (p_var, x_var), asm.Block( ( - # asm.Print((i16_var,)), - # asm.Print((f16_var,)), - # asm.Print((i32_var,)), - # asm.Print((f32_var,)), - # asm.Print((i64_var,)), - # asm.Print((f64_var,)), - # asm.Print((b_var,)), - # asm.Print((s_var,)), + asm.Assign(i16_var, asm.Literal(np.int16(32767))), + asm.Assign(i32_var, asm.Literal(np.int32(65536))), + asm.Assign(i64_var, asm.Literal(np.int64(65536))), + # asm.Assign(f16_var, asm.Literal(np.float16(1.0))), + # asm.Assign(f32_var, asm.Literal(np.float32(2.0))), + asm.Assign(f64_var, asm.Literal(np.float64(3.0))), + # asm.Assign(bool_var, asm.Literal(np.bool_(1))), + # asm.Assign(str_var, asm.Literal(np.str_("Test String"))), + + asm.Print((i16_var, i32_var, i64_var)), + asm.Print((f64_var,)), + # asm.Print((bool_var,)), + # asm.Print((str_var,)), asm.Print((p_var,)), asm.Print((x_var,)), @@ -1022,5 +1018,5 @@ def test_print(compiler, capsys, file_regression): result = prgm.simple_struct(p, x) assert result == np.float64(9.0) - capture = capsys.readouterr().out + capture = capfd.readouterr().out file_regression.check(capture, extension=".txt") From a53da65275d2c73de25b6624dd531f9427537127 Mon Sep 17 00:00:00 2001 From: siyi-hu Date: Thu, 6 Nov 2025 08:07:27 +0000 Subject: [PATCH 05/13] Fixed pre-commit error --- tests/test_codegen.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/test_codegen.py b/tests/test_codegen.py index 3535ae5fe..98cf14e19 100644 --- a/tests/test_codegen.py +++ b/tests/test_codegen.py @@ -950,7 +950,7 @@ def test_print(compiler, capfd, file_regression): # f32_var = asm.Variable("f32_var", np.float32) f64_var = asm.Variable("f64_var", np.float64) # bool_var = asm.Variable("bool_var", np.bool_) - str_var = asm.Variable("str_var", np.str_) + # str_var = asm.Variable("str_var", np.str_) prgm = compiler( asm.Module( @@ -968,12 +968,10 @@ def test_print(compiler, capfd, file_regression): asm.Assign(f64_var, asm.Literal(np.float64(3.0))), # asm.Assign(bool_var, asm.Literal(np.bool_(1))), # asm.Assign(str_var, asm.Literal(np.str_("Test String"))), - asm.Print((i16_var, i32_var, i64_var)), asm.Print((f64_var,)), # asm.Print((bool_var,)), # asm.Print((str_var,)), - asm.Print((p_var,)), asm.Print((x_var,)), asm.Print((p_var, x_var)), From 7a070906024b707d3f7a178764cfdd3469cd360b Mon Sep 17 00:00:00 2001 From: Siyi-Hu Date: Tue, 9 Dec 2025 15:59:47 +0000 Subject: [PATCH 06/13] Adds print statement to codegen backends --- src/finchlite/codegen/c.py | 26 +++++++++++------------ src/finchlite/codegen/numba_backend.py | 22 +------------------ tests/reference/test_print_compiler1_.txt | 13 ++++++------ tests/test_codegen.py | 22 +++++++++---------- 4 files changed, 30 insertions(+), 53 deletions(-) diff --git a/src/finchlite/codegen/c.py b/src/finchlite/codegen/c.py index 54c941c9b..5a73fe8c8 100644 --- a/src/finchlite/codegen/c.py +++ b/src/finchlite/codegen/c.py @@ -944,21 +944,19 @@ def __call__(self, prgm: asm.AssemblyNode): return None case asm.Print(args): self.add_header("#include ") + args_value_str = "" + fmt_str = "" for arg in args: - match arg: - case asm.Variable(name, t): - var_ctype = c_type(t) - if var_ctype in ctype_print_fmt: - fmt_str = f'"{ctype_print_fmt[var_ctype]} "' - arg_str = f"{name}" - else: - t_name = self.ctype_name(var_ctype) - fmt_str = '"%s "' - arg_str = f'"{t_name}"' - self.exec(f"{feed}printf({fmt_str}, {arg_str});") - case _: - self.exec(f'{feed}printf("%s ", "UnknownType");') - self.exec(f'{feed}printf("\\n");') + fmt_str = ( + fmt_str + f"{ctype_print_fmt[c_type(arg.type)]}," + if c_type(arg.type) in ctype_print_fmt + else fmt_str + "%p," + ) + args_value_str = args_value_str + f"{self(arg)}," + + fmt_str = fmt_str[:-1] + args_value_str = args_value_str[:-1] + self.exec(f'{feed}printf("{fmt_str}\\n", {args_value_str});') return None case _: raise NotImplementedError( diff --git a/src/finchlite/codegen/numba_backend.py b/src/finchlite/codegen/numba_backend.py index fe26e441f..9755c1183 100644 --- a/src/finchlite/codegen/numba_backend.py +++ b/src/finchlite/codegen/numba_backend.py @@ -21,17 +21,6 @@ numba_structnames = Namespace() numba_globals: dict[str, Any] = {} -numba_simple_types = [ - np.int8, - np.int16, - np.int32, - np.int64, - np.float16, - np.float32, - np.float64, - np.bool_, - np.str_, -] def numba_type(t): """ @@ -642,16 +631,7 @@ def __call__(self, prgm: asm.AssemblyNode): return None case asm.Print(args): for arg in args: - match arg: - case asm.Variable(name, t): - if t in numba_simple_types: - self.exec(f'{feed}print({name})') - else: - self.exec( - f'{feed}print("{self.full_name(numba_type(t))}")' - ) - case _: - self.exec(f'{feed}print("UnknownType")') + self.exec(f"{feed}print({self(arg)})") return None case node: raise NotImplementedError(f"Unrecognized node: {node}") diff --git a/tests/reference/test_print_compiler1_.txt b/tests/reference/test_print_compiler1_.txt index b423f6a97..bbb85298f 100644 --- a/tests/reference/test_print_compiler1_.txt +++ b/tests/reference/test_print_compiler1_.txt @@ -1,12 +1,13 @@ 32767 65536 65536 +2.0 3.0 -Numba_Point -Numba_tuple -Numba_Point -Numba_tuple + + + + 9.0 -Numba_Point -Numba_tuple + + 9.0 diff --git a/tests/test_codegen.py b/tests/test_codegen.py index 98cf14e19..e6d3c8515 100644 --- a/tests/test_codegen.py +++ b/tests/test_codegen.py @@ -946,11 +946,8 @@ def test_print(compiler, capfd, file_regression): i16_var = asm.Variable("i16_var", np.int16) i32_var = asm.Variable("i32_var", np.int32) i64_var = asm.Variable("i64_var", np.int64) - # f16_var = asm.Variable("f16_var", np.float16) - # f32_var = asm.Variable("f32_var", np.float32) + f32_var = asm.Variable("f32_var", np.float32) f64_var = asm.Variable("f64_var", np.float64) - # bool_var = asm.Variable("bool_var", np.bool_) - # str_var = asm.Variable("str_var", np.str_) prgm = compiler( asm.Module( @@ -963,15 +960,15 @@ def test_print(compiler, capfd, file_regression): asm.Assign(i16_var, asm.Literal(np.int16(32767))), asm.Assign(i32_var, asm.Literal(np.int32(65536))), asm.Assign(i64_var, asm.Literal(np.int64(65536))), - # asm.Assign(f16_var, asm.Literal(np.float16(1.0))), - # asm.Assign(f32_var, asm.Literal(np.float32(2.0))), + asm.Assign(f32_var, asm.Literal(np.float32(2.0))), asm.Assign(f64_var, asm.Literal(np.float64(3.0))), - # asm.Assign(bool_var, asm.Literal(np.bool_(1))), - # asm.Assign(str_var, asm.Literal(np.str_("Test String"))), asm.Print((i16_var, i32_var, i64_var)), - asm.Print((f64_var,)), - # asm.Print((bool_var,)), - # asm.Print((str_var,)), + asm.Print( + ( + f32_var, + f64_var, + ) + ), asm.Print((p_var,)), asm.Print((x_var,)), asm.Print((p_var, x_var)), @@ -1017,4 +1014,5 @@ def test_print(compiler, capfd, file_regression): assert result == np.float64(9.0) capture = capfd.readouterr().out - file_regression.check(capture, extension=".txt") + # file_regression.check(capture, extension=".txt") + print(capture) From 6fe42947779895c52a45e8fb854e803921ce09c7 Mon Sep 17 00:00:00 2001 From: Siyi-Hu Date: Mon, 5 Jan 2026 22:51:40 +0000 Subject: [PATCH 07/13] Added print statement to C and Numba backend, fixed bugs in pytest case --- src/finchlite/codegen/c.py | 15 ++++++++------- tests/reference/test_print_compiler0_.txt | 7 +++++++ tests/reference/test_print_compiler1_.txt | 12 ++++++------ tests/test_codegen.py | 8 ++++++-- 4 files changed, 27 insertions(+), 15 deletions(-) diff --git a/src/finchlite/codegen/c.py b/src/finchlite/codegen/c.py index 067c5b5af..bd3b8448f 100644 --- a/src/finchlite/codegen/c.py +++ b/src/finchlite/codegen/c.py @@ -953,13 +953,14 @@ def __call__(self, prgm: asm.AssemblyNode): args_value_str = "" fmt_str = "" for arg in args: - fmt_str = ( - fmt_str + f"{ctype_print_fmt[c_type(arg.type)]}," - if c_type(arg.type) in ctype_print_fmt - else fmt_str + "%p," - ) - args_value_str = args_value_str + f"{self(arg)}," - + if c_type(arg.type) in ctype_print_fmt: + fmt_str = fmt_str + f"{ctype_print_fmt[c_type(arg.type)]}," + args_value_str = args_value_str + f"{self(arg)}," + else: + fmt_str = fmt_str + "%s," + args_value_str = ( + args_value_str + f'"{self.ctype_name(c_type(arg.type))}",' + ) fmt_str = fmt_str[:-1] args_value_str = args_value_str[:-1] self.exec(f'{feed}printf("{fmt_str}\\n", {args_value_str});') diff --git a/tests/reference/test_print_compiler0_.txt b/tests/reference/test_print_compiler0_.txt index e69de29bb..21fc6e56f 100644 --- a/tests/reference/test_print_compiler0_.txt +++ b/tests/reference/test_print_compiler0_.txt @@ -0,0 +1,7 @@ +32767,65536,65536 +2.000000,3.000000 +struct C_Point +struct C_CTuple +struct C_Point,struct C_CTuple +9.000000 +struct C_Point,struct C_CTuple,9.000000 diff --git a/tests/reference/test_print_compiler1_.txt b/tests/reference/test_print_compiler1_.txt index bbb85298f..597ea94a5 100644 --- a/tests/reference/test_print_compiler1_.txt +++ b/tests/reference/test_print_compiler1_.txt @@ -3,11 +3,11 @@ 65536 2.0 3.0 - - - - + + + + 9.0 - - + + 9.0 diff --git a/tests/test_codegen.py b/tests/test_codegen.py index 1e86a560e..c36ea983a 100644 --- a/tests/test_codegen.py +++ b/tests/test_codegen.py @@ -1028,6 +1028,10 @@ def test_print(compiler, capfd, file_regression): result = prgm.simple_struct(p, x) assert result == np.float64(9.0) + ctypes.CDLL(None).fflush(None) capture = capfd.readouterr().out - # file_regression.check(capture, extension=".txt") - print(capture) + if isinstance(compiler, NumbaCompiler): + # Normalize runtime object addresses printed by Numba for file regression + capture = re.sub(r" object at 0x[0-9a-fA-F]+>", ">", capture) + + file_regression.check(capture, extension=".txt") From a81ae4d586a5d0ba4ed93110562b6921be0d2009 Mon Sep 17 00:00:00 2001 From: siyi-hu Date: Tue, 6 Jan 2026 01:32:36 +0000 Subject: [PATCH 08/13] Fixed TypeError in codegen pytest case caused by different ctypes CDLL behaviour --- tests/test_codegen.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/tests/test_codegen.py b/tests/test_codegen.py index f3d27abe7..7645aec19 100644 --- a/tests/test_codegen.py +++ b/tests/test_codegen.py @@ -1,5 +1,6 @@ import ctypes import operator +import os import re import subprocess import sys @@ -1040,7 +1041,22 @@ def test_print(compiler, capfd, file_regression): result = prgm.simple_struct(p, x) assert result == np.float64(9.0) - ctypes.CDLL(None).fflush(None) + # Flush CDLL buffer in order to capture stdout/stderr + if os.name == "nt": + # Handle ctypes behavior on Windows used by github CI + crt = ctypes.util.find_msvcrt() + libc = ctypes.CDLL(crt) if crt else ctypes.cdll.msvcrt + else: + libc = ctypes.CDLL(None) + + try: + fflush = libc.fflush + fflush.argtypes = [ctypes.c_void_p] + fflush.restype = ctypes.c_int + fflush(None) + except AttributeError: + pass + capture = capfd.readouterr().out if isinstance(compiler, NumbaCompiler): # Normalize runtime object addresses printed by Numba for file regression From 77dd3d05bbd1214a599a19551de2150624c8444f Mon Sep 17 00:00:00 2001 From: siyi-hu Date: Tue, 6 Jan 2026 01:43:00 +0000 Subject: [PATCH 09/13] Fixed TypeError in codegen pytest case caused by different ctypes CDLL behaviour --- tests/test_codegen.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_codegen.py b/tests/test_codegen.py index 7645aec19..09dd1f6f3 100644 --- a/tests/test_codegen.py +++ b/tests/test_codegen.py @@ -1,4 +1,5 @@ import ctypes +import ctypes.util import operator import os import re From 3e01396c85b069e38c50b7dadc2fa961b098d1c6 Mon Sep 17 00:00:00 2001 From: siyi-hu Date: Tue, 6 Jan 2026 02:36:33 +0000 Subject: [PATCH 10/13] Fixed TypeError in codegen pytest case caused by ctypes CDLL on Windows --- tests/test_codegen.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/tests/test_codegen.py b/tests/test_codegen.py index 09dd1f6f3..9efdeae29 100644 --- a/tests/test_codegen.py +++ b/tests/test_codegen.py @@ -1044,20 +1044,11 @@ def test_print(compiler, capfd, file_regression): # Flush CDLL buffer in order to capture stdout/stderr if os.name == "nt": - # Handle ctypes behavior on Windows used by github CI crt = ctypes.util.find_msvcrt() - libc = ctypes.CDLL(crt) if crt else ctypes.cdll.msvcrt + cdll = ctypes.CDLL(crt) if crt else ctypes.cdll.msvcrt else: - libc = ctypes.CDLL(None) - - try: - fflush = libc.fflush - fflush.argtypes = [ctypes.c_void_p] - fflush.restype = ctypes.c_int - fflush(None) - except AttributeError: - pass - + cdll = ctypes.CDLL(None) + cdll.fflush(None) capture = capfd.readouterr().out if isinstance(compiler, NumbaCompiler): # Normalize runtime object addresses printed by Numba for file regression From 0e399fe206a54f65a87096fdbbf904e2f84f99a9 Mon Sep 17 00:00:00 2001 From: siyi-hu Date: Tue, 27 Jan 2026 05:44:50 +0000 Subject: [PATCH 11/13] Updated file_regression reference due to type name changes --- tests/reference/test_print_compiler0_.txt | 6 +++--- tests/reference/test_print_compiler1_.txt | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/reference/test_print_compiler0_.txt b/tests/reference/test_print_compiler0_.txt index 21fc6e56f..c7222a38a 100644 --- a/tests/reference/test_print_compiler0_.txt +++ b/tests/reference/test_print_compiler0_.txt @@ -1,7 +1,7 @@ 32767,65536,65536 2.000000,3.000000 struct C_Point -struct C_CTuple -struct C_Point,struct C_CTuple +struct C_CTuple_3 +struct C_Point,struct C_CTuple_3 9.000000 -struct C_Point,struct C_CTuple,9.000000 +struct C_Point,struct C_CTuple_3,9.000000 diff --git a/tests/reference/test_print_compiler1_.txt b/tests/reference/test_print_compiler1_.txt index 597ea94a5..246cc5a7f 100644 --- a/tests/reference/test_print_compiler1_.txt +++ b/tests/reference/test_print_compiler1_.txt @@ -3,11 +3,11 @@ 65536 2.0 3.0 - - - - +(1.0, 2.0) +(1, 4) +(1.0, 2.0) +(1, 4) 9.0 - - +(1.0, 2.0) +(1, 4) 9.0 From 67b5361874b47f8dd5d921fe17ce4e210f59114b Mon Sep 17 00:00:00 2001 From: Willow Ahrens Date: Tue, 30 Jun 2026 17:00:23 -0400 Subject: [PATCH 12/13] fix --- src/finchlite/codegen/__init__.py | 4 + src/finchlite/codegen/c_codegen/__init__.py | 4 + src/finchlite/codegen/c_codegen/c.py | 126 +++++++++++++----- .../codegen/numba_codegen/__init__.py | 4 + src/finchlite/codegen/numba_codegen/numba.py | 25 +++- tests/reference/test_print_compiler0_.txt | 12 +- tests/reference/test_print_compiler1_.txt | 14 +- tests/test_codegen.py | 21 ++- 8 files changed, 148 insertions(+), 62 deletions(-) diff --git a/src/finchlite/codegen/__init__.py b/src/finchlite/codegen/__init__.py index 7a43d28ad..3eadff186 100644 --- a/src/finchlite/codegen/__init__.py +++ b/src/finchlite/codegen/__init__.py @@ -19,6 +19,7 @@ CKernel, CLibrary, CLowerer, + PrintableCFType, ) from .numba_codegen import ( NumbaBinaryOperator, @@ -31,6 +32,7 @@ NumbaNAryOperator, NumbaOperator, NumbaUnaryOperator, + PrintableNumbaFType, ) __all__ = [ @@ -64,6 +66,8 @@ "NumbaUnaryOperator", "NumpyBuffer", "NumpyBufferFType", + "PrintableCFType", + "PrintableNumbaFType", "SafeBuffer", "SafeBufferFType", ] diff --git a/src/finchlite/codegen/c_codegen/__init__.py b/src/finchlite/codegen/c_codegen/__init__.py index de5d320e7..3ab447a0b 100644 --- a/src/finchlite/codegen/c_codegen/__init__.py +++ b/src/finchlite/codegen/c_codegen/__init__.py @@ -13,11 +13,13 @@ COperator, CStackFType, CUnaryOperator, + PrintableCFType, c_eq, c_function_call, c_function_name, c_hash, c_literal, + c_print, c_type, construct_from_c, create_shared_lib, @@ -44,11 +46,13 @@ "COperator", "CStackFType", "CUnaryOperator", + "PrintableCFType", "c_eq", "c_function_call", "c_function_name", "c_hash", "c_literal", + "c_print", "c_type", "construct_from_c", "create_shared_lib", diff --git a/src/finchlite/codegen/c_codegen/c.py b/src/finchlite/codegen/c_codegen/c.py index 272b005b4..1d2ae4ae5 100644 --- a/src/finchlite/codegen/c_codegen/c.py +++ b/src/finchlite/codegen/c_codegen/c.py @@ -482,9 +482,9 @@ def struct_c_type(fmt: StructFType): ctype_print_fmt: dict[Any, str] = { - ctypes.c_bool: "%u", - ctypes.c_char: "%d", - ctypes.c_wchar: "%d", + ctypes.c_bool: "%d", + ctypes.c_char: "%c", + ctypes.c_wchar: "%lc", ctypes.c_byte: "%d", ctypes.c_ubyte: "%d", ctypes.c_int8: "%d", @@ -496,27 +496,86 @@ def struct_c_type(fmt: StructFType): ctypes.c_uint32: "%u", ctypes.c_uint64: "%lu", ctypes.c_char_p: "%s", - ctypes.c_wchar_p: "%s", - # use standard types instead of aliases - # ctypes.c_short: "", - # ctypes.c_ushort: "", - # ctypes.c_int: "", - # ctypes.c_uint: "", - # ctypes.c_long: "", - # ctypes.c_ulong: "", - # ctypes.c_longlong: "", - # ctypes.c_ulonglong: "", - # ctypes.c_size_t: "", - # ctypes.c_ssize_t: "", + ctypes.c_wchar_p: "%ls", ctypes.c_float: "%f", ctypes.c_double: "%f", - # ctypes.c_char_p: "", - # ctypes.c_wchar_p: "", - # ctypes.c_void_p: "", - # ctypes.py_object: "", } +class PrintableCFType(ABC): + @abstractmethod + def c_print(self, ctx, obj) -> tuple[str, tuple[str, ...]]: + """ + Return a printf format fragment and the C expressions it consumes. + """ + ... + + +def c_print(fmt: FType, ctx, obj) -> tuple[str, tuple[str, ...]]: + match fmt: + case PrintableCFType(): + return fmt.c_print(ctx, obj) + case ( + algebra.ftypes.FDTypeNumpy() + | algebra.int_ + | algebra.float_ + | algebra.bool_ + | algebra.str_ + ): + return c_print_scalar(fmt, ctx, obj) + case TupleFType(): + return c_print_tuple(fmt, ctx, obj) + case StructFType(): + return c_print_struct(fmt, ctx, obj) + case _: + raise NotImplementedError(f"No C print mapping for {fmt}") + + +def c_print_scalar(fmt: FType, ctx, obj) -> tuple[str, tuple[str, ...]]: + ctype = c_type(fmt) + if ctype not in ctype_print_fmt: + raise NotImplementedError(f"No C print mapping for {fmt}") + if isinstance(fmt, algebra.ftypes.FDTypeBoolean): + return ctype_print_fmt[ctype], (f"(int){obj}",) + return ctype_print_fmt[ctype], (obj,) + + +def c_print_tuple(fmt: TupleFType, ctx, obj) -> tuple[str, tuple[str, ...]]: + fields = fmt.struct_fields + if not fields: + return "()", () + + pieces: list[str] = ["("] + args: list[str] = [] + for field_idx, (field, field_t) in enumerate(fields): + if field_idx > 0: + pieces.append(", ") + field_fmt, field_args = c_print(field_t, ctx, c_getattr(fmt, ctx, obj, field)) + pieces.append(field_fmt) + args.extend(field_args) + if len(fields) == 1: + pieces.append(",") + pieces.append(")") + return "".join(pieces), tuple(args) + + +def c_print_struct(fmt: StructFType, ctx, obj) -> tuple[str, tuple[str, ...]]: + pieces: list[str] = [f"{fmt.struct_name}("] + args: list[str] = [] + for field_idx, (field, field_t) in enumerate(fmt.struct_fields): + if field_idx > 0: + pieces.append(", ") + field_fmt, field_args = c_print(field_t, ctx, c_getattr(fmt, ctx, obj, field)) + pieces.extend((f"{field}=", field_fmt)) + args.extend(field_args) + pieces.append(")") + return "".join(pieces), tuple(args) + + +def c_string_literal(value: str) -> str: + return value.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n") + + class CGenerator(UnvalidatedForm, CLowerer): def lower(self, prgm: asm.AssemblyNode) -> CCode: ctx = CContext() @@ -907,20 +966,21 @@ def __call__(self, prgm: asm.AssemblyNode): return None case asm.Print(args): self.add_header("#include ") - args_value_str = "" - fmt_str = "" - for arg in args: - if c_type(arg.type) in ctype_print_fmt: - fmt_str = fmt_str + f"{ctype_print_fmt[c_type(arg.type)]}," - args_value_str = args_value_str + f"{self(arg)}," - else: - fmt_str = fmt_str + "%s," - args_value_str = ( - args_value_str + f'"{self.ctype_name(c_type(arg.type))}",' - ) - fmt_str = fmt_str[:-1] - args_value_str = args_value_str[:-1] - self.exec(f'{feed}printf("{fmt_str}\\n", {args_value_str});') + pieces: list[str] = [] + print_args: list[str] = [] + for arg_idx, arg in enumerate(args): + if arg_idx > 0: + pieces.append(" ") + arg_fmt, arg_args = c_print(arg.result_type, self, self(arg)) + pieces.append(arg_fmt) + print_args.extend(arg_args) + fmt_str = c_string_literal("".join(pieces) + "\n") + if print_args: + self.exec( + f'{feed}printf("{fmt_str}", {", ".join(print_args)});' + ) + else: + self.exec(f'{feed}printf("{fmt_str}");') return None case _: raise NotImplementedError( diff --git a/src/finchlite/codegen/numba_codegen/__init__.py b/src/finchlite/codegen/numba_codegen/__init__.py index fe7d1d1d4..7ff0270ad 100644 --- a/src/finchlite/codegen/numba_codegen/__init__.py +++ b/src/finchlite/codegen/numba_codegen/__init__.py @@ -12,6 +12,7 @@ NumbaOperator, NumbaStackFType, NumbaUnaryOperator, + PrintableNumbaFType, construct_from_numba, deserialize_from_numba, numba_binary_function_call, @@ -21,6 +22,7 @@ numba_getattr, numba_jitclass_type, numba_nary_function_call, + numba_print, numba_setattr, numba_type, numba_unary_function_call, @@ -45,6 +47,7 @@ "NumbaOperator", "NumbaStackFType", "NumbaUnaryOperator", + "PrintableNumbaFType", "construct_from_numba", "deserialize_from_numba", "numba_binary_function_call", @@ -54,6 +57,7 @@ "numba_getattr", "numba_jitclass_type", "numba_nary_function_call", + "numba_print", "numba_setattr", "numba_type", "numba_unary_function_call", diff --git a/src/finchlite/codegen/numba_codegen/numba.py b/src/finchlite/codegen/numba_codegen/numba.py index ffe1acb07..513a9ec3e 100644 --- a/src/finchlite/codegen/numba_codegen/numba.py +++ b/src/finchlite/codegen/numba_codegen/numba.py @@ -207,6 +207,23 @@ def construct_from_numba(self, numba_buffer): ... +class PrintableNumbaFType(ABC): + @abstractmethod + def numba_print(self, ctx, obj) -> tuple[str, ...]: + """ + Return expressions to pass to Numba's print for this object. + """ + ... + + +def numba_print(fmt: FType, ctx, obj) -> tuple[str, ...]: + match fmt: + case PrintableNumbaFType(): + return fmt.numba_print(ctx, obj) + case _: + return (ctx(obj),) + + def to_numpy_type(t: FType) -> np.dtype: """Return a NumPy dtype for a Finch scalar/data type.""" if isinstance(t, algebra.ftypes.FDTypeNumpy): @@ -875,8 +892,12 @@ def __call__(self, prgm: asm.AssemblyNode): self(func) return None case asm.Print(args): - for arg in args: - self.exec(f"{feed}print({self(arg)})") + print_args = [ + expr + for arg in args + for expr in numba_print(arg.result_type, self, arg) + ] + self.exec(f"{feed}print({', '.join(print_args)})") return None case node: raise NotImplementedError(f"Unrecognized node: {node}") diff --git a/tests/reference/test_print_compiler0_.txt b/tests/reference/test_print_compiler0_.txt index c7222a38a..c00cc6142 100644 --- a/tests/reference/test_print_compiler0_.txt +++ b/tests/reference/test_print_compiler0_.txt @@ -1,7 +1,7 @@ -32767,65536,65536 -2.000000,3.000000 -struct C_Point -struct C_CTuple_3 -struct C_Point,struct C_CTuple_3 +32767 65536 65536 +2.000000 3.000000 +Point(x=1.000000, y=2.000000) +(1, 4) +Point(x=1.000000, y=2.000000) (1, 4) 9.000000 -struct C_Point,struct C_CTuple_3,9.000000 +Point(x=1.000000, y=2.000000) (1, 4) 9.000000 diff --git a/tests/reference/test_print_compiler1_.txt b/tests/reference/test_print_compiler1_.txt index 246cc5a7f..0b9e91710 100644 --- a/tests/reference/test_print_compiler1_.txt +++ b/tests/reference/test_print_compiler1_.txt @@ -1,13 +1,7 @@ -32767 -65536 -65536 -2.0 -3.0 -(1.0, 2.0) -(1, 4) -(1.0, 2.0) -(1, 4) -9.0 +32767 65536 65536 +2.0 3.0 (1.0, 2.0) (1, 4) +(1.0, 2.0) (1, 4) 9.0 +(1.0, 2.0) (1, 4) 9.0 diff --git a/tests/test_codegen.py b/tests/test_codegen.py index 45c4c2800..ecdc10927 100644 --- a/tests/test_codegen.py +++ b/tests/test_codegen.py @@ -1,6 +1,5 @@ import ctypes import ctypes.util -import operator import os import re import subprocess @@ -1187,19 +1186,19 @@ def test_print(compiler, capfd, file_regression): p_var = asm.Variable("p", ftype(p)) x_var = asm.Variable("x", ftype(x)) - res_var = asm.Variable("res", np.float64) + res_var = asm.Variable("res", ftype(np.float64)) - i16_var = asm.Variable("i16_var", np.int16) - i32_var = asm.Variable("i32_var", np.int32) - i64_var = asm.Variable("i64_var", np.int64) - f32_var = asm.Variable("f32_var", np.float32) - f64_var = asm.Variable("f64_var", np.float64) + i16_var = asm.Variable("i16_var", ftype(np.int16)) + i32_var = asm.Variable("i32_var", ftype(np.int32)) + i64_var = asm.Variable("i64_var", ftype(np.int64)) + f32_var = asm.Variable("f32_var", ftype(np.float32)) + f64_var = asm.Variable("f64_var", ftype(np.float64)) prgm = compiler( asm.Module( ( asm.Function( - asm.Variable("simple_struct", np.float64), + asm.Variable("simple_struct", ftype(np.float64)), (p_var, x_var), asm.Block( ( @@ -1221,7 +1220,7 @@ def test_print(compiler, capfd, file_regression): asm.Assign( res_var, asm.Call( - asm.Literal(operator.mul), + asm.Literal(ffuncs.mul), ( asm.GetAttr(p_var, asm.Literal("x")), asm.GetAttr(x_var, asm.Literal("element_0")), @@ -1231,11 +1230,11 @@ def test_print(compiler, capfd, file_regression): asm.Assign( res_var, asm.Call( - asm.Literal(operator.add), + asm.Literal(ffuncs.add), ( res_var, asm.Call( - asm.Literal(operator.mul), + asm.Literal(ffuncs.mul), ( asm.GetAttr(p_var, asm.Literal("y")), asm.GetAttr( From 965f79d1c3cb6090ca2738b37b9bf95c07e8021c Mon Sep 17 00:00:00 2001 From: Willow Ahrens Date: Tue, 30 Jun 2026 17:28:31 -0400 Subject: [PATCH 13/13] hmm --- src/finchlite/codegen/c_codegen/c.py | 125 +++++++++++-------- src/finchlite/codegen/numba_codegen/numba.py | 64 +++++++--- src/finchlite/finch_assembly/interpreter.py | 13 +- src/finchlite/finch_assembly/nodes.py | 23 ++-- tests/reference/test_asm_print.txt | 10 +- tests/reference/test_print_compiler0_.txt | 8 +- tests/reference/test_print_compiler1_.txt | 6 +- tests/test_assembly_interpreter.py | 12 +- tests/test_codegen.py | 22 +++- 9 files changed, 181 insertions(+), 102 deletions(-) diff --git a/src/finchlite/codegen/c_codegen/c.py b/src/finchlite/codegen/c_codegen/c.py index 1d2ae4ae5..bb1e2994f 100644 --- a/src/finchlite/codegen/c_codegen/c.py +++ b/src/finchlite/codegen/c_codegen/c.py @@ -1,3 +1,4 @@ +import builtins import ctypes import logging import shutil @@ -504,14 +505,14 @@ def struct_c_type(fmt: StructFType): class PrintableCFType(ABC): @abstractmethod - def c_print(self, ctx, obj) -> tuple[str, tuple[str, ...]]: + def c_print(self, ctx, obj) -> str: """ - Return a printf format fragment and the C expressions it consumes. + Return a C expression for this value in printf argument position. """ ... -def c_print(fmt: FType, ctx, obj) -> tuple[str, tuple[str, ...]]: +def c_print(fmt: FType, ctx, obj) -> str: match fmt: case PrintableCFType(): return fmt.c_print(ctx, obj) @@ -523,59 +524,74 @@ def c_print(fmt: FType, ctx, obj) -> tuple[str, tuple[str, ...]]: | algebra.str_ ): return c_print_scalar(fmt, ctx, obj) - case TupleFType(): - return c_print_tuple(fmt, ctx, obj) - case StructFType(): - return c_print_struct(fmt, ctx, obj) case _: raise NotImplementedError(f"No C print mapping for {fmt}") -def c_print_scalar(fmt: FType, ctx, obj) -> tuple[str, tuple[str, ...]]: +def c_print_scalar(fmt: FType, ctx, obj) -> str: ctype = c_type(fmt) if ctype not in ctype_print_fmt: raise NotImplementedError(f"No C print mapping for {fmt}") if isinstance(fmt, algebra.ftypes.FDTypeBoolean): - return ctype_print_fmt[ctype], (f"(int){obj}",) - return ctype_print_fmt[ctype], (obj,) - - -def c_print_tuple(fmt: TupleFType, ctx, obj) -> tuple[str, tuple[str, ...]]: - fields = fmt.struct_fields - if not fields: - return "()", () - - pieces: list[str] = ["("] - args: list[str] = [] - for field_idx, (field, field_t) in enumerate(fields): - if field_idx > 0: - pieces.append(", ") - field_fmt, field_args = c_print(field_t, ctx, c_getattr(fmt, ctx, obj, field)) - pieces.append(field_fmt) - args.extend(field_args) - if len(fields) == 1: - pieces.append(",") - pieces.append(")") - return "".join(pieces), tuple(args) - - -def c_print_struct(fmt: StructFType, ctx, obj) -> tuple[str, tuple[str, ...]]: - pieces: list[str] = [f"{fmt.struct_name}("] - args: list[str] = [] - for field_idx, (field, field_t) in enumerate(fmt.struct_fields): - if field_idx > 0: - pieces.append(", ") - field_fmt, field_args = c_print(field_t, ctx, c_getattr(fmt, ctx, obj, field)) - pieces.extend((f"{field}=", field_fmt)) - args.extend(field_args) - pieces.append(")") - return "".join(pieces), tuple(args) + return f"(int){obj}" + return obj + + +def c_print_string_fallback(fmt: FType, ctx) -> str: + try: + fallback = ctx.ctype_name(c_type(fmt)) + except NotImplementedError: + fallback = str(fmt) + return f'"{c_string_literal(fallback)}"' + + +def c_printf_arg(fmt: FType, ctx, obj, conversion: str) -> str: + if conversion == "s": + try: + return c_print(fmt, ctx, obj) + except NotImplementedError: + return c_print_string_fallback(fmt, ctx) + return c_print(fmt, ctx, obj) def c_string_literal(value: str) -> str: return value.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n") +def printf_conversions(fmt: str) -> list[str]: + conversions = [] + idx = 0 + while idx < len(fmt): + if fmt[idx] != "%": + idx += 1 + continue + idx += 1 + if idx < len(fmt) and fmt[idx] == "%": + idx += 1 + continue + while idx < len(fmt) and fmt[idx] in "-+ #0": + idx += 1 + if idx < len(fmt) and fmt[idx] == "*": + raise NotImplementedError("dynamic printf widths are not supported") + while idx < len(fmt) and fmt[idx].isdigit(): + idx += 1 + if idx < len(fmt) and fmt[idx] == ".": + idx += 1 + if idx < len(fmt) and fmt[idx] == "*": + raise NotImplementedError("dynamic printf precisions are not supported") + while idx < len(fmt) and fmt[idx].isdigit(): + idx += 1 + if fmt.startswith(("hh", "ll"), idx): + idx += 2 + elif idx < len(fmt) and fmt[idx] in "hljztL": + idx += 1 + if idx >= len(fmt): + raise ValueError("incomplete printf format specifier") + conversions.append(fmt[idx]) + idx += 1 + return conversions + + class CGenerator(UnvalidatedForm, CLowerer): def lower(self, prgm: asm.AssemblyNode) -> CCode: ctx = CContext() @@ -966,15 +982,22 @@ def __call__(self, prgm: asm.AssemblyNode): return None case asm.Print(args): self.add_header("#include ") - pieces: list[str] = [] - print_args: list[str] = [] - for arg_idx, arg in enumerate(args): - if arg_idx > 0: - pieces.append(" ") - arg_fmt, arg_args = c_print(arg.result_type, self, self(arg)) - pieces.append(arg_fmt) - print_args.extend(arg_args) - fmt_str = c_string_literal("".join(pieces) + "\n") + match args: + case (asm.Literal(str() as fmt), *vals): + pass + case _: + raise TypeError("Print expects a literal format string") + conversions = printf_conversions(fmt) + if builtins.len(conversions) != builtins.len(vals): + raise ValueError( + f"printf format expects {builtins.len(conversions)} arguments, " + f"got {builtins.len(vals)}" + ) + print_args = [ + c_printf_arg(val.result_type, self, self(val), conversion) + for val, conversion in zip(vals, conversions, strict=False) + ] + fmt_str = c_string_literal(fmt) if print_args: self.exec( f'{feed}printf("{fmt_str}", {", ".join(print_args)});' diff --git a/src/finchlite/codegen/numba_codegen/numba.py b/src/finchlite/codegen/numba_codegen/numba.py index 513a9ec3e..9e59b3442 100644 --- a/src/finchlite/codegen/numba_codegen/numba.py +++ b/src/finchlite/codegen/numba_codegen/numba.py @@ -209,19 +209,19 @@ def construct_from_numba(self, numba_buffer): class PrintableNumbaFType(ABC): @abstractmethod - def numba_print(self, ctx, obj) -> tuple[str, ...]: + def numba_print(self, ctx, obj) -> str: """ - Return expressions to pass to Numba's print for this object. + Return a Numba expression for this value in printf argument position. """ ... -def numba_print(fmt: FType, ctx, obj) -> tuple[str, ...]: +def numba_print(fmt: FType, ctx, obj) -> str: match fmt: case PrintableNumbaFType(): return fmt.numba_print(ctx, obj) case _: - return (ctx(obj),) + return ctx(obj) def to_numpy_type(t: FType) -> np.dtype: @@ -605,7 +605,15 @@ def lower(self, prgm: asm.AssemblyNode) -> NumbaCode: class NumbaContext(Context): - def __init__(self, tab=" ", indent=0, types=None, slots=None): + def __init__( + self, + tab=" ", + indent=0, + types=None, + slots=None, + imports=None, + importset=None, + ): if types is None: types = ScopedDict() if slots is None: @@ -618,13 +626,18 @@ def __init__(self, tab=" ", indent=0, types=None, slots=None): self.types = types self.slots = slots - self.imports = [ - "import _operator, builtins", - "from numba import njit", - "import numpy", - "from numpy import int64, float64", - "\n", - ] + if imports is None: + imports = [ + "import _operator, builtins", + "from numba import njit", + "import numpy", + "from numpy import int64, float64", + "\n", + ] + if importset is None: + importset = set(imports) + self.imports = imports + self._importset = importset @property def feed(self) -> str: @@ -639,10 +652,17 @@ def emit_global(self): def emit(self): return "\n".join([*self.preamble, *self.epilogue]) + def add_import(self, import_line: str): + if import_line not in self._importset: + self.imports.insert(-1, import_line) + self._importset.add(import_line) + def block(self) -> "NumbaContext": blk = super().block() blk.indent = self.indent blk.tab = self.tab + blk.imports = self.imports + blk._importset = self._importset blk.types = self.types blk.slots = self.slots return blk @@ -892,12 +912,24 @@ def __call__(self, prgm: asm.AssemblyNode): self(func) return None case asm.Print(args): + match args: + case (asm.Literal(str() as fmt), *vals): + pass + case _: + raise TypeError("Print expects a literal format string") print_args = [ - expr - for arg in args - for expr in numba_print(arg.result_type, self, arg) + numba_print(val.result_type, self, val) for val in vals ] - self.exec(f"{feed}print({', '.join(print_args)})") + tuple_expr = ", ".join(print_args) + if len(print_args) == 1: + tuple_expr = f"{tuple_expr}," + fmt_var = self.freshen("fmt") + self.add_import("from numba import objmode") + self.exec( + f"{feed}with objmode():\n" + f"{feed}{self.tab}{fmt_var} = {fmt!r}\n" + f"{feed}{self.tab}print({fmt_var} % ({tuple_expr}), end='')" + ) return None case node: raise NotImplementedError(f"Unrecognized node: {node}") diff --git a/src/finchlite/finch_assembly/interpreter.py b/src/finchlite/finch_assembly/interpreter.py index 50b28449b..8300643da 100644 --- a/src/finchlite/finch_assembly/interpreter.py +++ b/src/finchlite/finch_assembly/interpreter.py @@ -371,10 +371,15 @@ def my_func(*args_e): ) return AssemblyInterpreterLibrary(self, kernels) case asm.Print(args): - args_value_str = "" - for arg in args: - args_value_str = args_value_str + f"{self(arg)} " - print(args_value_str, file=self.stdout) + match args: + case (asm.Literal(str() as fmt), *vals): + print( + fmt % tuple(self(val) for val in vals), + end="", + file=self.stdout, + ) + case _: + raise TypeError("Print expects a literal format string") return None case asm.Stack(val): raise NotImplementedError( diff --git a/src/finchlite/finch_assembly/nodes.py b/src/finchlite/finch_assembly/nodes.py index 932505973..3f4aacd9d 100644 --- a/src/finchlite/finch_assembly/nodes.py +++ b/src/finchlite/finch_assembly/nodes.py @@ -675,13 +675,13 @@ def from_children(cls, *funcs): @dataclass(eq=True, frozen=True) class Print(AssemblyTree, AssemblyStatement): """ - Print values of give variables. + Print values using a printf-style format string. Attributes: - args: list of variables to be printed. + args: format string literal followed by values to be printed. """ - args: tuple[Variable, ...] + args: tuple[AssemblyExpression, ...] @property def children(self): @@ -857,11 +857,18 @@ def __call__(self, prgm: AssemblyNode, emit_calls: bool = False): self(func) return None case Print(args): - args_value_str = "" - for arg in args: - if isinstance(arg, Variable): - args_value_str = args_value_str + f"{{{self(arg)}}} " - self.exec(f"{feed}print(f'{args_value_str}')") + match args: + case (Literal(str() as fmt), *vals): + vals_expr = ", ".join(self(val) for val in vals) + if len(vals) == 0: + tuple_expr = "()" + elif len(vals) == 1: + tuple_expr = f"({vals_expr},)" + else: + tuple_expr = f"({vals_expr})" + self.exec(f"{feed}print({fmt!r} % {tuple_expr}, end='')") + case _: + raise TypeError("Print expects a literal format string") return None case Stack(obj, type_): self.exec(f"{feed}stack({self(obj)}, {str(type_)})") diff --git a/tests/reference/test_asm_print.txt b/tests/reference/test_asm_print.txt index bc911dfa8..e6432dd8e 100644 --- a/tests/reference/test_asm_print.txt +++ b/tests/reference/test_asm_print.txt @@ -1,5 +1,5 @@ -Point(x=np.float64(1.0), y=np.float64(2.0)) -(1, 4) -Point(x=np.float64(1.0), y=np.float64(2.0)) (1, 4) -9.0 -Point(x=np.float64(1.0), y=np.float64(2.0)) (1, 4) 9.0 +Point(x=np.float64(1.0), y=np.float64(2.0)) +(1, 4) +Point(x=np.float64(1.0), y=np.float64(2.0)) (1, 4) +9.0 +Point(x=np.float64(1.0), y=np.float64(2.0)) (1, 4) 9.0 diff --git a/tests/reference/test_print_compiler0_.txt b/tests/reference/test_print_compiler0_.txt index c00cc6142..5ea084d70 100644 --- a/tests/reference/test_print_compiler0_.txt +++ b/tests/reference/test_print_compiler0_.txt @@ -1,7 +1,7 @@ 32767 65536 65536 2.000000 3.000000 -Point(x=1.000000, y=2.000000) -(1, 4) -Point(x=1.000000, y=2.000000) (1, 4) +struct C_Point +struct C_CTuple +struct C_Point struct C_CTuple 9.000000 -Point(x=1.000000, y=2.000000) (1, 4) 9.000000 +struct C_Point struct C_CTuple 9.000000 diff --git a/tests/reference/test_print_compiler1_.txt b/tests/reference/test_print_compiler1_.txt index 0b9e91710..34d6c65c7 100644 --- a/tests/reference/test_print_compiler1_.txt +++ b/tests/reference/test_print_compiler1_.txt @@ -1,7 +1,7 @@ 32767 65536 65536 -2.0 3.0 +2.000000 3.000000 (1.0, 2.0) (1, 4) (1.0, 2.0) (1, 4) -9.0 -(1.0, 2.0) (1, 4) 9.0 +9.000000 +(1.0, 2.0) (1, 4) 9.000000 diff --git a/tests/test_assembly_interpreter.py b/tests/test_assembly_interpreter.py index 35d01bacd..7e679dff6 100644 --- a/tests/test_assembly_interpreter.py +++ b/tests/test_assembly_interpreter.py @@ -250,9 +250,9 @@ def test_asm_print(capsys, file_regression): (p_var, x_var), asm.Block( ( - asm.Print((p_var,)), - asm.Print((x_var,)), - asm.Print((p_var, x_var)), + asm.Print((asm.Literal("%s\n"), p_var)), + asm.Print((asm.Literal("%s\n"), x_var)), + asm.Print((asm.Literal("%s %s\n"), p_var, x_var)), asm.Assign( res_var, asm.Call( @@ -281,8 +281,10 @@ def test_asm_print(capsys, file_regression): ), ), ), - asm.Print((res_var,)), - asm.Print((p_var, x_var, res_var)), + asm.Print((asm.Literal("%s\n"), res_var)), + asm.Print( + (asm.Literal("%s %s %s\n"), p_var, x_var, res_var) + ), asm.Return(res_var), ) ), diff --git a/tests/test_codegen.py b/tests/test_codegen.py index ecdc10927..bfbce10d9 100644 --- a/tests/test_codegen.py +++ b/tests/test_codegen.py @@ -1207,16 +1207,24 @@ def test_print(compiler, capfd, file_regression): asm.Assign(i64_var, asm.Literal(np.int64(65536))), asm.Assign(f32_var, asm.Literal(np.float32(2.0))), asm.Assign(f64_var, asm.Literal(np.float64(3.0))), - asm.Print((i16_var, i32_var, i64_var)), asm.Print( ( + asm.Literal("%d %d %ld\n"), + i16_var, + i32_var, + i64_var, + ) + ), + asm.Print( + ( + asm.Literal("%f %f\n"), f32_var, f64_var, ) ), - asm.Print((p_var,)), - asm.Print((x_var,)), - asm.Print((p_var, x_var)), + asm.Print((asm.Literal("%s\n"), p_var)), + asm.Print((asm.Literal("%s\n"), x_var)), + asm.Print((asm.Literal("%s %s\n"), p_var, x_var)), asm.Assign( res_var, asm.Call( @@ -1245,8 +1253,10 @@ def test_print(compiler, capfd, file_regression): ), ), ), - asm.Print((res_var,)), - asm.Print((p_var, x_var, res_var)), + asm.Print((asm.Literal("%f\n"), res_var)), + asm.Print( + (asm.Literal("%s %s %f\n"), p_var, x_var, res_var) + ), asm.Return(res_var), ) ),