From a1def8d0e7d84d8166b5ea1b4a46b11c94d631d1 Mon Sep 17 00:00:00 2001 From: Tobias Mock Date: Fri, 20 Oct 2023 17:54:46 +0200 Subject: [PATCH] Support const fixed-size arrays Check return for const fixed-size arrays --- lib/codegen/arr.ml | 60 ++++++++++++++++++-------- lib/codegen/arr_intf.ml | 2 + lib/codegen/codegen.ml | 75 +++++++++++++++++---------------- lib/codegen/llvm_types.ml | 9 +++- lib/monomorph_tree.ml | 65 ++++++++++++++++------------ lib/monomorph_tree.mli | 19 ++++++--- lib/typing/exclusivity.ml | 5 ++- lib/typing/typing.ml | 12 ++++-- test/misc.t/const_fixed_arr.smu | 5 +++ test/misc.t/run.t | 27 ++++++++++++ 10 files changed, 186 insertions(+), 93 deletions(-) create mode 100644 test/misc.t/const_fixed_arr.smu diff --git a/lib/codegen/arr.ml b/lib/codegen/arr.ml index 00e32146..a75fc1a9 100644 --- a/lib/codegen/arr.ml +++ b/lib/codegen/arr.ml @@ -2,6 +2,7 @@ module type Core = sig open Llvm_types val gen_expr : param -> Monomorph_tree.monod_tree -> llvar + val gen_constexpr : param -> Monomorph_tree.monod_tree -> llvar end module Make @@ -288,28 +289,51 @@ struct { value = arr; typ; lltyp; kind = Ptr } - let gen_fixed_array_lit param exprs typ allocref = + let gen_fixed_array_lit param exprs typ allocref const return = let item_size = sizeof_typ typ in let lltyp = get_lltype_def typ in - let arr = get_prealloc !allocref param lltyp "arr" in - - List.iteri - (fun i expr -> - let dst = Llvm.build_gep arr [| ci 0; ci i |] "" builder in - let src = - gen_expr { param with alloca = Some dst } expr - |> func_to_closure param - in + let value, kind = + match const with + | Monomorph_tree.Cnot -> + let arr = get_prealloc !allocref param lltyp "arr" in + + List.iteri + (fun i expr -> + let dst = Llvm.build_gep arr [| ci 0; ci i |] "" builder in + let src = + gen_expr { param with alloca = Some dst } expr + |> func_to_closure param + in + + match src.kind with + | Ptr | Const_ptr -> + if dst <> src.value then + memcpy ~dst ~src ~size:(Llvm.const_int int_t item_size) + else (* The record was constructed inplace *) () + | Imm | Const -> ignore (Llvm.build_store src.value dst builder)) + exprs; + (arr, Ptr) + | Const -> + let values = + List.map (fun expr -> (gen_constexpr param expr).value) exprs + |> Array.of_list + in + let lltyp = + match typ with + | Tfixed_array (_, t) -> get_lltype_def t + | _ -> failwith "unreachable" + in + let value = Llvm.(const_array lltyp values) in + (* The value might be returned, thus boxed, so we wrap it in an automatic var *) + if return then ( + let record = get_prealloc !allocref param lltyp "" in + ignore (Llvm.build_store value record builder); + (record, Const_ptr)) + else (value, Const) + in - match src.kind with - | Ptr | Const_ptr -> - if dst <> src.value then - memcpy ~dst ~src ~size:(Llvm.const_int int_t item_size) - else (* The record was constructed inplace *) () - | Imm | Const -> ignore (Llvm.build_store src.value dst builder)) - exprs; - { value = arr; typ; lltyp; kind = Ptr } + { value; typ; lltyp; kind } let iter_fixed_array_children arr size child_typ f = let arr = bring_default arr in diff --git a/lib/codegen/arr_intf.ml b/lib/codegen/arr_intf.ml index 1f372ee9..2db9ad83 100644 --- a/lib/codegen/arr_intf.ml +++ b/lib/codegen/arr_intf.ml @@ -14,6 +14,8 @@ module type S = sig Monomorph_tree.monod_tree list -> typ -> Monomorph_tree.allocas ref -> + Monomorph_tree.const_kind -> + bool -> llvar val array_get : llvar list -> typ -> llvar diff --git a/lib/codegen/codegen.ml b/lib/codegen/codegen.ml index 124e932c..eeced335 100644 --- a/lib/codegen/codegen.ml +++ b/lib/codegen/codegen.ml @@ -9,10 +9,10 @@ module Ptrtbl = Hashtbl let the_module = Llvm_types.the_module let ( ++ ) = Seq.append let const_tbl = Strtbl.create 64 -let const_pass = ref true module rec Core : sig val gen_expr : param -> Monomorph_tree.monod_tree -> llvar + val gen_constexpr : param -> Monomorph_tree.monod_tree -> llvar val gen_function : param -> Monomorph_tree.to_gen_func -> param end = struct open T @@ -77,7 +77,9 @@ end = struct in let finalize = Some fun_finalize in - let param = { vars = tvars; alloca; finalize; rec_block } in + let param = + { vars = tvars; alloca; finalize; rec_block; const_pass = false } + in let ret = gen_expr param abs.body in (match recursive with @@ -95,6 +97,15 @@ end = struct prerr_endline name.call; failwith "Interal Error: generating non-function" + and gen_constexpr param expr = + let e = gen_expr param expr in + match e.kind with + | Const_ptr -> + (* The global value is a ptr, we need to 'deref' it *) + let value = Llvm.global_initializer e.value |> Option.get in + { e with value; kind = Const } + | _ -> e + and gen_expr param typed_expr = let fin e = match (typed_expr.return, param.finalize) with @@ -111,7 +122,10 @@ end = struct Hashtbl.replace free_tbl id v; v | Mconst (Fixed_array (arr, allocref, ms)) -> - let v = gen_fixed_array_lit param arr typed_expr.typ allocref in + let v = + gen_fixed_array_lit param arr typed_expr.typ allocref typed_expr.const + typed_expr.return + in List.iter (fun id -> Strtbl.replace free_tbl id v) ms; v | Mconst c -> gen_const c |> fin @@ -168,15 +182,15 @@ end = struct List.iter (fun id -> Strtbl.replace free_tbl id value) ms; fin value | Mif expr -> gen_if param expr - | Mrecord (labels, allocref, id, const) -> - gen_record param typed_expr.typ labels allocref id const + | Mrecord (labels, allocref, id) -> + gen_record param typed_expr.typ labels allocref id typed_expr.const typed_expr.return |> fin | Mfield (expr, index) -> gen_field param expr index |> fin | Mset (expr, value, moved) -> gen_set param expr value moved | Mseq (expr, cont) -> gen_chain param expr cont - | Mctor (ctor, allocref, id, const) -> - gen_ctor param ctor typed_expr.typ allocref id const + | Mctor (ctor, allocref, id) -> + gen_ctor param ctor typed_expr.typ allocref id | Mvar_index expr -> gen_var_index param expr |> fin | Mvar_data (expr, mid) -> gen_var_data param expr mid typed_expr.typ |> fin | Mfmt (fmts, allocref, id) -> @@ -427,11 +441,11 @@ end = struct a pointer. This isn't pretty, but will do for now. For the single param, unboxed case we can skip boxing *) let arg = - match (arg'.typ, pkind_of_typ oarg.mut arg'.typ, arg'.kind) with + match (pkind_of_typ oarg.mut arg'.typ, arg'.kind) with (* The [Two_params] case is tricky to do using only consts, so we box and use the standard runtime version *) - | (Trecord _ | Tvariant _), Boxed, Const - | (Trecord _ | Tvariant _), Unboxed (Two_params _), Const -> + | (Boxed, Const | Unboxed (Two_params _), Const) + when is_struct arg'.typ -> box_const param arg' | _ -> get_mono_func arg' param oarg.monomorph in @@ -835,7 +849,7 @@ end = struct let value, kind = match const with - | false -> + | Cnot -> let record = get_prealloc !allocref param lltyp "" in List.iteri @@ -850,19 +864,12 @@ end = struct set_struct_field value ptr) labels; (record, Ptr) - | true when not !const_pass -> + | Const -> (* We generate the const for runtime use. An addition to re-generating the constants, there are immediate literals. We have to take care that some global constants are pointers now *) let value = - let f (_, expr) = - let e = gen_expr param expr in - match e.kind with - | Const_ptr -> - (* The global value is a ptr, we need to 'deref' it *) - Llvm.global_initializer e.value |> Option.get - | _ -> e.value - in + let f (_, expr) = (gen_constexpr param expr).value in let values = List.map f labels |> Array.of_list in Llvm.const_named_struct lltyp values in @@ -872,13 +879,13 @@ end = struct ignore (Llvm.build_store value record builder); (record, Const_ptr)) else (value, Const) - | true -> - let values = - List.map (fun (_, expr) -> (gen_expr param expr).value) labels - |> Array.of_list - in - let ret = Llvm.const_named_struct lltyp values in - (ret, Const) + (* | Const -> *) + (* let values = *) + (* List.map (fun (_, expr) -> (gen_expr param expr).value) labels *) + (* |> Array.of_list *) + (* in *) + (* let ret = Llvm.const_named_struct lltyp values in *) + (* (ret, Const) *) in let v = { value; typ; lltyp; kind } in @@ -933,9 +940,7 @@ end = struct let ptr = get_const_string s in { value = ptr; typ; lltyp; kind = Const } - and gen_ctor param (variant, tag, expr) typ allocref ms const = - ignore const; - + and gen_ctor param (variant, tag, expr) typ allocref ms = (* This approach means we alloca every time, even if the enum ends up being a clike constant. There's room for improvement here *) let lltyp = get_struct typ in @@ -1128,7 +1133,7 @@ and Auto : Autogen_intf.S = Autogen.Make (T) (H) (Ar) let fill_constants constants = let f (name, tree, toplvl) = - let init = Core.gen_expr no_param tree in + let init = Core.gen_expr { no_param with const_pass = true } tree in (* We only add records to the global table, because they are expected as ptrs. For ints or floats, we just return the immediate value *) let init = @@ -1248,9 +1253,7 @@ let generate ~target ~outname ~release ~modul (* Fill const_tbl *) fill_constants constants; def_globals globals; - const_pass := false; - (* Factor out functions for llvm *) let funcs = let vars = List.fold_left @@ -1268,8 +1271,7 @@ let generate ~target ~outname ~release ~modul (* Generate functions *) List.fold_left (fun acc func -> Core.gen_function acc func) - { vars; alloca = None; finalize = None; rec_block = None } - funcs + { no_param with vars } funcs in let free_mallocs tree frees = @@ -1309,7 +1311,8 @@ let generate ~target ~outname ~release ~modul if not (Seq.is_empty frees) then let loc = (Lexing.dummy_pos, Lexing.dummy_pos) in let body = - Monomorph_tree.{ typ = Tunit; expr = Mconst Unit; return = true; loc } + Monomorph_tree. + { typ = Tunit; expr = Mconst Unit; return = true; loc; const = Cnot } in add_global_init no_param outname `Dtor (free_mallocs body frees)); (* Generate internal helper functions for arrays *) diff --git a/lib/codegen/llvm_types.ml b/lib/codegen/llvm_types.ml index eac4f818..5b0e8aa8 100644 --- a/lib/codegen/llvm_types.ml +++ b/lib/codegen/llvm_types.ml @@ -18,10 +18,17 @@ type param = { alloca : Llvm.llvalue option; finalize : (llvar -> unit) option; rec_block : rec_block option; + const_pass : bool; } let no_param = - { vars = Vars.empty; alloca = None; finalize = None; rec_block = None } + { + vars = Vars.empty; + alloca = None; + finalize = None; + rec_block = None; + const_pass = false; + } let context = Llvm.global_context () let the_module = Llvm.create_module context "context" diff --git a/lib/monomorph_tree.ml b/lib/monomorph_tree.ml index 76e21227..4b9ed354 100644 --- a/lib/monomorph_tree.ml +++ b/lib/monomorph_tree.ml @@ -22,11 +22,11 @@ type expr = id : int; ms : malloc_list; } - | Mrecord of (string * monod_tree) list * alloca * malloc_list * bool + | Mrecord of (string * monod_tree) list * alloca * malloc_list | Mfield of (monod_tree * int) | Mset of (monod_tree * monod_tree * bool) | Mseq of (monod_tree * monod_tree) - | Mctor of (string * int * monod_tree option) * alloca * malloc_list * bool + | Mctor of (string * int * monod_tree option) * alloca * malloc_list | Mvar_index of monod_tree | Mvar_data of monod_tree * int option | Mfmt of fmt list * alloca * int @@ -63,7 +63,15 @@ and call_name = | Inline of (string * int option) list * monod_tree and monod_expr = { ex : monod_tree; monomorph : call_name; mut : bool } -and monod_tree = { typ : typ; expr : expr; return : bool; loc : Ast.loc } + +and monod_tree = { + typ : typ; + expr : expr; + return : bool; + loc : Ast.loc; + const : const_kind; +} + and alloca = allocas ref and request = { id : int; lvl : int } and allocas = Preallocated | Request of request @@ -82,6 +90,7 @@ and copy_kind = Cglobal of string | Cnormal of bool and malloc_list = int list and free_list = Except of malloc_id list | Only of malloc_id list and let_kind = Lowned | Lborrow +and const_kind = Const | Cnot (* | Constexpr *) type recurs = Rnormal | Rtail | Rnone type func_name = { user : string; call : string } @@ -762,17 +771,11 @@ and subst_body p subst tree = typ = func.ret; expr = Mapp { callee; args; alloca; id; ms }; } - | Mrecord (labels, alloca, id, const) -> + | Mrecord (labels, alloca, id) -> let labels = List.map (fun (name, expr) -> (name, sub expr)) labels in - { - tree with - typ = subst tree.typ; - expr = Mrecord (labels, alloca, id, const); - } - | Mctor ((var, index, expr), alloca, id, const) -> - let expr = - Mctor ((var, index, Option.map sub expr), alloca, id, const) - in + { tree with typ = subst tree.typ; expr = Mrecord (labels, alloca, id) } + | Mctor ((var, index, expr), alloca, id) -> + let expr = Mctor ((var, index, Option.map sub expr), alloca, id) in { tree with typ = subst tree.typ; expr } | Mfield (expr, index) -> { tree with typ = subst tree.typ; expr = Mfield (sub expr, index) } @@ -1133,9 +1136,12 @@ let let_kind pass = | Dmove -> Lowned | Dset -> failwith "Internal Error: no set here" +let const (e : Typed_tree.typed_expr) = if not e.attr.const then Cnot else Const + let rec morph_expr param (texpr : Typed_tree.typed_expr) = let make expr return = - { typ = cln param texpr.typ; expr; return; loc = texpr.loc } + let (const : const_kind) = if texpr.attr.const then Const else Cnot in + { typ = cln param texpr.typ; expr; return; loc = texpr.loc; const } in match texpr.expr with | Typed_tree.Var (v, mname) -> morph_var make param v mname @@ -1153,35 +1159,36 @@ let rec morph_expr param (texpr : Typed_tree.typed_expr) = let un, p, e1, gn, ms = prep_let param id uniq rhs pass false in let p, e2, func = morph_expr { p with ret = param.ret } cont in (p, { e2 with expr = Mlet (un, e1, kind, gn, ms, e2) }, func) - | Bind (id, lhs, cont) -> + | Bind (id, lhs, ocont) -> let id = reconstr_module_username ~mname:param.mname ~mainmod:param.mainmodule id in let p, lhs, func = morph_expr { param with ret = false } lhs in let vars = Vars.add id (Normal func) p.vars in - let p, cont, func = morph_expr { p with ret = param.ret; vars } cont in + let p, cont, func = morph_expr { p with ret = param.ret; vars } ocont in ( p, { typ = cont.typ; expr = Mbind (id, lhs, cont); return = param.ret; loc = texpr.loc; + const = const ocont; }, func ) - | Record labels -> - morph_record make param labels texpr.attr (cln param texpr.typ) + | Record labels -> morph_record make param labels (cln param texpr.typ) | Field (expr, index, _) -> morph_field make param expr index | Set (expr, value) -> morph_set make param expr value | Sequence (expr, cont) -> morph_seq make param expr cont - | Function (name, uniq, abs, cont) -> + | Function (name, uniq, abs, ocont) -> let p, call, abs, alloca = prep_func param (name, uniq, abs) in - let p, cont, func = morph_expr { p with ret = param.ret } cont in + let p, cont, func = morph_expr { p with ret = param.ret } ocont in ( p, { typ = cont.typ; expr = Mfunction (call, abs, cont, alloca); return = param.ret; loc = texpr.loc; + const = const ocont; }, func ) | Mutual_rec_decls (decls, cont) -> @@ -1197,8 +1204,7 @@ let rec morph_expr param (texpr : Typed_tree.typed_expr) = | App { callee; args } -> morph_app make param callee args (cln param texpr.typ) | Ctor (variant, index, dataexpr) -> - morph_ctor make param variant index dataexpr texpr.attr - (cln param texpr.typ) + morph_ctor make param variant index dataexpr (cln param texpr.typ) | Variant_index expr -> morph_var_index make param expr | Variant_data expr -> morph_var_data make param expr (cln param texpr.typ) | Fmt exprs -> morph_fmt make param exprs @@ -1477,7 +1483,7 @@ and prep_let p id uniq e pass toplvl = in (un, p, e1, gn, ms) -and morph_record mk p labels is_const typ = +and morph_record mk p labels typ = let ret = p.ret in let p = { p with ret = false } in @@ -1500,7 +1506,7 @@ and morph_record mk p labels is_const typ = let alloca = ref (request ()) in ( { p with ret; mallocs }, - mk (Mrecord (labels, alloca, ms, is_const.const)) ret, + mk (Mrecord (labels, alloca, ms)) ret, { no_var with fn = No_function; alloc = Value alloca; malloc } ) and morph_field mk p expr index = @@ -1829,7 +1835,7 @@ and morph_app mk p callee args ret_typ = ({ p with ret; mallocs }, mk app ret, { no_var with alloc; malloc; tailrec }) -and morph_ctor mk p variant index expr is_const typ = +and morph_ctor mk p variant index expr typ = let ret = p.ret in let p = { p with ret = false } in @@ -1854,7 +1860,7 @@ and morph_ctor mk p variant index expr is_const typ = let alloca = ref (request ()) in ( { p with ret; mallocs }, - mk (Mctor (ctor, alloca, ms, is_const.const)) ret, + mk (Mctor (ctor, alloca, ms)) ret, { no_var with fn = No_function; alloc = Value alloca; malloc } ) (* Both variant exprs are as default as possible. @@ -1933,7 +1939,9 @@ let rec morph_toplvl param items = let rec aux param = function | [] -> let loc = (Lexing.dummy_pos, Lexing.dummy_pos) in - (param, { typ = Tunit; expr = Mconst Unit; return = true; loc }, no_var) + ( param, + { typ = Tunit; expr = Mconst Unit; return = true; loc; const = Cnot }, + no_var ) | [ (mname, Typed_tree.Tl_expr e) ] -> let param = { param with mname } in morph_expr param e @@ -1955,6 +1963,7 @@ let rec morph_toplvl param items = expr = Mfunction (call, abs, cont, alloca); return = param.ret; loc; + const = Cnot; }, func ) | Tl_bind (id, expr) -> @@ -1979,6 +1988,7 @@ let rec morph_toplvl param items = expr = Mseq (e, cont); return = param.ret; loc = e.loc; + const = Cnot; }, func ) | Tl_module mitems -> @@ -1990,6 +2000,7 @@ let rec morph_toplvl param items = expr = Mseq (e, cont); return = param.ret; loc = e.loc; + const = Cnot; }, func ) | Tl_module_alias _ -> aux param tl diff --git a/lib/monomorph_tree.mli b/lib/monomorph_tree.mli index 2e639fdd..45b8272c 100644 --- a/lib/monomorph_tree.mli +++ b/lib/monomorph_tree.mli @@ -22,15 +22,11 @@ type expr = id : int; (* Internal id for nested monomorphization *) ms : malloc_list; } - | Mrecord of - (string * monod_tree) list - * alloca - * malloc_list - * bool (* bool: is_const *) + | Mrecord of (string * monod_tree) list * alloca * malloc_list | Mfield of (monod_tree * int) | Mset of (monod_tree * monod_tree * bool (* is moved *)) | Mseq of (monod_tree * monod_tree) - | Mctor of (string * int * monod_tree option) * alloca * malloc_list * bool + | Mctor of (string * int * monod_tree option) * alloca * malloc_list | Mvar_index of monod_tree | Mvar_data of monod_tree * int option | Mfmt of fmt list * alloca * int @@ -71,7 +67,15 @@ and call_name = (* Builtin function with special codegen *) and monod_expr = { ex : monod_tree; monomorph : call_name; mut : bool } -and monod_tree = { typ : typ; expr : expr; return : bool; loc : Ast.loc } + +and monod_tree = { + typ : typ; + expr : expr; + return : bool; + loc : Ast.loc; + const : const_kind; +} + and alloca = allocas ref and request = { id : int; lvl : int } and allocas = Preallocated | Request of request @@ -94,6 +98,7 @@ and free_list = | Only of Malloc_types.malloc_id list and let_kind = Lowned | Lborrow +and const_kind = Const | Cnot (* | Constexpr *) type recurs = Rnormal | Rtail | Rnone type func_name = { user : string; call : string } diff --git a/lib/typing/exclusivity.ml b/lib/typing/exclusivity.ml index 196cb763..a2c2ca9f 100644 --- a/lib/typing/exclusivity.ml +++ b/lib/typing/exclusivity.ml @@ -601,7 +601,10 @@ let rec check_tree env mut ((bpart, special) as bdata) tree hist = let hs, es = List.fold_left_map (fun hs e -> - let expr, v, hs = check_tree env Umove no_bdata e hs in + let usage = + if contains_allocation e.typ then Usage.Umove else Uread + in + let expr, v, hs = check_tree env usage no_bdata e hs in let expr = { expr with expr = Move expr } in (add_hist v hs, expr)) hist es diff --git a/lib/typing/typing.ml b/lib/typing/typing.ml index 3afdf5f4..5313ad70 100644 --- a/lib/typing/typing.ml +++ b/lib/typing/typing.ml @@ -790,15 +790,21 @@ end = struct let f (typ, const) expr = let expr = convert env expr in unify (loc, "In fixed-size array literal") typ expr.typ env; - let const = const && expr.attr.const in + let const = + const + && + (* There's a special case for string literals. + They will get copied here which makes them not const. + NOTE copy in convert_tuple *) + match expr.expr with Const (String _) -> false | _ -> expr.attr.const + in ((typ, const), expr) in let (typ, const), exprs = List.fold_left_map f (newvar (), true) arr in let typ = Tfixed_array (ref (Known (List.length arr)), typ) in - ignore const; (* TODO check mut for const and introduce constexpr *) - let attr = { no_attr with const = false } in + let attr = { no_attr with const } in { typ; expr = Const (Fixed_array exprs); attr; loc } and typeof_annot_decl env loc annot block = diff --git a/test/misc.t/const_fixed_arr.smu b/test/misc.t/const_fixed_arr.smu new file mode 100644 index 00000000..f0a6d78f --- /dev/null +++ b/test/misc.t/const_fixed_arr.smu @@ -0,0 +1,5 @@ +(def a 17) +(def arr #[1 a 3]) + +(print (fmt-str arr.(1))) +(def _ {10 a}) diff --git a/test/misc.t/run.t b/test/misc.t/run.t index da4bb57f..16cfb15b 100644 --- a/test/misc.t/run.t +++ b/test/misc.t/run.t @@ -4093,3 +4093,30 @@ Check allocs in fixed array hi hie oho + +Const fixed array + $ schmu --dump-llvm const_fixed_arr.smu + ; ModuleID = 'context' + source_filename = "context" + target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" + + %tuple_int_int = type { i64, i64 } + + @schmu_a = constant i64 17 + @schmu_arr = constant [3 x i64] [i64 1, i64 17, i64 3] + @0 = private unnamed_addr constant { i64, i64, [5 x i8] } { i64 4, i64 4, [5 x i8] c"%li\0A\00" } + + define i64 @main(i64 %arg) { + entry: + tail call void (i8*, ...) @printf(i8* getelementptr (i8, i8* bitcast ({ i64, i64, [5 x i8] }* @0 to i8*), i64 16), i64 17) + %0 = alloca %tuple_int_int, align 8 + %"01" = bitcast %tuple_int_int* %0 to i64* + store i64 10, i64* %"01", align 8 + %"1" = getelementptr inbounds %tuple_int_int, %tuple_int_int* %0, i32 0, i32 1 + store i64 17, i64* %"1", align 8 + ret i64 0 + } + + declare void @printf(i8* %0, ...) + $ valgrind -q --leak-check=yes --show-reachable=yes ./const_fixed_arr + 17