From cbe8a77ec1a41412678830ff2444d69afb4bc7b4 Mon Sep 17 00:00:00 2001 From: Oscar Boykin Date: Wed, 11 Feb 2026 13:12:38 -1000 Subject: [PATCH] checkpoint: stack allocation escape analysis in clang gen --- c_runtime/bosatsu_runtime.h | 148 ++++ .../dev/bosatsu/codegen/clang/ClangGen.scala | 655 +++++++++++++++++- .../bosatsu/codegen/clang/ClangGenTest.scala | 314 +++++++++ 3 files changed, 1116 insertions(+), 1 deletion(-) diff --git a/c_runtime/bosatsu_runtime.h b/c_runtime/bosatsu_runtime.h index f6a417fb0..0d3272c76 100644 --- a/c_runtime/bosatsu_runtime.h +++ b/c_runtime/bosatsu_runtime.h @@ -26,6 +26,154 @@ static inline const void* bsts_bvalue_to_const_ptr(BValue value) { #define BSTS_PTR(type, value) ((type*)bsts_bvalue_to_ptr((value))) #define BSTS_CONST_PTR(type, value) ((const type*)bsts_bvalue_to_const_ptr((value))) +/* + * Stack allocation helpers for non-escaping values. These are intended for + * constructor values that provably do not outlive the current function scope. + * + * IMPORTANT: + * - These values MUST NOT be returned or stored in closures/statics. + * - Struct payloads are contiguous BValue fields at offset 0. + * - Enum payloads are prefixed by (ENUM_TAG tag, int32_t pad). + */ +#define BSTS_STACK_ALLOC_STRUCT_N(arity, ...) \ + bsts_bvalue_from_ptr((const void*)(&(struct { \ + BValue fields[(arity)]; \ + }){ .fields = { __VA_ARGS__ } })) + +#define BSTS_STACK_ALLOC_ENUM_N(arity, tag_value, ...) \ + bsts_bvalue_from_ptr((const void*)(&(struct { \ + ENUM_TAG _tag; \ + int32_t _pad; \ + BValue fields[(arity)]; \ + }){ ._tag = (tag_value), ._pad = 0, .fields = { __VA_ARGS__ } })) + +#define BSTS_STACK_ALLOC_STRUCT2(a0, a1) \ + BSTS_STACK_ALLOC_STRUCT_N(2, a0, a1) +#define BSTS_STACK_ALLOC_STRUCT3(a0, a1, a2) \ + BSTS_STACK_ALLOC_STRUCT_N(3, a0, a1, a2) +#define BSTS_STACK_ALLOC_STRUCT4(a0, a1, a2, a3) \ + BSTS_STACK_ALLOC_STRUCT_N(4, a0, a1, a2, a3) +#define BSTS_STACK_ALLOC_STRUCT5(a0, a1, a2, a3, a4) \ + BSTS_STACK_ALLOC_STRUCT_N(5, a0, a1, a2, a3, a4) +#define BSTS_STACK_ALLOC_STRUCT6(a0, a1, a2, a3, a4, a5) \ + BSTS_STACK_ALLOC_STRUCT_N(6, a0, a1, a2, a3, a4, a5) +#define BSTS_STACK_ALLOC_STRUCT7(a0, a1, a2, a3, a4, a5, a6) \ + BSTS_STACK_ALLOC_STRUCT_N(7, a0, a1, a2, a3, a4, a5, a6) +#define BSTS_STACK_ALLOC_STRUCT8(a0, a1, a2, a3, a4, a5, a6, a7) \ + BSTS_STACK_ALLOC_STRUCT_N(8, a0, a1, a2, a3, a4, a5, a6, a7) +#define BSTS_STACK_ALLOC_STRUCT9(a0, a1, a2, a3, a4, a5, a6, a7, a8) \ + BSTS_STACK_ALLOC_STRUCT_N(9, a0, a1, a2, a3, a4, a5, a6, a7, a8) +#define BSTS_STACK_ALLOC_STRUCT10(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9) \ + BSTS_STACK_ALLOC_STRUCT_N(10, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9) +#define BSTS_STACK_ALLOC_STRUCT11(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) \ + BSTS_STACK_ALLOC_STRUCT_N(11, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) +#define BSTS_STACK_ALLOC_STRUCT12(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11) \ + BSTS_STACK_ALLOC_STRUCT_N(12, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11) +#define BSTS_STACK_ALLOC_STRUCT13(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12) \ + BSTS_STACK_ALLOC_STRUCT_N(13, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12) +#define BSTS_STACK_ALLOC_STRUCT14(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13) \ + BSTS_STACK_ALLOC_STRUCT_N(14, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13) +#define BSTS_STACK_ALLOC_STRUCT15(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14) \ + BSTS_STACK_ALLOC_STRUCT_N(15, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14) +#define BSTS_STACK_ALLOC_STRUCT16(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15) \ + BSTS_STACK_ALLOC_STRUCT_N(16, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15) +#define BSTS_STACK_ALLOC_STRUCT17(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16) \ + BSTS_STACK_ALLOC_STRUCT_N(17, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16) +#define BSTS_STACK_ALLOC_STRUCT18(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17) \ + BSTS_STACK_ALLOC_STRUCT_N(18, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17) +#define BSTS_STACK_ALLOC_STRUCT19(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18) \ + BSTS_STACK_ALLOC_STRUCT_N(19, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18) +#define BSTS_STACK_ALLOC_STRUCT20(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19) \ + BSTS_STACK_ALLOC_STRUCT_N(20, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19) +#define BSTS_STACK_ALLOC_STRUCT21(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) \ + BSTS_STACK_ALLOC_STRUCT_N(21, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) +#define BSTS_STACK_ALLOC_STRUCT22(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21) \ + BSTS_STACK_ALLOC_STRUCT_N(22, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21) +#define BSTS_STACK_ALLOC_STRUCT23(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22) \ + BSTS_STACK_ALLOC_STRUCT_N(23, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22) +#define BSTS_STACK_ALLOC_STRUCT24(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23) \ + BSTS_STACK_ALLOC_STRUCT_N(24, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23) +#define BSTS_STACK_ALLOC_STRUCT25(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24) \ + BSTS_STACK_ALLOC_STRUCT_N(25, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24) +#define BSTS_STACK_ALLOC_STRUCT26(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25) \ + BSTS_STACK_ALLOC_STRUCT_N(26, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25) +#define BSTS_STACK_ALLOC_STRUCT27(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26) \ + BSTS_STACK_ALLOC_STRUCT_N(27, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26) +#define BSTS_STACK_ALLOC_STRUCT28(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27) \ + BSTS_STACK_ALLOC_STRUCT_N(28, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27) +#define BSTS_STACK_ALLOC_STRUCT29(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28) \ + BSTS_STACK_ALLOC_STRUCT_N(29, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28) +#define BSTS_STACK_ALLOC_STRUCT30(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29) \ + BSTS_STACK_ALLOC_STRUCT_N(30, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29) +#define BSTS_STACK_ALLOC_STRUCT31(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30) \ + BSTS_STACK_ALLOC_STRUCT_N(31, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30) +#define BSTS_STACK_ALLOC_STRUCT32(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31) \ + BSTS_STACK_ALLOC_STRUCT_N(32, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31) + +#define BSTS_STACK_ALLOC_ENUM1(tag_value, a0) \ + BSTS_STACK_ALLOC_ENUM_N(1, tag_value, a0) +#define BSTS_STACK_ALLOC_ENUM2(tag_value, a0, a1) \ + BSTS_STACK_ALLOC_ENUM_N(2, tag_value, a0, a1) +#define BSTS_STACK_ALLOC_ENUM3(tag_value, a0, a1, a2) \ + BSTS_STACK_ALLOC_ENUM_N(3, tag_value, a0, a1, a2) +#define BSTS_STACK_ALLOC_ENUM4(tag_value, a0, a1, a2, a3) \ + BSTS_STACK_ALLOC_ENUM_N(4, tag_value, a0, a1, a2, a3) +#define BSTS_STACK_ALLOC_ENUM5(tag_value, a0, a1, a2, a3, a4) \ + BSTS_STACK_ALLOC_ENUM_N(5, tag_value, a0, a1, a2, a3, a4) +#define BSTS_STACK_ALLOC_ENUM6(tag_value, a0, a1, a2, a3, a4, a5) \ + BSTS_STACK_ALLOC_ENUM_N(6, tag_value, a0, a1, a2, a3, a4, a5) +#define BSTS_STACK_ALLOC_ENUM7(tag_value, a0, a1, a2, a3, a4, a5, a6) \ + BSTS_STACK_ALLOC_ENUM_N(7, tag_value, a0, a1, a2, a3, a4, a5, a6) +#define BSTS_STACK_ALLOC_ENUM8(tag_value, a0, a1, a2, a3, a4, a5, a6, a7) \ + BSTS_STACK_ALLOC_ENUM_N(8, tag_value, a0, a1, a2, a3, a4, a5, a6, a7) +#define BSTS_STACK_ALLOC_ENUM9(tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8) \ + BSTS_STACK_ALLOC_ENUM_N(9, tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8) +#define BSTS_STACK_ALLOC_ENUM10(tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9) \ + BSTS_STACK_ALLOC_ENUM_N(10, tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9) +#define BSTS_STACK_ALLOC_ENUM11(tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) \ + BSTS_STACK_ALLOC_ENUM_N(11, tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) +#define BSTS_STACK_ALLOC_ENUM12(tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11) \ + BSTS_STACK_ALLOC_ENUM_N(12, tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11) +#define BSTS_STACK_ALLOC_ENUM13(tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12) \ + BSTS_STACK_ALLOC_ENUM_N(13, tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12) +#define BSTS_STACK_ALLOC_ENUM14(tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13) \ + BSTS_STACK_ALLOC_ENUM_N(14, tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13) +#define BSTS_STACK_ALLOC_ENUM15(tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14) \ + BSTS_STACK_ALLOC_ENUM_N(15, tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14) +#define BSTS_STACK_ALLOC_ENUM16(tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15) \ + BSTS_STACK_ALLOC_ENUM_N(16, tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15) +#define BSTS_STACK_ALLOC_ENUM17(tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16) \ + BSTS_STACK_ALLOC_ENUM_N(17, tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16) +#define BSTS_STACK_ALLOC_ENUM18(tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17) \ + BSTS_STACK_ALLOC_ENUM_N(18, tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17) +#define BSTS_STACK_ALLOC_ENUM19(tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18) \ + BSTS_STACK_ALLOC_ENUM_N(19, tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18) +#define BSTS_STACK_ALLOC_ENUM20(tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19) \ + BSTS_STACK_ALLOC_ENUM_N(20, tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19) +#define BSTS_STACK_ALLOC_ENUM21(tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) \ + BSTS_STACK_ALLOC_ENUM_N(21, tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) +#define BSTS_STACK_ALLOC_ENUM22(tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21) \ + BSTS_STACK_ALLOC_ENUM_N(22, tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21) +#define BSTS_STACK_ALLOC_ENUM23(tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22) \ + BSTS_STACK_ALLOC_ENUM_N(23, tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22) +#define BSTS_STACK_ALLOC_ENUM24(tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23) \ + BSTS_STACK_ALLOC_ENUM_N(24, tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23) +#define BSTS_STACK_ALLOC_ENUM25(tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24) \ + BSTS_STACK_ALLOC_ENUM_N(25, tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24) +#define BSTS_STACK_ALLOC_ENUM26(tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25) \ + BSTS_STACK_ALLOC_ENUM_N(26, tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25) +#define BSTS_STACK_ALLOC_ENUM27(tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26) \ + BSTS_STACK_ALLOC_ENUM_N(27, tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26) +#define BSTS_STACK_ALLOC_ENUM28(tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27) \ + BSTS_STACK_ALLOC_ENUM_N(28, tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27) +#define BSTS_STACK_ALLOC_ENUM29(tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28) \ + BSTS_STACK_ALLOC_ENUM_N(29, tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28) +#define BSTS_STACK_ALLOC_ENUM30(tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29) \ + BSTS_STACK_ALLOC_ENUM_N(30, tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29) +#define BSTS_STACK_ALLOC_ENUM31(tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30) \ + BSTS_STACK_ALLOC_ENUM_N(31, tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30) +#define BSTS_STACK_ALLOC_ENUM32(tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31) \ + BSTS_STACK_ALLOC_ENUM_N(32, tag_value, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31) // Nat values are encoded in integers // TODO: move these to functions implemented in bosatsu_runtime.c #define BSTS_NAT_0 ((BValue)((uintptr_t)0x1)) diff --git a/core/src/main/scala/dev/bosatsu/codegen/clang/ClangGen.scala b/core/src/main/scala/dev/bosatsu/codegen/clang/ClangGen.scala index 5171a88b5..2aa415f7c 100644 --- a/core/src/main/scala/dev/bosatsu/codegen/clang/ClangGen.scala +++ b/core/src/main/scala/dev/bosatsu/codegen/clang/ClangGen.scala @@ -11,7 +11,9 @@ import dev.bosatsu.pattern.StrPart import dev.bosatsu.Matchless.Expr import dev.bosatsu.Identifier.Bindable import org.typelevel.paiges.Doc +import scala.collection.mutable import scala.collection.immutable.{SortedMap, SortedSet} +import scala.annotation.nowarn import cats.syntax.all._ @@ -1448,7 +1450,9 @@ class ClangGen[K](ns: CompilationNamespace[K]) { .map(_._1.finalFile) def appendStatement(stmt: Code.Statement): T[Unit] = - StateT.modify(s => s.copy(stmts = s.stmts :+ stmt)) + StateT.modify(s => + s.copy(stmts = s.stmts :+ ClangGen.StackAllocPass.optimize(stmt)) + ) def errorRes[A](e: => Error): EitherT[Eval, Error, A] = EitherT[Eval, Error, A](Eval.later(Left(e))) @@ -1844,6 +1848,655 @@ object ClangGen { ) extends Error } + private object StackAllocPass { + private final case class Ref[A <: AnyRef](get: A) { + override def equals(that: Any): Boolean = + that match { + case ref: Ref[?] => get eq ref.get + case _ => false + } + override def hashCode(): Int = + System.identityHashCode(get) + } + + private enum AllocKind { + case Enum + case Struct + } + private given CanEqual[AllocKind, AllocKind] = CanEqual.derived + private case class AllocMeta(kind: AllocKind, arity: Int) { + def heapLike: Boolean = + kind match { + case AllocKind.Enum => arity > 0 + case AllocKind.Struct => arity > 1 + } + + def apply(args: List[Code.Expression]): Code.Expression = + kind match { + case AllocKind.Enum => stackAllocEnum(arity, args) + case AllocKind.Struct => stackAllocStruct(arity, args) + } + } + + private enum ParentRef { + case ParentExpr(parent: Code.Expression, role: ExprRole) + case ParentStmt(parent: Code.Statement, role: StmtRole) + } + private enum ExprRole { + case ApplyFn + case ApplyArg(index: Int) + case Wrapped + } + private given CanEqual[ExprRole, ExprRole] = CanEqual.derived + private enum StmtRole { + case AssignTarget + case AssignValue + case DeclareInit + case DeclareArrayValue + case ReturnValue + case Other + } + private given CanEqual[StmtRole, StmtRole] = CanEqual.derived + private enum GraphNode { + case AllocNode(id: Int) + case VarNode(id: Code.Ident) + case EscapeNode + } + + private enum Consumer { + case ToVar(id: Code.Ident) + case ToAlloc(id: Int) + case Escape + case Local + case Ignore + } + @nowarn("msg=unused private member") + private given CanEqual[Consumer, Consumer] = CanEqual.derived + + private val getterFns: Set[String] = + Set("get_variant", "get_variant_value", "get_enum_index", "get_struct_index") + + private def isEscapeBoundaryCall(name: String): Boolean = + name == "read_or_build" || + name.startsWith("call_fn") || + name.startsWith("alloc_closure") || + name.startsWith("alloc_boxed_pure_fn") + + private def isBValueType(tpe: Code.TypeIdent): Boolean = + tpe match { + case Code.TypeIdent.Named("BValue") => true + case _ => false + } + + private def calleeName(app: Code.Apply): Option[String] = + app.fn match { + case Code.Ident(name) => Some(name) + case _ => None + } + + private def allocInfoOf(app: Code.Apply): Option[AllocMeta] = + calleeName(app).flatMap { name => + if (name.startsWith("alloc_enum")) { + name.stripPrefix("alloc_enum").toIntOption.map { arity => + AllocMeta(AllocKind.Enum, arity) + } + } else if (name.startsWith("alloc_struct")) { + name.stripPrefix("alloc_struct").toIntOption.map { arity => + AllocMeta(AllocKind.Struct, arity) + } + } else None + } + + private case class Analysis( + parentOf: Map[Ref[Code.Expression], ParentRef], + allocIds: Map[Ref[Code.Expression], Int], + allocExprById: Map[Int, Code.Expression], + allocById: Map[Int, AllocMeta], + bValueVars: Set[Code.Ident], + mutableVars: Set[Code.Ident], + identExprs: List[(Code.Expression, Code.Ident)] + ) + + private def allocIdOf( + allocIds: Map[Ref[Code.Expression], Int], + expr: Code.Expression + ): Option[Int] = + allocIds.get(Ref(expr)) + + private def analyze(fn: Code.DeclareFn): Option[(Analysis, Set[Int])] = + fn.value match { + case None => None + case Some(body) => + val parentOf = mutable.Map.empty[Ref[Code.Expression], ParentRef] + val allocIds = mutable.Map.empty[Ref[Code.Expression], Int] + val allocExprById = mutable.Map.empty[Int, Code.Expression] + val allocById = mutable.Map.empty[Int, AllocMeta] + val bValueVars = mutable.Set.empty[Code.Ident] + val assignmentTargets = mutable.Set.empty[Code.Ident] + val identExprs = mutable.ListBuffer.empty[(Code.Expression, Code.Ident)] + var nextAllocId = 0 + + fn.args.iterator.foreach { + case Code.Param(tpe, name) if isBValueType(tpe) => + bValueVars += name + case _ => () + } + + def setParent( + child: Code.Expression, + parent: ParentRef + ): Unit = + parentOf.update(Ref(child), parent) + + def visitExpr(expr: Code.Expression): Unit = { + expr match { + case id @ Code.Ident(name) => + identExprs += ((id, Code.Ident(name))) + + case app @ Code.Apply(fnExpr, args) => + allocInfoOf(app).foreach { info => + val allocId = nextAllocId + nextAllocId += 1 + allocIds.update(Ref(app), allocId) + allocExprById.update(allocId, app) + allocById.update(allocId, info) + } + setParent(fnExpr, ParentRef.ParentExpr(app, ExprRole.ApplyFn)) + visitExpr(fnExpr) + args.zipWithIndex.foreach { case (argExpr, idx) => + setParent( + argExpr, + ParentRef.ParentExpr(app, ExprRole.ApplyArg(idx)) + ) + visitExpr(argExpr) + } + + case cast @ Code.Cast(_, in) => + setParent(in, ParentRef.ParentExpr(cast, ExprRole.Wrapped)) + visitExpr(in) + + case sel @ Code.Select(target, _) => + setParent(target, ParentRef.ParentExpr(sel, ExprRole.Wrapped)) + visitExpr(target) + + case bin @ Code.BinExpr(left, _, right) => + setParent(left, ParentRef.ParentExpr(bin, ExprRole.Wrapped)) + setParent(right, ParentRef.ParentExpr(bin, ExprRole.Wrapped)) + visitExpr(left) + visitExpr(right) + + case pre @ Code.PrefixExpr(_, target) => + setParent(target, ParentRef.ParentExpr(pre, ExprRole.Wrapped)) + visitExpr(target) + + case post @ Code.PostfixExpr(target, _) => + setParent(target, ParentRef.ParentExpr(post, ExprRole.Wrapped)) + visitExpr(target) + + case bracket @ Code.Bracket(target, item) => + setParent( + target, + ParentRef.ParentExpr(bracket, ExprRole.Wrapped) + ) + setParent(item, ParentRef.ParentExpr(bracket, ExprRole.Wrapped)) + visitExpr(target) + visitExpr(item) + + case tern @ Code.Ternary(cond, whenTrue, whenFalse) => + setParent(cond, ParentRef.ParentExpr(tern, ExprRole.Wrapped)) + setParent( + whenTrue, + ParentRef.ParentExpr(tern, ExprRole.Wrapped) + ) + setParent( + whenFalse, + ParentRef.ParentExpr(tern, ExprRole.Wrapped) + ) + visitExpr(cond) + visitExpr(whenTrue) + visitExpr(whenFalse) + + case Code.IntLiteral(_) | Code.StrLiteral(_) => + () + } + } + + def visitStatement(stmt: Code.Statement): Unit = + stmt match { + case assign @ Code.Assignment(target, value) => + setParent( + target, + ParentRef.ParentStmt(assign, StmtRole.AssignTarget) + ) + setParent( + value, + ParentRef.ParentStmt(assign, StmtRole.AssignValue) + ) + visitExpr(target) + visitExpr(value) + target match { + case id: Code.Ident => assignmentTargets += id + case _ => () + } + + case decl @ Code.DeclareVar(_, tpe, ident, valueOpt) => + if (isBValueType(tpe)) { + bValueVars += ident + } + valueOpt.foreach { value => + setParent( + value, + ParentRef.ParentStmt(decl, StmtRole.DeclareInit) + ) + visitExpr(value) + } + + case Code.DeclareArray(_, _, values) => + values match { + case Left(_) => () + case Right(vs) => + vs.foreach { value => + setParent( + value, + ParentRef.ParentStmt(stmt, StmtRole.DeclareArrayValue) + ) + visitExpr(value) + } + } + + case Code.Return(valueOpt) => + valueOpt.foreach { value => + setParent( + value, + ParentRef.ParentStmt(stmt, StmtRole.ReturnValue) + ) + visitExpr(value) + } + + case Code.Effect(expr) => + setParent(expr, ParentRef.ParentStmt(stmt, StmtRole.Other)) + visitExpr(expr) + + case Code.While(cond, body) => + setParent(cond, ParentRef.ParentStmt(stmt, StmtRole.Other)) + visitExpr(cond) + visitStatement(body) + + case Code.DoWhile(body, cond) => + visitStatement(body) + setParent(cond, ParentRef.ParentStmt(stmt, StmtRole.Other)) + visitExpr(cond) + + case Code.IfElse(ifs, elseCond) => + ifs.iterator.foreach { case (cond, blk) => + setParent(cond, ParentRef.ParentStmt(stmt, StmtRole.Other)) + visitExpr(cond) + visitStatement(blk) + } + elseCond.foreach(visitStatement) + + case Code.Block(items) => + items.iterator.foreach(visitStatement) + + case Code.Statements(items) => + items.iterator.foreach(visitStatement) + + case _: Code.DeclareFn | _: Code.Include | _: Code.Typedef | + _: Code.DefineComplex => + () + } + + visitStatement(body) + + if (allocById.isEmpty) None + else { + val mutableVars = assignmentTargets.toSet.intersect(bValueVars.toSet) + val analysis = Analysis( + parentOf = parentOf.toMap, + allocIds = allocIds.toMap, + allocExprById = allocExprById.toMap, + allocById = allocById.toMap, + bValueVars = bValueVars.toSet, + mutableVars = mutableVars, + identExprs = identExprs.toList + ) + val stackAllocIds = findStackAllocIds(analysis) + Some((analysis, stackAllocIds)) + } + } + + private def findStackAllocIds(analysis: Analysis): Set[Int] = { + import Consumer._ + import GraphNode._ + import ParentRef._ + + val reverseEdges = + mutable.Map.empty[GraphNode, mutable.Set[GraphNode]] + + def addEdge(src: GraphNode, dst: GraphNode): Unit = { + val predSet = reverseEdges.getOrElseUpdate(dst, mutable.Set.empty) + predSet += src + } + + def asVarTarget(id: Code.Ident): Consumer = + if (analysis.mutableVars(id)) Escape + else ToVar(id) + + def consumerOf(expr: Code.Expression): Consumer = { + var current: Code.Expression = expr + var done = false + var result: Consumer = Local + + while (!done) { + analysis.parentOf.get(Ref(current)) match { + case None => + done = true + result = Local + + case Some(ParentExpr(parent, role)) => + parent match { + case app @ Code.Apply(_, _) => + role match { + case ExprRole.ApplyFn => + current = parent + case ExprRole.ApplyArg(_) => + allocInfoOf(app) match { + case Some(_) => + allocIdOf(analysis.allocIds, app) match { + case Some(id) => + done = true + result = ToAlloc(id) + case None => + done = true + result = Local + } + case None => + calleeName(app) match { + case Some(name) if getterFns(name) => + done = true + result = Local + case Some(name) if isEscapeBoundaryCall(name) => + done = true + result = Escape + case _ => + done = true + // Without interprocedural escape facts, any non-constructor + // call argument is treated as potentially escaping. + result = Escape + } + } + case ExprRole.Wrapped => + current = parent + } + case _ => + current = parent + } + + case Some(ParentStmt(parent, role)) => + parent match { + case Code.Assignment(target, _) => + role match { + case StmtRole.AssignTarget => + done = true + result = Ignore + case StmtRole.AssignValue => + target match { + case id: Code.Ident if analysis.bValueVars(id) => + done = true + result = asVarTarget(id) + case _ => + done = true + result = Escape + } + case _ => + done = true + result = Local + } + + case Code.DeclareVar(_, tpe, ident, _) => + role match { + case StmtRole.DeclareInit if isBValueType(tpe) => + done = true + result = asVarTarget(ident) + case StmtRole.DeclareInit => + done = true + result = Escape + case StmtRole.DeclareArrayValue => + done = true + result = Escape + case _ => + done = true + result = Local + } + + case _: Code.DeclareArray => + role match { + case StmtRole.DeclareArrayValue => + done = true + result = Escape + case _ => + done = true + result = Local + } + + case Code.Return(_) => + role match { + case StmtRole.ReturnValue => + done = true + result = Escape + case _ => + done = true + result = Local + } + + case _ => + done = true + result = Local + } + } + } + + result + } + + analysis.allocById.iterator.foreach { case (id, _) => + analysis.allocExprById.get(id).foreach { allocExpr => + consumerOf(allocExpr) match { + case ToVar(varId) => addEdge(AllocNode(id), VarNode(varId)) + case ToAlloc(dstId) => addEdge(AllocNode(id), AllocNode(dstId)) + case Escape => addEdge(AllocNode(id), EscapeNode) + case Local | Ignore => () + } + } + } + + analysis.identExprs.foreach { case (expr, ident) => + if (analysis.bValueVars(ident)) { + consumerOf(expr) match { + case ToVar(dstVar) => addEdge(VarNode(ident), VarNode(dstVar)) + case ToAlloc(dstId) => addEdge(VarNode(ident), AllocNode(dstId)) + case Escape => addEdge(VarNode(ident), EscapeNode) + case Local | Ignore => () + } + } + } + + val escaping = mutable.Set.empty[GraphNode] + val seen = mutable.Set.empty[GraphNode] + val stack = mutable.Stack[GraphNode](EscapeNode) + while (stack.nonEmpty) { + val node = stack.pop() + if (!seen(node)) { + seen += node + reverseEdges.get(node).foreach { preds => + preds.foreach { pred => + escaping += pred + stack.push(pred) + } + } + } + } + + analysis.allocById.iterator.collect { + case (id, meta) if meta.heapLike && !escaping(AllocNode(id)) => id + }.toSet + } + + private def stackAllocEnum( + arity: Int, + args: List[Code.Expression] + ): Code.Expression = + Code.Apply(Code.Ident(s"BSTS_STACK_ALLOC_ENUM$arity"), args) + + private def stackAllocStruct( + arity: Int, + args: List[Code.Expression] + ): Code.Expression = + Code.Apply(Code.Ident(s"BSTS_STACK_ALLOC_STRUCT$arity"), args) + + private def rewriteExpr( + expr: Code.Expression, + analysis: Analysis, + stackAllocIds: Set[Int] + ): Code.Expression = { + val rewrittenChildren: Code.Expression = + expr match { + case app @ Code.Apply(fnExpr, args) => + val fn1 = rewriteExpr(fnExpr, analysis, stackAllocIds) + val args1 = args.map(rewriteExpr(_, analysis, stackAllocIds)) + app.copy(fn = fn1, args = args1) + case cast @ Code.Cast(_, in) => + cast.copy(expr = rewriteExpr(in, analysis, stackAllocIds)) + case sel @ Code.Select(target, _) => + sel.copy(target = rewriteExpr(target, analysis, stackAllocIds)) + case Code.BinExpr(left, op, right) => + Code.BinExpr( + rewriteExpr(left, analysis, stackAllocIds), + op, + rewriteExpr(right, analysis, stackAllocIds) + ) + case Code.PrefixExpr(op, target) => + Code.PrefixExpr(op, rewriteExpr(target, analysis, stackAllocIds)) + case Code.PostfixExpr(target, op) => + Code.PostfixExpr(rewriteExpr(target, analysis, stackAllocIds), op) + case Code.Bracket(target, item) => + Code.Bracket( + rewriteExpr(target, analysis, stackAllocIds), + rewriteExpr(item, analysis, stackAllocIds) + ) + case Code.Ternary(cond, whenTrue, whenFalse) => + Code.Ternary( + rewriteExpr(cond, analysis, stackAllocIds), + rewriteExpr(whenTrue, analysis, stackAllocIds), + rewriteExpr(whenFalse, analysis, stackAllocIds) + ) + case id @ Code.Ident(_) => id + case i @ Code.IntLiteral(_) => i + case s @ Code.StrLiteral(_) => s + } + + allocIdOf(analysis.allocIds, expr) match { + case Some(id) if stackAllocIds(id) => + analysis.allocById.get(id) match { + case Some(allocMeta) => + rewrittenChildren match { + case Code.Apply(_, args) => + allocMeta(args) + case _ => + rewrittenChildren + } + case None => + rewrittenChildren + } + case _ => + rewrittenChildren + } + } + + private def rewriteBlock( + block: Code.Block, + analysis: Analysis, + stackAllocIds: Set[Int] + ): Code.Block = + block.copy(items = block.items.map(rewriteStatement(_, analysis, stackAllocIds))) + + private def rewriteStatement( + stmt: Code.Statement, + analysis: Analysis, + stackAllocIds: Set[Int] + ): Code.Statement = + stmt match { + case Code.Assignment(target, value) => + Code.Assignment( + rewriteExpr(target, analysis, stackAllocIds), + rewriteExpr(value, analysis, stackAllocIds) + ) + case arr @ Code.DeclareArray(tpe, ident, values) => + val values1 = values match { + case Left(size) => Left(size) + case Right(exprs) => + Right(exprs.map(rewriteExpr(_, analysis, stackAllocIds))) + } + arr.copy(values = values1) + case decl @ Code.DeclareVar(attrs, tpe, ident, value) => + decl.copy( + attrs = attrs, + tpe = tpe, + ident = ident, + value = value.map(rewriteExpr(_, analysis, stackAllocIds)) + ) + case ret @ Code.Return(expr) => + ret.copy(expr = expr.map(rewriteExpr(_, analysis, stackAllocIds))) + case block @ Code.Block(items) => + rewriteBlock(block, analysis, stackAllocIds) + case stmts @ Code.Statements(items) => + stmts.copy( + items = items.map(rewriteStatement(_, analysis, stackAllocIds)) + ) + case ifElse @ Code.IfElse(ifs, elseCond) => + val ifs1 = ifs.map { case (cond, body) => + ( + rewriteExpr(cond, analysis, stackAllocIds), + rewriteBlock(body, analysis, stackAllocIds) + ) + } + val else1 = elseCond.map(rewriteBlock(_, analysis, stackAllocIds)) + ifElse.copy(ifs = ifs1, elseCond = else1) + case dw @ Code.DoWhile(block, cond) => + dw.copy( + block = rewriteBlock(block, analysis, stackAllocIds), + whileCond = rewriteExpr(cond, analysis, stackAllocIds) + ) + case wh @ Code.While(cond, body) => + wh.copy( + cond = rewriteExpr(cond, analysis, stackAllocIds), + body = rewriteBlock(body, analysis, stackAllocIds) + ) + case eff @ Code.Effect(expr) => + eff.copy(expr = rewriteExpr(expr, analysis, stackAllocIds)) + case stmt0 @ (_: Code.DeclareFn | _: Code.Include | _: Code.Typedef | + _: Code.DefineComplex) => + stmt0 + } + + def optimize(stmt: Code.Statement): Code.Statement = + stmt match { + case fn @ Code.DeclareFn(_, _, _, _, Some(_)) => + analyze(fn) match { + case Some((analysis, stackAllocIds)) if stackAllocIds.nonEmpty => + fn.copy( + value = fn.value.map(block => + rewriteBlock(block, analysis, stackAllocIds) + ) + ) + case _ => + fn + } + case _ => + stmt + } + } + def apply[S](src: S)(implicit CS: CompilationSource[S] ): ClangGen[CS.ScopeKey] = { diff --git a/core/src/test/scala/dev/bosatsu/codegen/clang/ClangGenTest.scala b/core/src/test/scala/dev/bosatsu/codegen/clang/ClangGenTest.scala index bc52a077e..00affbcac 100644 --- a/core/src/test/scala/dev/bosatsu/codegen/clang/ClangGenTest.scala +++ b/core/src/test/scala/dev/bosatsu/codegen/clang/ClangGenTest.scala @@ -460,6 +460,320 @@ main = is_one } } + test("single-constructor enum lowers as NewType in local match") { + TestUtils.checkPackageMap(""" +enum Nat: + Z + S(prev: Nat) + +# Boxed has one constructor with one field; this should lower as NewType. +enum Boxed: + Box(v: Nat) + +def local_unbox(x): + match Box(x): + case Box(v): v + +main = local_unbox +""") { pm => + val renderedE = Par.withEC { + ClangGen(pm).renderMain( + TestUtils.testPackage, + Identifier.Name("main"), + Code.Ident("run_main") + ) + } + renderedE match { + case Left(err) => + fail(err.toString) + case Right(doc) => + val rendered = doc.render(80) + assert( + "(?s)BValue ___bsts_g_.*_l_local__unbox\\(BValue __bsts_b_x0\\) \\{.*return __bsts_b_x0;".r + .findFirstIn(rendered) + .nonEmpty + ) + assert( + "(?s)BValue ___bsts_g_.*_l_local__unbox\\(BValue __bsts_b_x0\\) \\{.*alloc_enum1\\(".r + .findFirstIn(rendered) + .isEmpty + ) + assert( + "(?s)BValue ___bsts_g_.*_l_local__unbox\\(BValue __bsts_b_x0\\) \\{.*BSTS_STACK_ALLOC_ENUM\\d+\\(".r + .findFirstIn(rendered) + .isEmpty + ) + } + } + } + + test("single-constructor enum argument call lowers as NewType") { + TestUtils.checkPackageMap(""" +enum Nat: + Z + S(prev: Nat) + +# Boxed has one constructor with one field; this should lower as NewType. +enum Boxed: + Box(v: Nat) + +def apply_box(fn, x): + fn(Box(x)) + +main = apply_box +""") { pm => + val renderedE = Par.withEC { + ClangGen(pm).renderMain( + TestUtils.testPackage, + Identifier.Name("main"), + Code.Ident("run_main") + ) + } + renderedE match { + case Left(err) => + fail(err.toString) + case Right(doc) => + val rendered = doc.render(80) + assert( + "(?s)BValue ___bsts_g_.*_l_apply__box\\(BValue __bsts_b_fn0,\\s*BValue __bsts_b_x0\\) \\{.*call_fn1\\(__bsts_b_fn0,\\s*__bsts_b_x0\\)".r + .findFirstIn(rendered) + .nonEmpty + ) + assert( + "(?s)BValue ___bsts_g_.*_l_apply__box\\(BValue __bsts_b_fn0,\\s*BValue __bsts_b_x0\\) \\{.*alloc_enum1\\(".r + .findFirstIn(rendered) + .isEmpty + ) + assert( + "(?s)BValue ___bsts_g_.*_l_apply__box\\(BValue __bsts_b_fn0,\\s*BValue __bsts_b_x0\\) \\{.*BSTS_STACK_ALLOC_ENUM\\d+\\(".r + .findFirstIn(rendered) + .isEmpty + ) + } + } + } + + test("local struct constructor can be simplified away") { + TestUtils.checkPackageMap(""" +enum Nat: + Z + S(prev: Nat) + +struct Pair(a: Nat, b: Nat) + +def local_pair_sum(x): + match Pair(x, Z): + case Pair(a, _): a + +main = local_pair_sum +""") { pm => + val renderedE = Par.withEC { + ClangGen(pm).renderMain( + TestUtils.testPackage, + Identifier.Name("main"), + Code.Ident("run_main") + ) + } + renderedE match { + case Left(err) => + fail(err.toString) + case Right(doc) => + val rendered = doc.render(80) + assert( + "(?s)BValue ___bsts_g_.*_l_local__pair__sum\\(BValue __bsts_b_x0\\) \\{.*return __bsts_b_x0;".r + .findFirstIn(rendered) + .nonEmpty + ) + assert( + "(?s)BValue ___bsts_g_.*_l_local__pair__sum\\(BValue __bsts_b_x0\\) \\{.*alloc_struct2\\(".r + .findFirstIn(rendered) + .isEmpty + ) + assert( + "(?s)BValue ___bsts_g_.*_l_local__pair__sum\\(BValue __bsts_b_x0\\) \\{.*BSTS_STACK_ALLOC_STRUCT\\d+\\(".r + .findFirstIn(rendered) + .isEmpty + ) + } + } + } + + test("does not stack allocate returned struct constructor") { + TestUtils.checkPackageMap(""" +enum Nat: + Z + S(prev: Nat) + +struct Pair(a: Nat, b: Nat) + +def mk_pair(x): + Pair(x, Z) + +main = mk_pair +""") { pm => + val renderedE = Par.withEC { + ClangGen(pm).renderMain( + TestUtils.testPackage, + Identifier.Name("main"), + Code.Ident("run_main") + ) + } + renderedE match { + case Left(err) => + fail(err.toString) + case Right(doc) => + val rendered = doc.render(80) + assert( + "(?s)BValue ___bsts_g_.*_l_mk__pair\\(BValue __bsts_b_x0\\) \\{.*alloc_struct2\\(".r + .findFirstIn(rendered) + .nonEmpty + ) + assert( + "(?s)BValue ___bsts_g_.*_l_mk__pair\\(BValue __bsts_b_x0\\) \\{.*BSTS_STACK_ALLOC_STRUCT\\d+\\(".r + .findFirstIn(rendered) + .isEmpty + ) + } + } + } + + test("nested local newtype+struct chain can be simplified away") { + TestUtils.checkPackageMap(""" +enum Nat: + Z + S(prev: Nat) + +struct Pair(a: Nat, b: Nat) + +# Boxed has one constructor with one field; this should lower as NewType. +enum Boxed: + Box(v: Pair) + +def nested_local(x): + match Box(Pair(x, Z)): + case Box(Pair(a, _)): a + +main = nested_local +""") { pm => + val renderedE = Par.withEC { + ClangGen(pm).renderMain( + TestUtils.testPackage, + Identifier.Name("main"), + Code.Ident("run_main") + ) + } + renderedE match { + case Left(err) => + fail(err.toString) + case Right(doc) => + val rendered = doc.render(80) + assert( + "(?s)BValue ___bsts_g_.*_l_nested__local\\(BValue __bsts_b_x0\\) \\{[^}]*return __bsts_b_x0;".r + .findFirstIn(rendered) + .nonEmpty + ) + assert( + "(?s)BValue ___bsts_g_.*_l_nested__local\\(BValue __bsts_b_x0\\) \\{[^}]*BSTS_STACK_ALLOC_ENUM\\d+\\(".r + .findFirstIn(rendered) + .isEmpty + ) + assert( + "(?s)BValue ___bsts_g_.*_l_nested__local\\(BValue __bsts_b_x0\\) \\{[^}]*BSTS_STACK_ALLOC_STRUCT\\d+\\(".r + .findFirstIn(rendered) + .isEmpty + ) + } + } + } + + test("single-constructor enum escape keeps struct allocation without enum allocation") { + TestUtils.checkPackageMap(""" +enum Nat: + Z + S(prev: Nat) + +struct Pair(a: Nat, b: Nat) + +# Boxed has one constructor with one field; this should lower as NewType. +enum Boxed: + Box(v: Pair) + +def mk_box(x): + Box(Pair(x, Z)) + +main = mk_box +""") { pm => + val renderedE = Par.withEC { + ClangGen(pm).renderMain( + TestUtils.testPackage, + Identifier.Name("main"), + Code.Ident("run_main") + ) + } + renderedE match { + case Left(err) => + fail(err.toString) + case Right(doc) => + val rendered = doc.render(80) + assert( + "(?s)BValue ___bsts_g_.*_l_mk__box\\(BValue __bsts_b_x0\\) \\{[^}]*alloc_struct2\\(".r + .findFirstIn(rendered) + .nonEmpty + ) + assert( + "(?s)BValue ___bsts_g_.*_l_mk__box\\(BValue __bsts_b_x0\\) \\{[^}]*alloc_enum1\\(".r + .findFirstIn(rendered) + .isEmpty + ) + assert( + "(?s)BValue ___bsts_g_.*_l_mk__box\\(BValue __bsts_b_x0\\) \\{[^}]*BSTS_STACK_ALLOC_(STRUCT|ENUM)\\d+\\(".r + .findFirstIn(rendered) + .isEmpty + ) + } + } + } + + test("does not stack allocate constructor passed to non-whitelisted external call") { + TestUtils.checkPackageMap(""" +external def opaque_id[a](x: a) -> a +enum Nat: + Z + S(prev: Nat) + +struct Pair(a: Nat, b: Nat) + +def mk_pair_via_opaque(x): + opaque_id(Pair(x, Z)) + +main = mk_pair_via_opaque +""") { pm => + val renderedE = Par.withEC { + ClangGen(pm).renderMain( + TestUtils.testPackage, + Identifier.Name("main"), + Code.Ident("run_main") + ) + } + renderedE match { + case Left(err) => + fail(err.toString) + case Right(doc) => + val rendered = doc.render(80) + assert( + "(?s)BValue ___bsts_g_.*_l_mk__pair__via__opaque\\(BValue __bsts_b_x0\\) \\{[^}]*alloc_struct2\\(".r + .findFirstIn(rendered) + .nonEmpty + ) + assert( + "(?s)BValue ___bsts_g_.*_l_mk__pair__via__opaque\\(BValue __bsts_b_x0\\) \\{[^}]*BSTS_STACK_ALLOC_STRUCT\\d+\\(".r + .findFirstIn(rendered) + .isEmpty + ) + } + } + } + def mockCodePointFn(bytes: Array[Byte], offset: Int): Int = { def s(i: Int) = bytes(offset + i).toInt & 0xff