From 0c5ece34112a65b7c7d4bc847af271d02aae8362 Mon Sep 17 00:00:00 2001 From: "P. Oscar Boykin" Date: Sun, 17 Nov 2024 09:47:34 -1000 Subject: [PATCH] Generating C code from Matchless (#1253) * checkpoint * checkpoint more work * simplify * implement fns * checkpoint most of c tranlation implemented * fill in more impls * ignore compiled c code * work on string representation * implement moar * code complete * add a basic, failing, test * get some tests passing * Add foldLeft * add reverse_concat * optimize ifThenElseV --- .gitignore | 4 +- c_runtime/bosatsu_generated.h | 192 +---- c_runtime/bosatsu_runtime.c | 96 ++- c_runtime/bosatsu_runtime.h | 78 ++ c_runtime/typegen.py | 6 +- .../scala/org/bykn/bosatsu/Matchless.scala | 9 +- .../org/bykn/bosatsu/codegen/Idents.scala | 25 +- .../bykn/bosatsu/codegen/clang/ClangGen.scala | 814 ++++++++++++++++++ .../org/bykn/bosatsu/codegen/clang/Code.scala | 251 +++++- .../org/bykn/bosatsu/MatchlessTests.scala | 2 +- .../scala/org/bykn/bosatsu/TestUtils.scala | 4 +- .../org/bykn/bosatsu/codegen/IdentsTest.scala | 10 + .../bosatsu/codegen/clang/ClangGenTest.scala | 131 +++ 13 files changed, 1396 insertions(+), 226 deletions(-) create mode 100644 core/src/main/scala/org/bykn/bosatsu/codegen/clang/ClangGen.scala create mode 100644 core/src/test/scala/org/bykn/bosatsu/codegen/clang/ClangGenTest.scala diff --git a/.gitignore b/.gitignore index c877a8163..1f3cd8628 100644 --- a/.gitignore +++ b/.gitignore @@ -45,4 +45,6 @@ node_modules/ .bloop project/metals.sbt project/project/* -jsui/bosatsu_ui.js \ No newline at end of file +jsui/bosatsu_ui.js + +c_runtime/bosatsu_runtime.o \ No newline at end of file diff --git a/c_runtime/bosatsu_generated.h b/c_runtime/bosatsu_generated.h index d108565fe..58f6cca1b 100644 --- a/c_runtime/bosatsu_generated.h +++ b/c_runtime/bosatsu_generated.h @@ -2916,7 +2916,7 @@ BValue alloc_closure1(size_t size, BValue* data, BClosure1 fn) { } BValue call_fn1(BValue fn, BValue arg0) { - if (IS_STATIC_VALUE(fn)) { + if (IS_PURE_VALUE(fn)) { BPureFn1 pfn = (BPureFn1)TO_POINTER(fn); return pfn(arg0); } @@ -2928,10 +2928,6 @@ BValue call_fn1(BValue fn, BValue arg0) { } } -BValue value_from_pure_fn1(BPureFn1 fn) { - return (BValue)(((uintptr_t)fn) | STATIC_VALUE_TAG); -} - DEFINE_RC_STRUCT(Closure2Data, BClosure2 fn; size_t slot_len;); @@ -2949,7 +2945,7 @@ BValue alloc_closure2(size_t size, BValue* data, BClosure2 fn) { } BValue call_fn2(BValue fn, BValue arg0, BValue arg1) { - if (IS_STATIC_VALUE(fn)) { + if (IS_PURE_VALUE(fn)) { BPureFn2 pfn = (BPureFn2)TO_POINTER(fn); return pfn(arg0, arg1); } @@ -2961,10 +2957,6 @@ BValue call_fn2(BValue fn, BValue arg0, BValue arg1) { } } -BValue value_from_pure_fn2(BPureFn2 fn) { - return (BValue)(((uintptr_t)fn) | STATIC_VALUE_TAG); -} - DEFINE_RC_STRUCT(Closure3Data, BClosure3 fn; size_t slot_len;); @@ -2982,7 +2974,7 @@ BValue alloc_closure3(size_t size, BValue* data, BClosure3 fn) { } BValue call_fn3(BValue fn, BValue arg0, BValue arg1, BValue arg2) { - if (IS_STATIC_VALUE(fn)) { + if (IS_PURE_VALUE(fn)) { BPureFn3 pfn = (BPureFn3)TO_POINTER(fn); return pfn(arg0, arg1, arg2); } @@ -2994,10 +2986,6 @@ BValue call_fn3(BValue fn, BValue arg0, BValue arg1, BValue arg2) { } } -BValue value_from_pure_fn3(BPureFn3 fn) { - return (BValue)(((uintptr_t)fn) | STATIC_VALUE_TAG); -} - DEFINE_RC_STRUCT(Closure4Data, BClosure4 fn; size_t slot_len;); @@ -3015,7 +3003,7 @@ BValue alloc_closure4(size_t size, BValue* data, BClosure4 fn) { } BValue call_fn4(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3) { - if (IS_STATIC_VALUE(fn)) { + if (IS_PURE_VALUE(fn)) { BPureFn4 pfn = (BPureFn4)TO_POINTER(fn); return pfn(arg0, arg1, arg2, arg3); } @@ -3027,10 +3015,6 @@ BValue call_fn4(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3) { } } -BValue value_from_pure_fn4(BPureFn4 fn) { - return (BValue)(((uintptr_t)fn) | STATIC_VALUE_TAG); -} - DEFINE_RC_STRUCT(Closure5Data, BClosure5 fn; size_t slot_len;); @@ -3048,7 +3032,7 @@ BValue alloc_closure5(size_t size, BValue* data, BClosure5 fn) { } BValue call_fn5(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, BValue arg4) { - if (IS_STATIC_VALUE(fn)) { + if (IS_PURE_VALUE(fn)) { BPureFn5 pfn = (BPureFn5)TO_POINTER(fn); return pfn(arg0, arg1, arg2, arg3, arg4); } @@ -3060,10 +3044,6 @@ BValue call_fn5(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, B } } -BValue value_from_pure_fn5(BPureFn5 fn) { - return (BValue)(((uintptr_t)fn) | STATIC_VALUE_TAG); -} - DEFINE_RC_STRUCT(Closure6Data, BClosure6 fn; size_t slot_len;); @@ -3081,7 +3061,7 @@ BValue alloc_closure6(size_t size, BValue* data, BClosure6 fn) { } BValue call_fn6(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, BValue arg4, BValue arg5) { - if (IS_STATIC_VALUE(fn)) { + if (IS_PURE_VALUE(fn)) { BPureFn6 pfn = (BPureFn6)TO_POINTER(fn); return pfn(arg0, arg1, arg2, arg3, arg4, arg5); } @@ -3093,10 +3073,6 @@ BValue call_fn6(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, B } } -BValue value_from_pure_fn6(BPureFn6 fn) { - return (BValue)(((uintptr_t)fn) | STATIC_VALUE_TAG); -} - DEFINE_RC_STRUCT(Closure7Data, BClosure7 fn; size_t slot_len;); @@ -3114,7 +3090,7 @@ BValue alloc_closure7(size_t size, BValue* data, BClosure7 fn) { } BValue call_fn7(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, BValue arg4, BValue arg5, BValue arg6) { - if (IS_STATIC_VALUE(fn)) { + if (IS_PURE_VALUE(fn)) { BPureFn7 pfn = (BPureFn7)TO_POINTER(fn); return pfn(arg0, arg1, arg2, arg3, arg4, arg5, arg6); } @@ -3126,10 +3102,6 @@ BValue call_fn7(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, B } } -BValue value_from_pure_fn7(BPureFn7 fn) { - return (BValue)(((uintptr_t)fn) | STATIC_VALUE_TAG); -} - DEFINE_RC_STRUCT(Closure8Data, BClosure8 fn; size_t slot_len;); @@ -3147,7 +3119,7 @@ BValue alloc_closure8(size_t size, BValue* data, BClosure8 fn) { } BValue call_fn8(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, BValue arg4, BValue arg5, BValue arg6, BValue arg7) { - if (IS_STATIC_VALUE(fn)) { + if (IS_PURE_VALUE(fn)) { BPureFn8 pfn = (BPureFn8)TO_POINTER(fn); return pfn(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7); } @@ -3159,10 +3131,6 @@ BValue call_fn8(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, B } } -BValue value_from_pure_fn8(BPureFn8 fn) { - return (BValue)(((uintptr_t)fn) | STATIC_VALUE_TAG); -} - DEFINE_RC_STRUCT(Closure9Data, BClosure9 fn; size_t slot_len;); @@ -3180,7 +3148,7 @@ BValue alloc_closure9(size_t size, BValue* data, BClosure9 fn) { } BValue call_fn9(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, BValue arg4, BValue arg5, BValue arg6, BValue arg7, BValue arg8) { - if (IS_STATIC_VALUE(fn)) { + if (IS_PURE_VALUE(fn)) { BPureFn9 pfn = (BPureFn9)TO_POINTER(fn); return pfn(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); } @@ -3192,10 +3160,6 @@ BValue call_fn9(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, B } } -BValue value_from_pure_fn9(BPureFn9 fn) { - return (BValue)(((uintptr_t)fn) | STATIC_VALUE_TAG); -} - DEFINE_RC_STRUCT(Closure10Data, BClosure10 fn; size_t slot_len;); @@ -3213,7 +3177,7 @@ BValue alloc_closure10(size_t size, BValue* data, BClosure10 fn) { } BValue call_fn10(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, BValue arg4, BValue arg5, BValue arg6, BValue arg7, BValue arg8, BValue arg9) { - if (IS_STATIC_VALUE(fn)) { + if (IS_PURE_VALUE(fn)) { BPureFn10 pfn = (BPureFn10)TO_POINTER(fn); return pfn(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); } @@ -3225,10 +3189,6 @@ BValue call_fn10(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, } } -BValue value_from_pure_fn10(BPureFn10 fn) { - return (BValue)(((uintptr_t)fn) | STATIC_VALUE_TAG); -} - DEFINE_RC_STRUCT(Closure11Data, BClosure11 fn; size_t slot_len;); @@ -3246,7 +3206,7 @@ BValue alloc_closure11(size_t size, BValue* data, BClosure11 fn) { } BValue call_fn11(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, BValue arg4, BValue arg5, BValue arg6, BValue arg7, BValue arg8, BValue arg9, BValue arg10) { - if (IS_STATIC_VALUE(fn)) { + if (IS_PURE_VALUE(fn)) { BPureFn11 pfn = (BPureFn11)TO_POINTER(fn); return pfn(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10); } @@ -3258,10 +3218,6 @@ BValue call_fn11(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, } } -BValue value_from_pure_fn11(BPureFn11 fn) { - return (BValue)(((uintptr_t)fn) | STATIC_VALUE_TAG); -} - DEFINE_RC_STRUCT(Closure12Data, BClosure12 fn; size_t slot_len;); @@ -3279,7 +3235,7 @@ BValue alloc_closure12(size_t size, BValue* data, BClosure12 fn) { } BValue call_fn12(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, BValue arg4, BValue arg5, BValue arg6, BValue arg7, BValue arg8, BValue arg9, BValue arg10, BValue arg11) { - if (IS_STATIC_VALUE(fn)) { + if (IS_PURE_VALUE(fn)) { BPureFn12 pfn = (BPureFn12)TO_POINTER(fn); return pfn(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11); } @@ -3291,10 +3247,6 @@ BValue call_fn12(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, } } -BValue value_from_pure_fn12(BPureFn12 fn) { - return (BValue)(((uintptr_t)fn) | STATIC_VALUE_TAG); -} - DEFINE_RC_STRUCT(Closure13Data, BClosure13 fn; size_t slot_len;); @@ -3312,7 +3264,7 @@ BValue alloc_closure13(size_t size, BValue* data, BClosure13 fn) { } BValue call_fn13(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, BValue arg4, BValue arg5, BValue arg6, BValue arg7, BValue arg8, BValue arg9, BValue arg10, BValue arg11, BValue arg12) { - if (IS_STATIC_VALUE(fn)) { + if (IS_PURE_VALUE(fn)) { BPureFn13 pfn = (BPureFn13)TO_POINTER(fn); return pfn(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12); } @@ -3324,10 +3276,6 @@ BValue call_fn13(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, } } -BValue value_from_pure_fn13(BPureFn13 fn) { - return (BValue)(((uintptr_t)fn) | STATIC_VALUE_TAG); -} - DEFINE_RC_STRUCT(Closure14Data, BClosure14 fn; size_t slot_len;); @@ -3345,7 +3293,7 @@ BValue alloc_closure14(size_t size, BValue* data, BClosure14 fn) { } BValue call_fn14(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, BValue arg4, BValue arg5, BValue arg6, BValue arg7, BValue arg8, BValue arg9, BValue arg10, BValue arg11, BValue arg12, BValue arg13) { - if (IS_STATIC_VALUE(fn)) { + if (IS_PURE_VALUE(fn)) { BPureFn14 pfn = (BPureFn14)TO_POINTER(fn); return pfn(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13); } @@ -3357,10 +3305,6 @@ BValue call_fn14(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, } } -BValue value_from_pure_fn14(BPureFn14 fn) { - return (BValue)(((uintptr_t)fn) | STATIC_VALUE_TAG); -} - DEFINE_RC_STRUCT(Closure15Data, BClosure15 fn; size_t slot_len;); @@ -3378,7 +3322,7 @@ BValue alloc_closure15(size_t size, BValue* data, BClosure15 fn) { } BValue call_fn15(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, BValue arg4, BValue arg5, BValue arg6, BValue arg7, BValue arg8, BValue arg9, BValue arg10, BValue arg11, BValue arg12, BValue arg13, BValue arg14) { - if (IS_STATIC_VALUE(fn)) { + if (IS_PURE_VALUE(fn)) { BPureFn15 pfn = (BPureFn15)TO_POINTER(fn); return pfn(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14); } @@ -3390,10 +3334,6 @@ BValue call_fn15(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, } } -BValue value_from_pure_fn15(BPureFn15 fn) { - return (BValue)(((uintptr_t)fn) | STATIC_VALUE_TAG); -} - DEFINE_RC_STRUCT(Closure16Data, BClosure16 fn; size_t slot_len;); @@ -3411,7 +3351,7 @@ BValue alloc_closure16(size_t size, BValue* data, BClosure16 fn) { } BValue call_fn16(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, BValue arg4, BValue arg5, BValue arg6, BValue arg7, BValue arg8, BValue arg9, BValue arg10, BValue arg11, BValue arg12, BValue arg13, BValue arg14, BValue arg15) { - if (IS_STATIC_VALUE(fn)) { + if (IS_PURE_VALUE(fn)) { BPureFn16 pfn = (BPureFn16)TO_POINTER(fn); return pfn(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15); } @@ -3423,10 +3363,6 @@ BValue call_fn16(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, } } -BValue value_from_pure_fn16(BPureFn16 fn) { - return (BValue)(((uintptr_t)fn) | STATIC_VALUE_TAG); -} - DEFINE_RC_STRUCT(Closure17Data, BClosure17 fn; size_t slot_len;); @@ -3444,7 +3380,7 @@ BValue alloc_closure17(size_t size, BValue* data, BClosure17 fn) { } BValue call_fn17(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, BValue arg4, BValue arg5, BValue arg6, BValue arg7, BValue arg8, BValue arg9, BValue arg10, BValue arg11, BValue arg12, BValue arg13, BValue arg14, BValue arg15, BValue arg16) { - if (IS_STATIC_VALUE(fn)) { + if (IS_PURE_VALUE(fn)) { BPureFn17 pfn = (BPureFn17)TO_POINTER(fn); return pfn(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16); } @@ -3456,10 +3392,6 @@ BValue call_fn17(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, } } -BValue value_from_pure_fn17(BPureFn17 fn) { - return (BValue)(((uintptr_t)fn) | STATIC_VALUE_TAG); -} - DEFINE_RC_STRUCT(Closure18Data, BClosure18 fn; size_t slot_len;); @@ -3477,7 +3409,7 @@ BValue alloc_closure18(size_t size, BValue* data, BClosure18 fn) { } BValue call_fn18(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, BValue arg4, BValue arg5, BValue arg6, BValue arg7, BValue arg8, BValue arg9, BValue arg10, BValue arg11, BValue arg12, BValue arg13, BValue arg14, BValue arg15, BValue arg16, BValue arg17) { - if (IS_STATIC_VALUE(fn)) { + if (IS_PURE_VALUE(fn)) { BPureFn18 pfn = (BPureFn18)TO_POINTER(fn); return pfn(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17); } @@ -3489,10 +3421,6 @@ BValue call_fn18(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, } } -BValue value_from_pure_fn18(BPureFn18 fn) { - return (BValue)(((uintptr_t)fn) | STATIC_VALUE_TAG); -} - DEFINE_RC_STRUCT(Closure19Data, BClosure19 fn; size_t slot_len;); @@ -3510,7 +3438,7 @@ BValue alloc_closure19(size_t size, BValue* data, BClosure19 fn) { } BValue call_fn19(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, BValue arg4, BValue arg5, BValue arg6, BValue arg7, BValue arg8, BValue arg9, BValue arg10, BValue arg11, BValue arg12, BValue arg13, BValue arg14, BValue arg15, BValue arg16, BValue arg17, BValue arg18) { - if (IS_STATIC_VALUE(fn)) { + if (IS_PURE_VALUE(fn)) { BPureFn19 pfn = (BPureFn19)TO_POINTER(fn); return pfn(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18); } @@ -3522,10 +3450,6 @@ BValue call_fn19(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, } } -BValue value_from_pure_fn19(BPureFn19 fn) { - return (BValue)(((uintptr_t)fn) | STATIC_VALUE_TAG); -} - DEFINE_RC_STRUCT(Closure20Data, BClosure20 fn; size_t slot_len;); @@ -3543,7 +3467,7 @@ BValue alloc_closure20(size_t size, BValue* data, BClosure20 fn) { } BValue call_fn20(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, BValue arg4, BValue arg5, BValue arg6, BValue arg7, BValue arg8, BValue arg9, BValue arg10, BValue arg11, BValue arg12, BValue arg13, BValue arg14, BValue arg15, BValue arg16, BValue arg17, BValue arg18, BValue arg19) { - if (IS_STATIC_VALUE(fn)) { + if (IS_PURE_VALUE(fn)) { BPureFn20 pfn = (BPureFn20)TO_POINTER(fn); return pfn(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19); } @@ -3555,10 +3479,6 @@ BValue call_fn20(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, } } -BValue value_from_pure_fn20(BPureFn20 fn) { - return (BValue)(((uintptr_t)fn) | STATIC_VALUE_TAG); -} - DEFINE_RC_STRUCT(Closure21Data, BClosure21 fn; size_t slot_len;); @@ -3576,7 +3496,7 @@ BValue alloc_closure21(size_t size, BValue* data, BClosure21 fn) { } BValue call_fn21(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, BValue arg4, BValue arg5, BValue arg6, BValue arg7, BValue arg8, BValue arg9, BValue arg10, BValue arg11, BValue arg12, BValue arg13, BValue arg14, BValue arg15, BValue arg16, BValue arg17, BValue arg18, BValue arg19, BValue arg20) { - if (IS_STATIC_VALUE(fn)) { + if (IS_PURE_VALUE(fn)) { BPureFn21 pfn = (BPureFn21)TO_POINTER(fn); return pfn(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19, arg20); } @@ -3588,10 +3508,6 @@ BValue call_fn21(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, } } -BValue value_from_pure_fn21(BPureFn21 fn) { - return (BValue)(((uintptr_t)fn) | STATIC_VALUE_TAG); -} - DEFINE_RC_STRUCT(Closure22Data, BClosure22 fn; size_t slot_len;); @@ -3609,7 +3525,7 @@ BValue alloc_closure22(size_t size, BValue* data, BClosure22 fn) { } BValue call_fn22(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, BValue arg4, BValue arg5, BValue arg6, BValue arg7, BValue arg8, BValue arg9, BValue arg10, BValue arg11, BValue arg12, BValue arg13, BValue arg14, BValue arg15, BValue arg16, BValue arg17, BValue arg18, BValue arg19, BValue arg20, BValue arg21) { - if (IS_STATIC_VALUE(fn)) { + if (IS_PURE_VALUE(fn)) { BPureFn22 pfn = (BPureFn22)TO_POINTER(fn); return pfn(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19, arg20, arg21); } @@ -3621,10 +3537,6 @@ BValue call_fn22(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, } } -BValue value_from_pure_fn22(BPureFn22 fn) { - return (BValue)(((uintptr_t)fn) | STATIC_VALUE_TAG); -} - DEFINE_RC_STRUCT(Closure23Data, BClosure23 fn; size_t slot_len;); @@ -3642,7 +3554,7 @@ BValue alloc_closure23(size_t size, BValue* data, BClosure23 fn) { } BValue call_fn23(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, BValue arg4, BValue arg5, BValue arg6, BValue arg7, BValue arg8, BValue arg9, BValue arg10, BValue arg11, BValue arg12, BValue arg13, BValue arg14, BValue arg15, BValue arg16, BValue arg17, BValue arg18, BValue arg19, BValue arg20, BValue arg21, BValue arg22) { - if (IS_STATIC_VALUE(fn)) { + if (IS_PURE_VALUE(fn)) { BPureFn23 pfn = (BPureFn23)TO_POINTER(fn); return pfn(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19, arg20, arg21, arg22); } @@ -3654,10 +3566,6 @@ BValue call_fn23(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, } } -BValue value_from_pure_fn23(BPureFn23 fn) { - return (BValue)(((uintptr_t)fn) | STATIC_VALUE_TAG); -} - DEFINE_RC_STRUCT(Closure24Data, BClosure24 fn; size_t slot_len;); @@ -3675,7 +3583,7 @@ BValue alloc_closure24(size_t size, BValue* data, BClosure24 fn) { } BValue call_fn24(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, BValue arg4, BValue arg5, BValue arg6, BValue arg7, BValue arg8, BValue arg9, BValue arg10, BValue arg11, BValue arg12, BValue arg13, BValue arg14, BValue arg15, BValue arg16, BValue arg17, BValue arg18, BValue arg19, BValue arg20, BValue arg21, BValue arg22, BValue arg23) { - if (IS_STATIC_VALUE(fn)) { + if (IS_PURE_VALUE(fn)) { BPureFn24 pfn = (BPureFn24)TO_POINTER(fn); return pfn(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19, arg20, arg21, arg22, arg23); } @@ -3687,10 +3595,6 @@ BValue call_fn24(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, } } -BValue value_from_pure_fn24(BPureFn24 fn) { - return (BValue)(((uintptr_t)fn) | STATIC_VALUE_TAG); -} - DEFINE_RC_STRUCT(Closure25Data, BClosure25 fn; size_t slot_len;); @@ -3708,7 +3612,7 @@ BValue alloc_closure25(size_t size, BValue* data, BClosure25 fn) { } BValue call_fn25(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, BValue arg4, BValue arg5, BValue arg6, BValue arg7, BValue arg8, BValue arg9, BValue arg10, BValue arg11, BValue arg12, BValue arg13, BValue arg14, BValue arg15, BValue arg16, BValue arg17, BValue arg18, BValue arg19, BValue arg20, BValue arg21, BValue arg22, BValue arg23, BValue arg24) { - if (IS_STATIC_VALUE(fn)) { + if (IS_PURE_VALUE(fn)) { BPureFn25 pfn = (BPureFn25)TO_POINTER(fn); return pfn(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19, arg20, arg21, arg22, arg23, arg24); } @@ -3720,10 +3624,6 @@ BValue call_fn25(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, } } -BValue value_from_pure_fn25(BPureFn25 fn) { - return (BValue)(((uintptr_t)fn) | STATIC_VALUE_TAG); -} - DEFINE_RC_STRUCT(Closure26Data, BClosure26 fn; size_t slot_len;); @@ -3741,7 +3641,7 @@ BValue alloc_closure26(size_t size, BValue* data, BClosure26 fn) { } BValue call_fn26(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, BValue arg4, BValue arg5, BValue arg6, BValue arg7, BValue arg8, BValue arg9, BValue arg10, BValue arg11, BValue arg12, BValue arg13, BValue arg14, BValue arg15, BValue arg16, BValue arg17, BValue arg18, BValue arg19, BValue arg20, BValue arg21, BValue arg22, BValue arg23, BValue arg24, BValue arg25) { - if (IS_STATIC_VALUE(fn)) { + if (IS_PURE_VALUE(fn)) { BPureFn26 pfn = (BPureFn26)TO_POINTER(fn); return pfn(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19, arg20, arg21, arg22, arg23, arg24, arg25); } @@ -3753,10 +3653,6 @@ BValue call_fn26(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, } } -BValue value_from_pure_fn26(BPureFn26 fn) { - return (BValue)(((uintptr_t)fn) | STATIC_VALUE_TAG); -} - DEFINE_RC_STRUCT(Closure27Data, BClosure27 fn; size_t slot_len;); @@ -3774,7 +3670,7 @@ BValue alloc_closure27(size_t size, BValue* data, BClosure27 fn) { } BValue call_fn27(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, BValue arg4, BValue arg5, BValue arg6, BValue arg7, BValue arg8, BValue arg9, BValue arg10, BValue arg11, BValue arg12, BValue arg13, BValue arg14, BValue arg15, BValue arg16, BValue arg17, BValue arg18, BValue arg19, BValue arg20, BValue arg21, BValue arg22, BValue arg23, BValue arg24, BValue arg25, BValue arg26) { - if (IS_STATIC_VALUE(fn)) { + if (IS_PURE_VALUE(fn)) { BPureFn27 pfn = (BPureFn27)TO_POINTER(fn); return pfn(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19, arg20, arg21, arg22, arg23, arg24, arg25, arg26); } @@ -3786,10 +3682,6 @@ BValue call_fn27(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, } } -BValue value_from_pure_fn27(BPureFn27 fn) { - return (BValue)(((uintptr_t)fn) | STATIC_VALUE_TAG); -} - DEFINE_RC_STRUCT(Closure28Data, BClosure28 fn; size_t slot_len;); @@ -3807,7 +3699,7 @@ BValue alloc_closure28(size_t size, BValue* data, BClosure28 fn) { } BValue call_fn28(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, BValue arg4, BValue arg5, BValue arg6, BValue arg7, BValue arg8, BValue arg9, BValue arg10, BValue arg11, BValue arg12, BValue arg13, BValue arg14, BValue arg15, BValue arg16, BValue arg17, BValue arg18, BValue arg19, BValue arg20, BValue arg21, BValue arg22, BValue arg23, BValue arg24, BValue arg25, BValue arg26, BValue arg27) { - if (IS_STATIC_VALUE(fn)) { + if (IS_PURE_VALUE(fn)) { BPureFn28 pfn = (BPureFn28)TO_POINTER(fn); return pfn(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19, arg20, arg21, arg22, arg23, arg24, arg25, arg26, arg27); } @@ -3819,10 +3711,6 @@ BValue call_fn28(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, } } -BValue value_from_pure_fn28(BPureFn28 fn) { - return (BValue)(((uintptr_t)fn) | STATIC_VALUE_TAG); -} - DEFINE_RC_STRUCT(Closure29Data, BClosure29 fn; size_t slot_len;); @@ -3840,7 +3728,7 @@ BValue alloc_closure29(size_t size, BValue* data, BClosure29 fn) { } BValue call_fn29(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, BValue arg4, BValue arg5, BValue arg6, BValue arg7, BValue arg8, BValue arg9, BValue arg10, BValue arg11, BValue arg12, BValue arg13, BValue arg14, BValue arg15, BValue arg16, BValue arg17, BValue arg18, BValue arg19, BValue arg20, BValue arg21, BValue arg22, BValue arg23, BValue arg24, BValue arg25, BValue arg26, BValue arg27, BValue arg28) { - if (IS_STATIC_VALUE(fn)) { + if (IS_PURE_VALUE(fn)) { BPureFn29 pfn = (BPureFn29)TO_POINTER(fn); return pfn(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19, arg20, arg21, arg22, arg23, arg24, arg25, arg26, arg27, arg28); } @@ -3852,10 +3740,6 @@ BValue call_fn29(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, } } -BValue value_from_pure_fn29(BPureFn29 fn) { - return (BValue)(((uintptr_t)fn) | STATIC_VALUE_TAG); -} - DEFINE_RC_STRUCT(Closure30Data, BClosure30 fn; size_t slot_len;); @@ -3873,7 +3757,7 @@ BValue alloc_closure30(size_t size, BValue* data, BClosure30 fn) { } BValue call_fn30(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, BValue arg4, BValue arg5, BValue arg6, BValue arg7, BValue arg8, BValue arg9, BValue arg10, BValue arg11, BValue arg12, BValue arg13, BValue arg14, BValue arg15, BValue arg16, BValue arg17, BValue arg18, BValue arg19, BValue arg20, BValue arg21, BValue arg22, BValue arg23, BValue arg24, BValue arg25, BValue arg26, BValue arg27, BValue arg28, BValue arg29) { - if (IS_STATIC_VALUE(fn)) { + if (IS_PURE_VALUE(fn)) { BPureFn30 pfn = (BPureFn30)TO_POINTER(fn); return pfn(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19, arg20, arg21, arg22, arg23, arg24, arg25, arg26, arg27, arg28, arg29); } @@ -3885,10 +3769,6 @@ BValue call_fn30(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, } } -BValue value_from_pure_fn30(BPureFn30 fn) { - return (BValue)(((uintptr_t)fn) | STATIC_VALUE_TAG); -} - DEFINE_RC_STRUCT(Closure31Data, BClosure31 fn; size_t slot_len;); @@ -3906,7 +3786,7 @@ BValue alloc_closure31(size_t size, BValue* data, BClosure31 fn) { } BValue call_fn31(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, BValue arg4, BValue arg5, BValue arg6, BValue arg7, BValue arg8, BValue arg9, BValue arg10, BValue arg11, BValue arg12, BValue arg13, BValue arg14, BValue arg15, BValue arg16, BValue arg17, BValue arg18, BValue arg19, BValue arg20, BValue arg21, BValue arg22, BValue arg23, BValue arg24, BValue arg25, BValue arg26, BValue arg27, BValue arg28, BValue arg29, BValue arg30) { - if (IS_STATIC_VALUE(fn)) { + if (IS_PURE_VALUE(fn)) { BPureFn31 pfn = (BPureFn31)TO_POINTER(fn); return pfn(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19, arg20, arg21, arg22, arg23, arg24, arg25, arg26, arg27, arg28, arg29, arg30); } @@ -3918,10 +3798,6 @@ BValue call_fn31(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, } } -BValue value_from_pure_fn31(BPureFn31 fn) { - return (BValue)(((uintptr_t)fn) | STATIC_VALUE_TAG); -} - DEFINE_RC_STRUCT(Closure32Data, BClosure32 fn; size_t slot_len;); @@ -3939,7 +3815,7 @@ BValue alloc_closure32(size_t size, BValue* data, BClosure32 fn) { } BValue call_fn32(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, BValue arg4, BValue arg5, BValue arg6, BValue arg7, BValue arg8, BValue arg9, BValue arg10, BValue arg11, BValue arg12, BValue arg13, BValue arg14, BValue arg15, BValue arg16, BValue arg17, BValue arg18, BValue arg19, BValue arg20, BValue arg21, BValue arg22, BValue arg23, BValue arg24, BValue arg25, BValue arg26, BValue arg27, BValue arg28, BValue arg29, BValue arg30, BValue arg31) { - if (IS_STATIC_VALUE(fn)) { + if (IS_PURE_VALUE(fn)) { BPureFn32 pfn = (BPureFn32)TO_POINTER(fn); return pfn(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19, arg20, arg21, arg22, arg23, arg24, arg25, arg26, arg27, arg28, arg29, arg30, arg31); } @@ -3951,7 +3827,3 @@ BValue call_fn32(BValue fn, BValue arg0, BValue arg1, BValue arg2, BValue arg3, } } -BValue value_from_pure_fn32(BPureFn32 fn) { - return (BValue)(((uintptr_t)fn) | STATIC_VALUE_TAG); -} - diff --git a/c_runtime/bosatsu_runtime.c b/c_runtime/bosatsu_runtime.c index a15c0a452..3b2db2913 100644 --- a/c_runtime/bosatsu_runtime.c +++ b/c_runtime/bosatsu_runtime.c @@ -3,50 +3,7 @@ #include #include -/* -There are a few kinds of values: - -1. pure values: small ints, characters, small strings that can fit into 63 bits. -2. pointers to referenced counted values -3. pointers to static values stack allocated at startup - -to distinguish these cases we allocate pointers such that they are aligned to at least 4 byte -boundaries: - a. ends with 1: pure value - b. ends with 10: static pointer (allocated once and deleteds at the end of the world) - c. ends with 00: refcount pointer. - -We need to know which case we are in because in generic context we need to know -how to clone values. -*/ -#define TAG_MASK 0x3 -#define PURE_VALUE_TAG 0x1 -#define STATIC_VALUE_TAG 0x3 -#define POINTER_TAG 0x0 - -// Utility macros to check the tag of a value -#define IS_PURE_VALUE(ptr) (((uintptr_t)(ptr) & PURE_VALUE_TAG) == PURE_VALUE_TAG) -#define PURE_VALUE(ptr) ((uintptr_t)(ptr) >> 1) -#define IS_STATIC_VALUE(ptr) (((uintptr_t)(ptr) & TAG_MASK) == STATIC_VALUE_TAG) -#define IS_POINTER(ptr) (((uintptr_t)(ptr) & TAG_MASK) == POINTER_TAG) -#define TO_POINTER(ptr) ((uintptr_t)(ptr) & ~TAG_MASK) - -#define DEFINE_RC_STRUCT(name, fields) \ - struct name { \ - atomic_int ref_count; \ - FreeFn free; \ - fields \ - }; \ - typedef struct name name - -#define DEFINE_RC_ENUM(name, fields) \ - struct name { \ - atomic_int ref_count; \ - FreeFn free; \ - ENUM_TAG tag; \ - fields \ - }; \ - typedef struct name name +#define DEFINE_RC_ENUM(name, fields) DEFINE_RC_STRUCT(name, ENUM_TAG tag; fields) DEFINE_RC_STRUCT(RefCounted,); @@ -77,6 +34,9 @@ void free_closure(Closure1Data* s) { DEFINE_RC_ENUM(Enum0,); DEFINE_RC_STRUCT(External, void* external; FreeFn ex_free;); + +DEFINE_RC_STRUCT(BSTS_String, size_t len; char* bytes;); + // A general structure for a reference counted memory block // it is always allocated with len BValue array immediately after typedef struct _Node { @@ -177,6 +137,27 @@ void* get_external(BValue v) { return rc->external; } +void free_string(void* str) { + BSTS_String* casted = (BSTS_String*)str; + free(casted->bytes); + free(str); +} + +// this copies the bytes in, it does not take ownership +BValue bsts_string_from_utf8_bytes_copy(size_t len, char* bytes) { + BSTS_String* str = malloc(sizeof(BSTS_String)); + char* bytes_copy = malloc(sizeof(char) * len); + for(size_t i = 0; i < len; i++) { + bytes_copy[i] = bytes[i]; + } + str->len = len; + str->bytes = bytes_copy; + atomic_init(&str->ref_count, 1); + str->free = (FreeFn)free_string; + + return (BValue)str; +} + // Function to determine the type of the given value pointer and clone if necessary BValue clone_value(BValue value) { if (IS_POINTER(value)) { @@ -227,11 +208,34 @@ BValue make_static(BValue v) { return v; } +BValue read_or_build(_Atomic BValue* target, BConstruct cons) { + BValue result = atomic_load(target); + if (result == NULL) { + result = cons(); + BValue static_version = make_static(result); + BValue expected = NULL; + do { + if (atomic_compare_exchange_weak(target, &expected, static_version)) { + free_on_close(result); + break; + } else { + expected = atomic_load(target); + if (expected != NULL) { + release_value(result); + result = expected; + break; + } + } + } while (1); + } + return result; +} + // Example static BValue make_foo(); static _Atomic BValue __bvalue_foo = NULL; // Add this to the main function to construct all // the top level values before we start BValue foo() { - return CONSTRUCT(&__bvalue_foo, make_foo); -} \ No newline at end of file + return read_or_build(&__bvalue_foo, make_foo); +} diff --git a/c_runtime/bosatsu_runtime.h b/c_runtime/bosatsu_runtime.h index efb486d1f..59bde9fdd 100644 --- a/c_runtime/bosatsu_runtime.h +++ b/c_runtime/bosatsu_runtime.h @@ -4,10 +4,77 @@ #include #include +/* +There are a few kinds of values: + +1. pure values: small ints, characters, small strings that can fit into 63 bits. +2. pointers to referenced counted values +3. pointers to static values stack allocated at startup + +to distinguish these cases we allocate pointers such that they are aligned to at least 4 byte +boundaries: + a. ends with 01: pure value + b. ends with 11: static pointer (allocated once and deleteds at the end of the world) + c. ends with 00: refcount pointer. + +when it comes to functions there are three types: + a. top level pure function: ends with 1 + b. static closure (something that closes over static things, ideally we would optimize this away): ends with 10 + c. refcounted closure: ends with 00 + +Nat-like values are represented by positive integers encoded as PURE_VALUE such that +NAT(x) = (x << 1) | 1, since we don't have enough time to increment through 2^{63} values +this is a safe encoding. + +Char values are stored as unicode code points with a trailing 1. + +String values encodings, string values are like ref-counted structs with +a length and char* holding the utf-8 bytes. We could also potentially optimize +short strings by packing them literally into 63 bits with a length. + +Integer values are either pure values (signed values packed into 63 bits), +or ref-counted big integers + +We need to know which case we are in because in generic context we need to know +how to clone values. +*/ +#define TAG_MASK 0x3 +#define PURE_VALUE_TAG 0x1 +#define STATIC_VALUE_TAG 0x3 +#define POINTER_TAG 0x0 + +// Utility macros to check the tag of a value +#define IS_PURE_VALUE(ptr) (((uintptr_t)(ptr) & PURE_VALUE_TAG) == PURE_VALUE_TAG) +#define PURE_VALUE(ptr) ((uintptr_t)(ptr) >> 1) +#define IS_STATIC_VALUE(ptr) (((uintptr_t)(ptr) & TAG_MASK) == STATIC_VALUE_TAG) +#define IS_POINTER(ptr) (((uintptr_t)(ptr) & TAG_MASK) == POINTER_TAG) +#define TO_POINTER(ptr) ((uintptr_t)(ptr) & ~TAG_MASK) +#define STATIC_PUREFN(ptr) (BValue*)((uintptr_t)(ptr) | PURE_VALUE_TAG) + +#define DEFINE_RC_STRUCT(name, fields) \ + struct name { \ + atomic_int ref_count; \ + FreeFn free; \ + fields \ + }; \ + typedef struct name name + typedef void* BValue; typedef uint32_t ENUM_TAG; #include "bosatsu_decls_generated.h" +// Nat values are encoded in integers +#define BSTS_NAT_0 ((BValue)0x1) +#define BSTS_NAT_SUCC(n) ((BValue)((uintptr_t)(n) + 2)) +#define BSTS_NAT_PREV(n) ((BValue)((uintptr_t)(n) - 2)) +#define BSTS_NAT_IS_0(n) (((uintptr_t)(n)) == 0x1) +#define BSTS_NAT_GT_0(n) (((uintptr_t)(n)) != 0x1) + +#define BSTS_AND(x, y) ((x) && (y)) + +#define BSTS_TO_CHAR(x) (BValue)((x << 1) | 1) +#define BSTS_NULL_TERM_STATIC_STR(x) (BValue)(((uintptr_t)(x)) | PURE_VALUE_TAG) + // this is the free function to call on an external value typedef void (*FreeFn)(void*); // A function which constructs a BValue @@ -21,6 +88,15 @@ BValue get_struct_index(BValue v, int idx); ENUM_TAG get_variant(BValue v); BValue get_enum_index(BValue v, int idx); +// This one is not auto generated because it can always be fit into the BValue directly +BValue alloc_enum0(ENUM_TAG tag); + +BValue bsts_string_from_utf8_bytes_copy(size_t len, char* bytes); +_Bool bsts_equals_string(BValue left, BValue right); + +BValue bsts_integer_from_int(int small_int); +BValue bsts_integer_from_words_copy(_Bool is_pos, size_t size, int32_t* words); +_Bool bsts_equals_int(BValue left, BValue right); BValue alloc_external(void* eval, FreeFn free_fn); void* get_external(BValue v); @@ -36,6 +112,8 @@ void free_statics(); BValue make_static(BValue v); void free_on_close(BValue v); +BValue read_or_build(_Atomic BValue* v, BConstruct cons); + #define CONSTRUCT(target, cons) (\ {\ BValue result = atomic_load(target);\ diff --git a/c_runtime/typegen.py b/c_runtime/typegen.py index cdca044ce..b3f56ab52 100644 --- a/c_runtime/typegen.py +++ b/c_runtime/typegen.py @@ -75,7 +75,7 @@ def function_impl(size): }} BValue call_fn{size}(BValue fn, {arg_params}) {{ - if (IS_STATIC_VALUE(fn)) {{ + if (IS_PURE_VALUE(fn)) {{ BPureFn{size} pfn = (BPureFn{size})TO_POINTER(fn); return pfn({just_args}); }} @@ -85,10 +85,6 @@ def function_impl(size): BValue* data = closure_data_of({cast_to_1}rc); return rc->fn(data, {just_args}); }} -}} - -BValue value_from_pure_fn{size}(BPureFn{size} fn) {{ - return (BValue)(((uintptr_t)fn) | STATIC_VALUE_TAG); }}""" cast_to_1 = "" if size == 1 else "(Closure1Data*)" arg_params = ", ".join("BValue arg{i}".format(i = i) for i in range(size)) diff --git a/core/src/main/scala/org/bykn/bosatsu/Matchless.scala b/core/src/main/scala/org/bykn/bosatsu/Matchless.scala index 11f704767..dac23c593 100644 --- a/core/src/main/scala/org/bykn/bosatsu/Matchless.scala +++ b/core/src/main/scala/org/bykn/bosatsu/Matchless.scala @@ -17,6 +17,11 @@ object Matchless { def captures: List[Expr] // this is set if the function is recursive def recursiveName: Option[Bindable] + def recursionKind: RecursionKind = RecursionKind.recursive(recursiveName.isDefined) + + def args: NonEmptyList[Bindable] + def arity: Int = args.length + def body: Expr } sealed abstract class StrPart @@ -84,7 +89,7 @@ object Matchless { captures: List[Expr], recursiveName: Option[Bindable], args: NonEmptyList[Bindable], - expr: Expr + body: Expr ) extends FnExpr // this is a tail recursive function that should be compiled into a loop @@ -94,7 +99,7 @@ object Matchless { case class LoopFn( captures: List[Expr], name: Bindable, - arg: NonEmptyList[Bindable], + args: NonEmptyList[Bindable], body: Expr ) extends FnExpr { val recursiveName: Option[Bindable] = Some(name) diff --git a/core/src/main/scala/org/bykn/bosatsu/codegen/Idents.scala b/core/src/main/scala/org/bykn/bosatsu/codegen/Idents.scala index 491b7d1d2..1bc325629 100644 --- a/core/src/main/scala/org/bykn/bosatsu/codegen/Idents.scala +++ b/core/src/main/scala/org/bykn/bosatsu/codegen/Idents.scala @@ -2,8 +2,29 @@ package org.bykn.bosatsu.codegen object Idents { - private[this] val base62Items = - (('0' to '9') ++ ('A' to 'Z') ++ ('a' to 'z')).toSet + private[this] val firstChars = + (('a' to 'z') ++ ('A' to 'Z')).toArray + + private[this] val base62ItemsArray = + (firstChars ++ ('0' to '9')).toArray + + private[this] val base62Items = base62ItemsArray.toSet + + // these are all the strings that escape as themselves + val allSimpleIdents: LazyList[String] = { + val front = firstChars.to(LazyList).map(_.toString) + val inners = base62ItemsArray.to(LazyList).map(_.toString) + + lazy val tails: LazyList[String] = inners #::: (for { + h <- inners + t <- tails + } yield h + t) + + front #::: (for { + f <- front + t <- tails + } yield f + t) + } private[this] val offset0: Int = '0'.toInt private[this] val offsetA: Int = 'A'.toInt - 10 diff --git a/core/src/main/scala/org/bykn/bosatsu/codegen/clang/ClangGen.scala b/core/src/main/scala/org/bykn/bosatsu/codegen/clang/ClangGen.scala new file mode 100644 index 000000000..933d323c1 --- /dev/null +++ b/core/src/main/scala/org/bykn/bosatsu/codegen/clang/ClangGen.scala @@ -0,0 +1,814 @@ +package org.bykn.bosatsu.codegen.clang + +import cats.{Eval, Monad, Traverse} +import cats.data.{StateT, EitherT, NonEmptyList, Chain} +import java.math.BigInteger +import java.nio.charset.StandardCharsets +import org.bykn.bosatsu.codegen.Idents +import org.bykn.bosatsu.rankn.DataRepr +import org.bykn.bosatsu.{Identifier, Lit, Matchless, PackageName} +import org.bykn.bosatsu.Matchless.Expr +import org.bykn.bosatsu.Identifier.Bindable +import org.typelevel.paiges.Doc + +import cats.syntax.all._ + +object ClangGen { + sealed abstract class Error + object Error { + case class UnknownValue(pack: PackageName, value: Bindable) extends Error + case class InvariantViolation(message: String, expr: Expr) extends Error + case class Unbound(bn: Bindable, inside: Option[(PackageName, Bindable)]) extends Error + } + + def renderMain( + sortedEnv: Vector[NonEmptyList[(PackageName, List[(Bindable, Expr)])]], + externals: Map[(PackageName, Bindable), (Code.Include, Code.Ident)], + value: (PackageName, Bindable), + evaluator: (Code.Include, Code.Ident) + ): Either[Error, Doc] = { + val env = Impl.Env.impl + import env._ + + val trav2 = Traverse[Vector].compose[NonEmptyList] + + val res = + trav2.traverse_(sortedEnv) { case (pn, values) => + values.traverse_ { case (bindable, expr) => + renderTop(pn, bindable, expr) + } + } *> env.renderMain(value._1, value._2, evaluator._1, evaluator._2) + + val allValues: Impl.AllValues = + sortedEnv + .iterator.flatMap(_.iterator) + .flatMap { case (p, vs) => + vs.iterator.map { case (b, e) => + (p, b) -> (e, Impl.generatedName(p, b)) + } + } + .toMap + + run(allValues, externals, res) + } + + private object Impl { + type AllValues = Map[(PackageName, Bindable), (Expr, Code.Ident)] + type Externals = Map[(PackageName, Bindable), (Code.Include, Code.Ident)] + + def fullName(p: PackageName, b: Bindable): String = + p.asString + "/" + b.asString + + def generatedName(p: PackageName, b: Bindable): Code.Ident = + Code.Ident(Idents.escape("___bsts_g_", fullName(p, b))) + + trait Env { + import Matchless._ + + type T[A] + implicit val monadImpl: Monad[T] + def run(pm: AllValues, externals: Externals, t: T[Unit]): Either[Error, Doc] + def appendStatement(stmt: Code.Statement): T[Unit] + def error[A](e: => Error): T[A] + def globalIdent(pn: PackageName, bn: Bindable): T[Code.Ident] + def bind[A](bn: Bindable)(in: T[A]): T[A] + def getBinding(bn: Bindable): T[Code.Ident] + def bindAnon[A](idx: Long)(in: T[A]): T[A] + def getAnon(idx: Long): T[Code.Ident] + // a recursive function needs to remap the Bindable to the top-level mangling + def recursiveName[A](fnName: Code.Ident, bn: Bindable, isClosure: Boolean)(in: T[A]): T[A] + // used for temporary variables of type BValue + def newLocalName(tag: String): T[Code.Ident] + def newTopName(tag: String): T[Code.Ident] + def directFn(p: PackageName, b: Bindable): T[Option[Code.Ident]] + def directFn(b: Bindable): T[Option[(Code.Ident, Boolean)]] + def inTop[A](p: PackageName, bn: Bindable)(ta: T[A]): T[A] + def staticValueName(p: PackageName, b: Bindable): T[Code.Ident] + def constructorFn(p: PackageName, b: Bindable): T[Code.Ident] + + ///////////////////////////////////////// + // the below are independent of the environment implementation + ///////////////////////////////////////// + + // This name has to be impossible to give out for any other purpose + val slotsArgName: Code.Ident = Code.Ident("__bstsi_slot") + + // assign any results to result and set the condition to false + // and replace any tail calls to nm(args) with assigning args to those values + def toWhileBody(fnName: Code.Ident, args: NonEmptyList[Code.Param], isClosure: Boolean, cond: Code.Ident, result: Code.Ident, body: Code.ValueLike): Code.Block = { + + import Code._ + + def returnValue(vl: ValueLike): Statement = + (cond := FalseLit) + + (result := vl) + + def loop(vl: ValueLike): Option[Statement] = + vl match { + case Apply(fn, appArgs) if fn == fnName => + // this is a tail call + val newArgsList = + if (isClosure) appArgs.tail + else appArgs + // we know the length of appArgs must match args or the code wouldn't have compiled + val assigns = args.zipWith(NonEmptyList.fromListUnsafe(newArgsList)) { + case (Param(_, name), value) => + Assignment(name, value) + } + Some(Statements(assigns)) + case IfElseValue(c, t, f) => + // this can possible have tail calls inside the branches + (loop(t), loop(f)) match { + case (Some(t), Some(f)) => + Some(ifThenElse(c, t, f)) + case (None, Some(f)) => + Some(ifThenElse(c, returnValue(t), f)) + case (Some(t), None) => + Some(ifThenElse(c, t, returnValue(f))) + case (None, None) => None + } + case Ternary(c, t, f) => loop(IfElseValue(c, t, f)) + case WithValue(s, vl) => loop(vl).map(s + _) + case Apply(_, _) | Cast(_, _) | BinExpr(_, _, _) | Bracket(_, _) | Ident(_) | + IntLiteral(_) | PostfixExpr(_, _) | PrefixExpr(_, _) | Select(_, _) | + StrLiteral(_) => None + } + + loop(body) match { + case Some(stmt) => block(stmt) + case None => + sys.error("invariant violation: could not find tail calls in:" + + s"toWhileBody(fnName = $fnName, body = $body)") + } + } + + def bindAll[A](nel: NonEmptyList[Bindable])(in: T[A]): T[A] = + bind(nel.head) { + NonEmptyList.fromList(nel.tail) match { + case None => in + case Some(rest) => bindAll(rest)(in) + } + } + + def equalsChar(expr: Code.Expression, codePoint: Int): Code.Expression = + expr =:= Code.Ident("BSTS_TO_CHAR")(Code.IntLiteral(codePoint)) + + def pv(e: Code.ValueLike): T[Code.ValueLike] = monadImpl.pure(e) + + // The type of this value must be a C _Bool + def boolToValue(boolExpr: BoolExpr): T[Code.ValueLike] = + boolExpr match { + case EqualsLit(expr, lit) => + innerToValue(expr).flatMap { vl => + lit match { + case c @ Lit.Chr(_) => vl.onExpr { e => pv(equalsChar(e, c.toCodePoint)) }(newLocalName) + case Lit.Str(_) => + vl.onExpr { e => + literal(lit).flatMap { litStr => + Code.ValueLike.applyArgs(Code.Ident("bsts_equals_string"), + NonEmptyList(e, litStr :: Nil) + )(newLocalName) + } + }(newLocalName) + case Lit.Integer(_) => + vl.onExpr { e => + literal(lit).flatMap { litStr => + Code.ValueLike.applyArgs(Code.Ident("bsts_equals_int"), + NonEmptyList(e, litStr :: Nil) + )(newLocalName) + } + }(newLocalName) + } + } + case EqualsNat(expr, nat) => + val fn = nat match { + case DataRepr.ZeroNat => Code.Ident("BSTS_NAT_IS_0") + case DataRepr.SuccNat => Code.Ident("BSTS_NAT_GT_0") + } + innerToValue(expr).flatMap { vl => + vl.onExpr { expr => pv(fn(expr)) }(newLocalName) + } + case And(e1, e2) => + (boolToValue(e1), boolToValue(e2)) + .flatMapN { (a, b) => + Code.ValueLike.applyArgs( + Code.Ident("BSTS_AND"), + NonEmptyList(a, b :: Nil) + )(newLocalName) + } + case CheckVariant(expr, expect, _, _) => + innerToValue(expr).flatMap { vl => + // this is just get_variant(expr) == expect + vl.onExpr { expr => pv(Code.Ident("get_variant")(expr) =:= Code.IntLiteral(expect)) }(newLocalName) + } + case sl @ SearchList(lst, init, check, leftAcc) => + // TODO: ??? + println(s"TODO: implement boolToValue($sl) returning false") + pv(Code.FalseLit) + case ms @ MatchString(arg, parts, binds) => + // TODO: ??? + println(s"TODO: implement boolToValue($ms) returning false") + pv(Code.FalseLit) + case SetMut(LocalAnonMut(idx), expr) => + for { + name <- getAnon(idx) + vl <- innerToValue(expr) + } yield (name := vl) +: Code.TrueLit + case TrueConst => pv(Code.TrueLit) + } + + // We have to lift functions to the top level and not + // create any nesting + def innerFn(fn: FnExpr): T[Code.ValueLike] = + if (fn.captures.isEmpty) { + for { + ident <- newTopName("lambda") + stmt <- fnStatement(ident, fn) + _ <- appendStatement(stmt) + } yield Code.Ident("STATIC_PUREFN")(ident) + } + else { + // we create the function, then we allocate + // values for the capture + // alloc_closure(capLen, captures, fnName) + for { + ident <- newTopName("closure") + stmt <- fnStatement(ident, fn) + _ <- appendStatement(stmt) + capName <- newLocalName("captures") + capValues <- fn.captures.traverse(innerToValue(_)) + decl <- Code.ValueLike.declareArray(capName, Code.TypeIdent.BValue, capValues)(newLocalName) + } yield Code.WithValue(decl, + Code.Ident(s"alloc_closure${fn.arity}")( + Code.IntLiteral(BigInt(fn.captures.length)), + capName, + ident + ) + ) + } + + def literal(lit: Lit): T[Code.ValueLike] = + lit match { + case c @ Lit.Chr(_) => + // encoded as integers in pure values + pv(Code.Ident("BSTS_TO_CHAR")(Code.IntLiteral(c.toCodePoint))) + case Lit.Integer(toBigInteger) => + try { + val iv = toBigInteger.intValueExact() + pv(Code.Ident("bsts_integer_from_int")(Code.IntLiteral(iv))) + } + catch { + case _: ArithmeticException => + // emit the uint32 words and sign + val isPos = toBigInteger.signum >= 0 + var current = if (isPos) toBigInteger else toBigInteger.negate() + val two32 = BigInteger.ONE.shiftLeft(32) + val bldr = List.newBuilder[Code.IntLiteral] + while (current.compareTo(BigInteger.ZERO) > 0) { + bldr += Code.IntLiteral(current.mod(two32).longValue()) + current = current.shiftRight(32) + } + val lits = bldr.result() + //call: + // bsts_integer_from_words_copy(_Bool is_pos, size_t size, int32_t* words); + newLocalName("int").map { ident => + Code.DeclareArray(Code.TypeIdent.UInt32, ident, Right(lits)) +: + Code.Ident("bsts_integer_from_words_copy")( + if (isPos) Code.TrueLit else Code.FalseLit, + Code.IntLiteral(lits.length), + ident + ) + } + } + + case Lit.Str(toStr) => + // convert to utf8 and then to a literal array of bytes + val bytes = toStr.getBytes(StandardCharsets.UTF_8) + if (bytes.forall(_.toInt != 0)) { + // just send the utf8 bytes as a string to C + pv( + Code.Ident("BSTS_NULL_TERM_STATIC_STR")(Code.StrLiteral( + new String(bytes.map(_.toChar)) + )) + ) + } + else { + // We have some null bytes, we have to encode the length + val lits = + bytes.iterator.map { byte => + Code.IntLiteral(byte.toInt & 0xff) + }.toList + //call: + // bsts_string_from_utf8_bytes_copy(size_t size, char* bytes); + newLocalName("str").map { ident => + // TODO: this could be a static top level definition to initialize + // one time and avoid the copy probably, but copies are fast.... + Code.DeclareArray(Code.TypeIdent.Char, ident, Right(lits)) +: + Code.Ident("bsts_string_from_utf8_bytes_copy")( + Code.IntLiteral(lits.length), + ident + ) + } + } + } + + def innerApp(app: App): T[Code.ValueLike] = + app match { + case App(Global(pack, fnName), args) => + directFn(pack, fnName).flatMap { + case Some(ident) => + // directly invoke instead of by treating them like lambdas + args.traverse(innerToValue(_)).flatMap { argsVL => + Code.ValueLike.applyArgs(ident, argsVL)(newLocalName) + } + case None => + // the ref be holding the result of another function call + (globalIdent(pack, fnName), args.traverse(innerToValue(_))).flatMapN { (fnVL, argsVL) => + // we need to invoke call_fn(fn, arg0, arg1, ....) + // but since these are ValueLike, we need to handle more carefully + val fnSize = argsVL.length + val callFn = Code.Ident(s"call_fn$fnSize") + Code.ValueLike.applyArgs(callFn, fnVL :: argsVL)(newLocalName) + } + } + case App(Local(fnName), args) => + directFn(fnName).flatMap { + case Some((ident, isClosure)) => + // directly invoke instead of by treating them like lambdas + args.traverse(innerToValue(_)).flatMap { argsVL => + val withSlot = + if (isClosure) slotsArgName :: argsVL + else argsVL + Code.ValueLike.applyArgs(ident, withSlot)(newLocalName) + } + case None => + // the ref be holding the result of another function call + (getBinding(fnName), args.traverse(innerToValue(_))).flatMapN { (fnVL, argsVL) => + // we need to invoke call_fn(fn, arg0, arg1, ....) + // but since these are ValueLike, we need to handle more carefully + val fnSize = argsVL.length + val callFn = Code.Ident(s"call_fn$fnSize") + Code.ValueLike.applyArgs(callFn, fnVL :: argsVL)(newLocalName) + } + } + case App(MakeEnum(variant, arity, _), args) => + // to type check, we know that the arity must have the same length as args + args.traverse(innerToValue).flatMap { argsVL => + val tag = Code.IntLiteral(variant) + Code.ValueLike.applyArgs(Code.Ident(s"alloc_enum$arity"), tag :: argsVL)(newLocalName) + } + case App(MakeStruct(arity), args) => + if (arity == 1) { + // this is a new-type, just return the arg + innerToValue(args.head) + } + else { + // to type check, we know that the arity must have the same length as args + args.traverse(innerToValue).flatMap { argsVL => + Code.ValueLike.applyArgs(Code.Ident(s"alloc_struct$arity"), argsVL)(newLocalName) + } + } + case App(SuccNat, args) => + innerToValue(args.head).flatMap { arg => + Code.ValueLike.applyArgs(Code.Ident("BSTS_NAT_SUCC"), NonEmptyList.one(arg))(newLocalName) + } + case App(fn, args) => + (innerToValue(fn), args.traverse(innerToValue(_))).flatMapN { (fnVL, argsVL) => + // we need to invoke call_fn(fn, arg0, arg1, ....) + // but since these are ValueLike, we need to handle more carefully + val fnSize = argsVL.length + val callFn = Code.Ident(s"call_fn$fnSize") + Code.ValueLike.applyArgs(callFn, fnVL :: argsVL)(newLocalName) + } + } + + def innerToValue(expr: Expr): T[Code.ValueLike] = + expr match { + case fn: FnExpr => innerFn(fn) + case Let(Right(arg), argV, in) => + bind(arg) { + for { + name <- getBinding(arg) + v <- innerToValue(argV) + result <- innerToValue(in) + stmt <- Code.ValueLike.declareVar(name, Code.TypeIdent.BValue, v)(newLocalName) + } yield stmt +: result + } + case Let(Left(LocalAnon(idx)), argV, in) => + bindAnon(idx) { + for { + name <- getAnon(idx) + v <- innerToValue(argV) + result <- innerToValue(in) + stmt <- Code.ValueLike.declareVar(name, Code.TypeIdent.BValue, v)(newLocalName) + } yield stmt +: result + } + case app @ App(_, _) => innerApp(app) + case Global(pack, name) => + directFn(pack, name) + .flatMap { + case Some(nm) => + pv(Code.Ident("STATIC_PUREFN")(nm)) + case None => + // read_or_build(&__bvalue_foo, make_foo); + for { + value <- staticValueName(pack, name) + consFn <- constructorFn(pack, name) + } yield Code.Ident("read_or_build")(value.addr, consFn): Code.ValueLike + } + case Local(arg) => + directFn(arg) + .flatMap { + case Some((nm, false)) => + // a closure can't be a static name + pv(Code.Ident("STATIC_PUREFN")(nm)) + case _ => + getBinding(arg).widen + } + case ClosureSlot(idx) => + // we must be inside a closure function, so we should have a slots argument to access + pv(slotsArgName.bracket(Code.IntLiteral(BigInt(idx)))) + case LocalAnon(ident) => getAnon(ident).widen + case LocalAnonMut(ident) => getAnon(ident).widen + case LetMut(LocalAnonMut(m), span) => + bindAnon(m) { + for { + ident <- getAnon(m) + decl = Code.DeclareVar(Nil, Code.TypeIdent.BValue, ident, None) + res <- innerToValue(span) + } yield decl +: res + } + case Literal(lit) => literal(lit) + case If(cond, thenExpr, elseExpr) => + (boolToValue(cond), innerToValue(thenExpr), innerToValue(elseExpr)) + .flatMapN { (c, thenC, elseC) => + Code.ValueLike.ifThenElseV(c, thenC, elseC)(newLocalName) + } + case Always(cond, thenExpr) => + boolToValue(cond).flatMap { bv => + bv.discardValue match { + case None => innerToValue(thenExpr) + case Some(effect) => innerToValue(thenExpr).map(effect +: _) + } + } + case GetEnumElement(arg, _, index, _) => + // call get_enum_index(v, index) + innerToValue(arg).flatMap { v => + v.onExpr(e => pv(Code.Ident("get_enum_index")(e, Code.IntLiteral(index))))(newLocalName) + } + case GetStructElement(arg, index, size) => + if (size == 1) { + // this is just a new-type wrapper, ignore it + innerToValue(arg) + } + else { + // call get_struct_index(v, index) + innerToValue(arg).flatMap { v => + v.onExpr { e => + pv(Code.Ident("get_struct_index")(e, Code.IntLiteral(index))) + }(newLocalName) + } + } + case makeEnum @ MakeEnum(variant, arity, _) => + // this is a closure over variant, we rewrite this + if (arity == 0) pv(Code.Ident("alloc_enum0")(Code.IntLiteral(variant))) + else { + val named = + // safe because arity > 0 + NonEmptyList.fromListUnsafe( + Idents.allSimpleIdents.take(arity).map { nm => Identifier.Name(nm) }.toList + ) + // This relies on optimizing App(MakeEnum, _) otherwise + // it creates an infinite loop. + // Also, this we should cache creation of Lambda/Closure values + innerToValue(Lambda(Nil, None, named, App(makeEnum, named.map(Local(_))))) + } + case MakeStruct(arity) => + pv { + if (arity == 0) Code.Ident("PURE_VALUE_TAG") + else { + val allocStructFn = s"alloc_struct$arity" + Code.Ident("STATIC_PUREFN")(Code.Ident(allocStructFn)) + } + } + case ZeroNat => + pv(Code.Ident("BSTS_NAT_0")) + case SuccNat => + val arg = Identifier.Name("arg0") + // This relies on optimizing App(SuccNat, _) otherwise + // it creates an infinite loop. + // Also, this we should cache creation of Lambda/Closure values + innerToValue(Lambda(Nil, None, NonEmptyList.one(arg), + App(SuccNat, NonEmptyList.one(Local(arg))))) + case PrevNat(of) => + innerToValue(of).flatMap { argVL => + Code.ValueLike.applyArgs( + Code.Ident("BSTS_NAT_PREV"), + NonEmptyList.one(argVL) + )(newLocalName) + } + } + + def fnStatement(fnName: Code.Ident, fn: FnExpr): T[Code.Statement] = + fn match { + case Lambda(captures, name, args, expr) => + val body = innerToValue(expr).map(Code.returnValue(_)) + val body1 = name match { + case None => body + case Some(rec) => recursiveName(fnName, rec, isClosure = captures.nonEmpty)(body) + } + + bindAll(args) { + for { + argParams <- args.traverse { b => + getBinding(b).map { i => Code.Param(Code.TypeIdent.BValue, i) } + } + fnBody <- body1 + allArgs = + if (captures.isEmpty) argParams + else { + Code.Param(Code.TypeIdent.BValue.ptr, slotsArgName) :: argParams + } + } yield Code.DeclareFn(Nil, Code.TypeIdent.BValue, fnName, allArgs.toList, Some(Code.block(fnBody))) + } + case LoopFn(captures, nm, args, body) => + recursiveName(fnName, nm, isClosure = captures.nonEmpty) { + bindAll(args) { + for { + cond <- newLocalName("cond") + res <- newLocalName("res") + bodyVL <- innerToValue(body) + argParams <- args.traverse { b => + getBinding(b).map { i => Code.Param(Code.TypeIdent.BValue, i) } + } + whileBody = toWhileBody(fnName, argParams, isClosure = captures.nonEmpty, cond = cond, result = res, body = bodyVL) + fnBody = Code.block( + Code.DeclareVar(Nil, Code.TypeIdent.Bool, cond, Some(Code.TrueLit)), + Code.DeclareVar(Nil, Code.TypeIdent.BValue, res, None), + Code.While(cond, whileBody), + Code.Return(Some(res)) + ) + allArgs = + if (captures.isEmpty) argParams + else { + Code.Param(Code.TypeIdent.BValue.ptr, slotsArgName) :: argParams + } + } yield Code.DeclareFn(Nil, Code.TypeIdent.BValue, fnName, allArgs.toList, Some(fnBody)) + } + } + } + + def renderTop(p: PackageName, b: Bindable, expr: Expr): T[Unit] = + inTop(p, b) { expr match { + case fn: FnExpr => + for { + fnName <- globalIdent(p, b) + stmt <- fnStatement(fnName, fn) + _ <- appendStatement(stmt) + } yield () + case someValue => + // we materialize an Atomic value to hold the static data + // then we generate a function to populate the value + for { + vl <- innerToValue(someValue) + value <- staticValueName(p, b) + consFn <- constructorFn(p, b) + _ <- appendStatement(Code.DeclareVar( + Code.Attr.Static :: Nil, + Code.TypeIdent.AtomicBValue, + value, + Some(Code.IntLiteral.Zero) + )) + _ <- appendStatement(Code.DeclareFn( + Code.Attr.Static :: Nil, + Code.TypeIdent.BValue, + consFn, + Nil, + Some(Code.block(Code.returnValue(vl))) + )) + } yield () + } + } + + def renderMain(p: PackageName, b: Bindable, evalInc: Code.Include, evalFn: Code.Ident): T[Unit] + } + + object Env { + def impl: Env = { + def catsMonad[S]: Monad[StateT[EitherT[Eval, Error, *], S, *]] = implicitly + + new Env { + case class State( + allValues: AllValues, + externals: Externals, + includeSet: Set[Code.Include], + includes: Chain[Code.Include], + stmts: Chain[Code.Statement], + currentTop: Option[(PackageName, Bindable)], + binds: Map[Bindable, NonEmptyList[Either[((Code.Ident, Boolean), Int), Int]]], + counter: Long + ) { + def finalFile: Doc = + Doc.intercalate(Doc.hardLine, includes.iterator.map(Code.toDoc(_)).toList) + + Doc.hardLine + Doc.hardLine + + Doc.intercalate(Doc.hardLine + Doc.hardLine, stmts.iterator.map(Code.toDoc(_)).toList) + } + + object State { + def init(allValues: AllValues, externals: Externals): State = { + val defaultIncludes = + List(Code.Include(true, "bosatsu_runtime.h")) + + State(allValues, externals, Set.empty ++ defaultIncludes, Chain.fromSeq(defaultIncludes), Chain.empty, + None, Map.empty, 0L + ) + } + } + + type T[A] = StateT[EitherT[Eval, Error, *], State, A] + + implicit val monadImpl: Monad[T] = catsMonad[State] + + def run(pm: AllValues, externals: Externals, t: T[Unit]): Either[Error, Doc] = + t.run(State.init(pm, externals)) + .value // get the value out of the EitherT + .value // evaluate the Eval + .map(_._1.finalFile) + + def appendStatement(stmt: Code.Statement): T[Unit] = + StateT.modify(s => s.copy(stmts = s.stmts :+ stmt)) + + def errorRes[A](e: => Error): EitherT[Eval, Error, A] = + EitherT[Eval, Error, A](Eval.later(Left(e))) + + def error[A](e: => Error): T[A] = + StateT(_ => errorRes(e)) + + def result[A](s: State, a: A): EitherT[Eval, Error, (State, A)] = + EitherT[Eval, Error, (State, A)]( + Eval.now(Right((s, a))) + ) + + def globalIdent(pn: PackageName, bn: Bindable): T[Code.Ident] = + StateT { s => + val key = (pn, bn) + s.externals.get(key) match { + case Some((incl, ident)) => + val withIncl = + if (s.includeSet(incl)) s + else s.copy(includeSet = s.includeSet + incl, includes = s.includes :+ incl) + + result(withIncl, ident) + case None => + s.allValues.get(key) match { + case Some((_, ident)) => result(s, ident) + case None => errorRes(Error.UnknownValue(pn, bn)) + } + } + } + + def bind[A](bn: Bindable)(in: T[A]): T[A] = { + val init: T[Unit] = StateT { s => + val v = s.binds.get(bn) match { + case None => NonEmptyList.one(Right(0)) + case Some(items @ NonEmptyList(Right(idx), _)) => + Right(idx + 1) :: items + case Some(items @ NonEmptyList(Left((_, idx)), _)) => + Right(idx + 1) :: items + } + result(s.copy(binds = s.binds.updated(bn, v)), ()) + } + + val uninit: T[Unit] = StateT { s => + s.binds.get(bn) match { + case Some(NonEmptyList(_, tail)) => + val s1 = NonEmptyList.fromList(tail) match { + case None => + s.copy(binds = s.binds - bn) + case Some(prior) => + s.copy(binds = s.binds.updated(bn, prior)) + } + result(s1, ()) + case None => sys.error(s"bindable $bn no longer in $s") + } + } + + for { + _ <- init + a <- in + _ <- uninit + } yield a + } + def getBinding(bn: Bindable): T[Code.Ident] = + StateT { s => + s.binds.get(bn) match { + case Some(stack) => + stack.head match { + case Right(idx) => + result(s, Code.Ident(Idents.escape("__bsts_b_", bn.asString + idx.toString))) + case Left(((ident, _), _)) => + // TODO: suspicious to ignore isClosure here + result(s, ident) + } + case None => errorRes(Error.Unbound(bn, s.currentTop)) + } + } + def bindAnon[A](idx: Long)(in: T[A]): T[A] = + // in the future we see the scope of the binding which matters for GC, but here + // we don't care + in + + def getAnon(idx: Long): T[Code.Ident] = + monadImpl.pure(Code.Ident(Idents.escape("__bsts_a_", idx.toString))) + + // a recursive function needs to remap the Bindable to the top-level mangling + def recursiveName[A](fnName: Code.Ident, bn: Bindable, isClosure: Boolean)(in: T[A]): T[A] = { + val init: T[Unit] = StateT { s => + val entry = (fnName, isClosure) + val v = s.binds.get(bn) match { + case None => NonEmptyList.one(Left((entry, -1))) + case Some(items @ NonEmptyList(Right(idx), _)) => + Left((entry, idx)) :: items + case Some(items @ NonEmptyList(Left((_, idx)), _)) => + Left((entry, idx)) :: items + } + result(s.copy(binds = s.binds.updated(bn, v)), ()) + } + + val uninit: T[Unit] = StateT { s => + s.binds.get(bn) match { + case Some(NonEmptyList(_, tail)) => + val s1 = NonEmptyList.fromList(tail) match { + case None => + s.copy(binds = s.binds - bn) + case Some(prior) => + s.copy(binds = s.binds.updated(bn, prior)) + } + result(s1, ()) + case None => sys.error(s"bindable $bn no longer in $s") + } + } + + for { + _ <- init + a <- in + _ <- uninit + } yield a + } + + val nextCnt: T[Long] = + StateT { s => + val cnt = s.counter + val s1 = s.copy(counter = cnt + 1L) + result(s1, cnt) + } + + // used for temporary variables of type BValue + def newLocalName(tag: String): T[Code.Ident] = + nextCnt.map { cnt => + Code.Ident(Idents.escape("__bsts_l_", tag + cnt.toString)) + } + def newTopName(tag: String): T[Code.Ident] = + nextCnt.map { cnt => + Code.Ident(Idents.escape("__bsts_t_", tag + cnt.toString)) + } + // record that this name is a top level function, so applying it can be direct + def directFn(pack: PackageName, b: Bindable): T[Option[Code.Ident]] = + StateT { s => + s.allValues.get((pack, b)) match { + case Some((_: Matchless.FnExpr, ident)) => + result(s, Some(ident)) + case _ => result(s, None) + } + } + + def directFn(b: Bindable): T[Option[(Code.Ident, Boolean)]] = + StateT { s => + s.binds.get(b) match { + case Some(NonEmptyList(Left((c, _)), _)) => + result(s, Some(c)) + case _ => + result(s, None) + } + } + + def inTop[A](p: PackageName, bn: Bindable)(ta: T[A]): T[A] = + for { + _ <- StateT { (s: State) => result(s.copy(currentTop = Some((p, bn))), ())} + a <- ta + _ <- StateT { (s: State) => result(s.copy(currentTop = None), ()) } + } yield a + + def staticValueName(p: PackageName, b: Bindable): T[Code.Ident] = + monadImpl.pure(Code.Ident(Idents.escape("___bsts_s_", fullName(p, b)))) + def constructorFn(p: PackageName, b: Bindable): T[Code.Ident] = + monadImpl.pure(Code.Ident(Idents.escape("___bsts_c_", fullName(p, b)))) + + def renderMain(p: PackageName, b: Bindable, evalInc: Code.Include, evalFn: Code.Ident): T[Unit] = + // TODO ??? + monadImpl.unit + } + } + } + } +} \ No newline at end of file diff --git a/core/src/main/scala/org/bykn/bosatsu/codegen/clang/Code.scala b/core/src/main/scala/org/bykn/bosatsu/codegen/clang/Code.scala index 28ad90da7..565be64b4 100644 --- a/core/src/main/scala/org/bykn/bosatsu/codegen/clang/Code.scala +++ b/core/src/main/scala/org/bykn/bosatsu/codegen/clang/Code.scala @@ -1,9 +1,13 @@ package org.bykn.bosatsu.codegen.clang +import cats.Monad +import cats.data.{NonEmptyList, NonEmptyChain} +import java.nio.charset.StandardCharsets import org.typelevel.paiges.Doc -import cats.data.NonEmptyList import scala.language.implicitConversions +import cats.syntax.all._ + sealed trait Code object Code { @@ -34,6 +38,12 @@ object Code { case class UnionType(name: String) extends ComplexType case class Named(name: String) extends TypeIdent case class Ptr(tpe: TypeIdent) extends TypeIdent + val Int: TypeIdent = Named("int") + val UInt32: TypeIdent = Named("uint32_t") + val Char: TypeIdent = Named("char") + val BValue: TypeIdent = Named("BValue") + val AtomicBValue: TypeIdent = Named("_Atomic BValue") + val Bool: TypeIdent = Named("_Bool") private val structDoc = Doc.text("struct ") private val unionDoc = Doc.text("union ") @@ -47,9 +57,67 @@ object Code { } } - sealed trait Expression extends Code { - def :=(rhs: Expression): Statement = - Assignment(this, rhs) + sealed trait ValueLike { + def +:(prefix: Statement): ValueLike = + ValueLike.prefix(prefix, this) + + def discardValue: Option[Statement] = + this match { + case _: Expression => None + case WithValue(stmt, vl) => + vl.discardValue match { + case None => Some(stmt) + case Some(rhs) => Some(stmt + rhs) + } + case IfElseValue(cond, thenC, elseC) => + (thenC.discardValue, elseC.discardValue) match { + case (Some(ts), Some(es)) => Some(ifThenElse(cond, ts, es)) + case (Some(ts), None) => Some(IfElse(NonEmptyList.one(cond -> block(ts)), None)) + case (None, Some(es)) => + // if (cond) {} else {es} == if (!cond) { es } + Some(IfElse(NonEmptyList.one(!cond -> block(es)), None)) + case (None, None) => None + } + } + + def onExpr[F[_]: Monad](fn: Expression => F[ValueLike])(newLocalName: String => F[Code.Ident]): F[ValueLike] = + this match { + case expr: Expression => fn(expr) + case WithValue(stmt, vl) => vl.onExpr[F](fn)(newLocalName).map(stmt +: _) + case branch @ IfElseValue(_, _, _) => + for { + resIdent <- newLocalName("branch_res") + value <- fn(resIdent) + } yield ( + // Assign branchCond to a temp variable in both branches + // and then use it so we don't exponentially blow up the code + // size + (Code.DeclareVar(Nil, Code.TypeIdent.BValue, resIdent, None) + + (resIdent := branch)) +: value + ) + } + + def exprToStatement[F[_]: Monad](fn: Expression => F[Statement])(newLocalName: String => F[Code.Ident]): F[Statement] = + this match { + case expr: Expression => fn(expr) + case WithValue(stmt, vl) => vl.exprToStatement[F](fn)(newLocalName).map(stmt + _) + case branch @ IfElseValue(_, _, _) => + for { + resIdent <- newLocalName("branch_res") + last <- fn(resIdent) + } yield + // Assign branchCond to a temp variable in both branches + // and then use it so we don't exponentially blow up the code + // size + (Code.DeclareVar(Nil, Code.TypeIdent.BValue, resIdent, None) + + (resIdent := branch)) + + last + } + } + + sealed trait Expression extends Code with ValueLike { + def :=(rhs: ValueLike): Statement = + ValueLike.assign(this, rhs) def ret: Statement = Return(Some(this)) @@ -72,11 +140,112 @@ object Code { def -(that: Expression): Expression = bin(BinOp.Sub, that) def *(that: Expression): Expression = bin(BinOp.Mult, that) def /(that: Expression): Expression = bin(BinOp.Div, that) + def unary_! : Expression = PrefixExpr(PrefixUnary.Not, this) + def =:=(that: Expression): Expression = bin(BinOp.Eq, that) def postInc: Expression = PostfixExpr(this, PostfixUnary.Inc) def postDec: Expression = PostfixExpr(this, PostfixUnary.Dec) } + ///////////////////////// + // Here are all the ValueLike + ///////////////////////// + + // this prepares an expression with a number of statements + case class WithValue(statement: Statement, value: ValueLike) extends ValueLike + // At least one of thenCond or elseCond should not be an expression + case class IfElseValue(cond: Expression, thenCond: ValueLike, elseCond: ValueLike) extends ValueLike + + object ValueLike { + def applyArgs[F[_]: Monad]( + fn: ValueLike, + args: NonEmptyList[ValueLike] + )(newLocalName: String => F[Code.Ident]): F[ValueLike] = + fn.onExpr { fnExpr => + def loop(rest: List[ValueLike], acc: NonEmptyList[Expression]): F[ValueLike] = + rest match { + case Nil => Monad[F].pure[ValueLike](fnExpr(acc.reverse.toList: _*)) + case h :: t => h.onExpr { hexpr => loop(t, hexpr :: acc) }(newLocalName) + } + + args.head.onExpr { hexpr => + loop(args.tail, NonEmptyList.one(hexpr)) + }(newLocalName) + }(newLocalName) + + + def declareArray[F[_]: Monad](ident: Ident, tpe: TypeIdent, values: List[ValueLike])(newLocalName: String => F[Code.Ident]): F[Statement] = { + def loop(values: List[ValueLike], acc: List[Expression]): F[Statement] = + values match { + case Nil => Monad[F].pure(DeclareArray(tpe, ident, Right(acc.reverse))) + case (e: Expression) :: tail => + loop(tail, e :: acc) + case h :: tail => + h.exprToStatement { e => + loop(tail, e :: acc) + }(newLocalName) + } + + loop(values, Nil) + } + + def declareVar[F[_]: Monad]( + ident: Ident, + tpe: TypeIdent, + value: ValueLike)(newLocalName: String => F[Code.Ident]): F[Statement] = + value.exprToStatement[F] { expr => + Monad[F].pure(DeclareVar(Nil, tpe, ident, Some(expr))) + }(newLocalName) + + def prefix(stmt: Statement, of: ValueLike): ValueLike = + of match { + case (_: Expression) | (_: IfElseValue) => WithValue(stmt, of) + case WithValue(stmt1, v) => WithValue(stmt + stmt1, v) + } + + def assign(left: Expression, rhs: ValueLike): Statement = + rhs match { + case expr: Expression => Assignment(left, expr) + case WithValue(stmt, v) => stmt + (left := v) + case IfElseValue(cond, thenC, elseC) => + ifThenElse(cond, left := thenC, left := elseC) + } + + def ifThenElseV[F[_]: Monad](cond: Code.ValueLike, thenC: Code.ValueLike, elseC: Code.ValueLike)(newLocalName: String => F[Code.Ident]): F[Code.ValueLike] = { + cond match { + case expr: Code.Expression => + Monad[F].pure { + (thenC, elseC) match { + case (thenX: Expression, elseX: Expression) => Ternary(expr, thenX, elseX) + case _ => IfElseValue(expr, thenC, elseC) + } + } + case Code.WithValue(stmt, v) => + ifThenElseV(v, thenC, elseC)(newLocalName).map(stmt +: _) + case branchCond @ Code.IfElseValue(_, _, _) => + for { + condIdent <- newLocalName("cond") + res <- ifThenElseV(condIdent, thenC, elseC)(newLocalName) + } yield { + // Assign branchCond to a temp variable in both branches + // and then use it so we don't exponentially blow up the code + // size + (Code.DeclareVar(Nil, Code.TypeIdent.Bool, condIdent, None) + + (condIdent := branchCond)) +: + res + } + } + } + } + + def returnValue(vl: ValueLike): Statement = + vl match { + case expr: Expression => Return(Some(expr)) + case WithValue(stmt, v) => stmt + returnValue(v) + case IfElseValue(cond, thenC, elseC) => + ifThenElse(cond, returnValue(thenC), returnValue(elseC)) + } + sealed abstract class BinOp(repr: String) { val toDoc: Doc = Doc.text(repr) } @@ -131,6 +300,18 @@ object Code { implicit def fromString(str: String): Ident = Ident(str) } case class IntLiteral(value: BigInt) extends Expression + case class StrLiteral(value: String) extends Expression + object IntLiteral { + val One: IntLiteral = IntLiteral(BigInt(1)) + val Zero: IntLiteral = IntLiteral(BigInt(0)) + + def apply(i: Int): IntLiteral = IntLiteral(BigInt(i)) + def apply(i: Long): IntLiteral = IntLiteral(BigInt(i)) + } + + def TrueLit: Expression = IntLiteral.One + def FalseLit: Expression = IntLiteral.Zero + case class Cast(tpe: TypeIdent, expr: Expression) extends Expression case class Apply(fn: Expression, args: List[Expression]) extends Expression case class Select(target: Expression, name: Ident) extends Expression @@ -144,7 +325,10 @@ object Code { def toDoc: Doc = TypeIdent.toDoc(tpe) + Doc.space + Doc.text(name.name) } - sealed trait Statement extends Code + sealed trait Statement extends Code { + def +(stmt: Statement): Statement = Statements.combine(this, stmt) + def :+(vl: ValueLike): ValueLike = (this +: vl) + } case class Assignment(target: Expression, value: Expression) extends Statement case class DeclareArray(tpe: TypeIdent, ident: Ident, values: Either[Int, List[Expression]]) extends Statement case class DeclareVar(attrs: List[Attr], tpe: TypeIdent, ident: Ident, value: Option[Expression]) extends Statement @@ -155,6 +339,26 @@ object Code { case class Block(items: NonEmptyList[Statement]) extends Statement { def doWhile(cond: Expression): Statement = DoWhile(this, cond) } + // nothing more than a collection of statements + case class Statements(items: NonEmptyChain[Statement]) extends Statement + object Statements { + def apply(nel: NonEmptyList[Statement]): Statements = + Statements(NonEmptyChain.fromNonEmptyList(nel)) + + def combine(first: Statement, last: Statement): Statement = + first match { + case Statements(items) => + last match { + case Statements(rhs) => Statements(items ++ rhs) + case notStmts => Statements(items :+ notStmts) + } + case notBlock => + last match { + case Statements(rhs) => Statements(notBlock +: rhs) + case notStmts => Statements(NonEmptyChain.of(notBlock, notStmts)) + } + } + } case class IfElse(ifs: NonEmptyList[(Expression, Block)], elseCond: Option[Block]) extends Statement case class DoWhile(block: Block, whileCond: Expression) extends Statement case class Effect(expr: Expression) extends Statement @@ -164,8 +368,20 @@ object Code { val returnVoid: Statement = Return(None) def block(item: Statement, rest: Statement*): Block = - Block(NonEmptyList(item, rest.toList)) + item match { + case block @ Block(_) if rest.isEmpty => block + case _ => Block(NonEmptyList(item, rest.toList)) + } + def ifThenElse(cond: Expression, thenCond: Statement, elseCond: Statement): Statement = { + val first = cond -> block(thenCond) + elseCond match { + case IfElse(ifs, elseCond) => + IfElse(first :: ifs, elseCond) + case notIfElse => + IfElse(NonEmptyList.one(first), Some(block(notIfElse))) + } + } private val equalsDoc = Doc.text(" = ") private val semiDoc = Doc.char(';') private val typeDefDoc = Doc.text("typedef ") @@ -218,6 +434,25 @@ object Code { c match { case Ident(n) => Doc.text(n) case IntLiteral(bi) => Doc.str(bi) + case StrLiteral(str) => + val result = new java.lang.StringBuilder() + val bytes = str.getBytes(StandardCharsets.US_ASCII) + bytes.foreach { c => + val cint = c.toInt & 0xFF + if (25 <= cint && cint <= 126) { + result.append(cint.toChar) + } + else if (cint == 92) { // this is \ + result.append("\\\\") + } + else if (cint == 34) { // this is " + result.append("\\\"") + } + else { + result.append(s"\\x${java.lang.Integer.toHexString(cint)}") + } + } + quoteDoc + (Doc.text(result.toString()) + quoteDoc) case Cast(tpe, expr) => val edoc = expr match { case Ident(n) => Doc.text(n) @@ -316,7 +551,7 @@ object Code { Doc.intercalate(Doc.space, attrs.map(a => Attr.toDoc(a))) + Doc.space } - val paramDoc = Doc.intercalate(Doc.line, args.map(_.toDoc)).nested(4).grouped + val paramDoc = Doc.intercalate(commaLine, args.map(_.toDoc)).nested(4).grouped val prefix = Doc.intercalate(Doc.space, (attrDoc + TypeIdent.toDoc(tpe)) :: @@ -342,6 +577,8 @@ object Code { case Some(expr) => returnSpace + toDoc(expr) + semiDoc } case Block(items) => curlyBlock(items.toList) { s => toDoc(s) } + case Statements(items) => + Doc.intercalate(Doc.line, items.toNonEmptyList.toList.map(toDoc(_))) case IfElse(ifs, els) => //"if (ex) {} else if" val (fcond, fblock) = ifs.head diff --git a/core/src/test/scala/org/bykn/bosatsu/MatchlessTests.scala b/core/src/test/scala/org/bykn/bosatsu/MatchlessTests.scala index 6c5014b57..50292978e 100644 --- a/core/src/test/scala/org/bykn/bosatsu/MatchlessTests.scala +++ b/core/src/test/scala/org/bykn/bosatsu/MatchlessTests.scala @@ -176,7 +176,7 @@ class MatchlessTest extends AnyFunSuite { TestUtils.checkMatchless(""" x = 1 """) { binds => - val map = binds.toMap + val map = binds(TestUtils.testPackage).toMap assert(map.contains(Identifier.Name("x"))) assert(map(Identifier.Name("x")) == Matchless.Literal(Lit(1))) diff --git a/core/src/test/scala/org/bykn/bosatsu/TestUtils.scala b/core/src/test/scala/org/bykn/bosatsu/TestUtils.scala index c4593e097..9fb15c714 100644 --- a/core/src/test/scala/org/bykn/bosatsu/TestUtils.scala +++ b/core/src/test/scala/org/bykn/bosatsu/TestUtils.scala @@ -101,7 +101,7 @@ object TestUtils { def checkMatchless[A]( statement: String - )(fn: List[(Identifier.Bindable, Matchless.Expr)] => A): A = { + )(fn: Map[PackageName, List[(Identifier.Bindable, Matchless.Expr)]] => A): A = { val stmts = Parser.unsafeParse(Statement.parser, statement) Package.inferBody(testPackage, Nil, stmts).strictToValidated match { case Validated.Invalid(errs) => @@ -122,7 +122,7 @@ object TestUtils { try { implicit val ec = Par.ecFromService(srv) val comp = MatchlessFromTypedExpr.compile(pm) - fn(comp(testPackage)) + fn(comp) } finally Par.shutdownService(srv) } diff --git a/core/src/test/scala/org/bykn/bosatsu/codegen/IdentsTest.scala b/core/src/test/scala/org/bykn/bosatsu/codegen/IdentsTest.scala index f856abe3f..75edc3ba8 100644 --- a/core/src/test/scala/org/bykn/bosatsu/codegen/IdentsTest.scala +++ b/core/src/test/scala/org/bykn/bosatsu/codegen/IdentsTest.scala @@ -16,6 +16,16 @@ class IdentsTest extends munit.ScalaCheckSuite { } } + test("allSimpleIdents escape to identity") { + Idents.allSimpleIdents.take(10000).foreach { str => + assertEquals(Idents.escape("", str), str) + } + } + + test("allSimpleIdents are distinct") { + assertEquals(Idents.allSimpleIdents.take(10000).toSet.size, 10000) + } + property("escape starts with prefix") { forAll { (prefix: String, content: String) => assert(Idents.escape(prefix, content).startsWith(prefix)) diff --git a/core/src/test/scala/org/bykn/bosatsu/codegen/clang/ClangGenTest.scala b/core/src/test/scala/org/bykn/bosatsu/codegen/clang/ClangGenTest.scala new file mode 100644 index 000000000..b20432673 --- /dev/null +++ b/core/src/test/scala/org/bykn/bosatsu/codegen/clang/ClangGenTest.scala @@ -0,0 +1,131 @@ +package org.bykn.bosatsu.codegen.clang + +import cats.data.NonEmptyList +import org.bykn.bosatsu.codegen.Idents +import org.bykn.bosatsu.{PackageName, TestUtils, Identifier, Predef} +import Identifier.Name + +class ClangGenTest extends munit.FunSuite { + val predef_c = Code.Include(true, "bosatsu_predef.h") + + def predef(s: String) = + (PackageName.PredefName -> Name(s)) -> (predef_c, + Code.Ident(Idents.escape("__bsts_predef_", s))) + + def assertPredefFns(fns: String*)(matches: String)(implicit loc: munit.Location) = + TestUtils.checkMatchless(""" +x = 1 +""") { matchlessMap0 => + + val fnSet = fns.toSet + val matchlessMap = matchlessMap0 + .flatMap { + case (k, vs) if k == PackageName.PredefName => + (k -> vs.filter(tup => fnSet(tup._1.asString))) :: Nil + case _ => Nil + } + .toMap + + val res = ClangGen.renderMain( + sortedEnv = Vector( + NonEmptyList.one(PackageName.PredefName -> matchlessMap(PackageName.PredefName)), + ), + externals = + Predef.jvmExternals.toMap.keys.iterator.map { case (_, n) => predef(n) }.toMap, + value = (PackageName.PredefName, Identifier.Name(fns.last)), + evaluator = (Code.Include(true, "eval.h"), Code.Ident("evaluator_run")) + ) + + res match { + case Right(d) => assertEquals(d.render(80), matches) + case Left(e) => fail(e.toString) + } + } + + test("check build_List") { + assertPredefFns("build_List")("""#include "bosatsu_runtime.h" + +BValue __bsts_t_lambda0(BValue __bsts_b_a0, BValue __bsts_b_b0) { + return alloc_enum2(1, __bsts_b_a0, __bsts_b_b0); +} + +BValue ___bsts_g_Bosatsu_l_Predef_l_build__List(BValue __bsts_b_fn0) { + return call_fn2(__bsts_b_fn0, + STATIC_PUREFN(__bsts_t_lambda0), + alloc_enum0(0)); +}""") + } + test("check foldr_List") { + assertPredefFns("foldr_List")("""#include "bosatsu_runtime.h" + +BValue __bsts_t_closure0(BValue* __bstsi_slot, BValue __bsts_b_list1) { + if (get_variant(__bsts_b_list1) == (0)) { + return __bstsi_slot[0]; + } + else { + BValue __bsts_b_h0 = get_enum_index(__bsts_b_list1, 0); + BValue __bsts_b_t0 = get_enum_index(__bsts_b_list1, 1); + return call_fn2(__bstsi_slot[1], + __bsts_b_h0, + __bsts_t_closure0(__bstsi_slot, __bsts_b_t0)); + } +} + +BValue ___bsts_g_Bosatsu_l_Predef_l_foldr__List(BValue __bsts_b_list0, + BValue __bsts_b_fn0, + BValue __bsts_b_acc0) { + BValue __bsts_l_captures1[2] = { __bsts_b_acc0, __bsts_b_fn0 }; + BValue __bsts_b_loop0 = alloc_closure1(2, + __bsts_l_captures1, + __bsts_t_closure0); + return call_fn1(__bsts_b_loop0, __bsts_b_list0); +}""") + } + + test("check foldLeft and reverse_concat") { + assertPredefFns("foldLeft", "reverse_concat")("""#include "bosatsu_runtime.h" + +BValue __bsts_t_closure0(BValue* __bstsi_slot, + BValue __bsts_b_lst1, + BValue __bsts_b_item1) { + _Bool __bsts_l_cond1 = 1; + BValue __bsts_l_res2; + while (__bsts_l_cond1) { + if (get_variant(__bsts_b_lst1) == (0)) { + __bsts_l_cond1 = 0; + __bsts_l_res2 = __bsts_b_item1; + } + else { + BValue __bsts_b_head0 = get_enum_index(__bsts_b_lst1, 0); + BValue __bsts_b_tail0 = get_enum_index(__bsts_b_lst1, 1); + __bsts_b_lst1 = __bsts_b_tail0; + __bsts_b_item1 = call_fn2(__bstsi_slot[0], + __bsts_b_item1, + __bsts_b_head0); + } + } + return __bsts_l_res2; +} + +BValue ___bsts_g_Bosatsu_l_Predef_l_foldLeft(BValue __bsts_b_lst0, + BValue __bsts_b_item0, + BValue __bsts_b_fn0) { + BValue __bsts_l_captures3[1] = { __bsts_b_fn0 }; + BValue __bsts_b_loop0 = alloc_closure2(1, + __bsts_l_captures3, + __bsts_t_closure0); + return call_fn2(__bsts_b_loop0, __bsts_b_lst0, __bsts_b_item0); +} + +BValue __bsts_t_lambda4(BValue __bsts_b_tail0, BValue __bsts_b_h0) { + return alloc_enum2(1, __bsts_b_h0, __bsts_b_tail0); +} + +BValue ___bsts_g_Bosatsu_l_Predef_l_reverse__concat(BValue __bsts_b_front0, + BValue __bsts_b_back0) { + return ___bsts_g_Bosatsu_l_Predef_l_foldLeft(__bsts_b_front0, + __bsts_b_back0, + STATIC_PUREFN(__bsts_t_lambda4)); +}""") + } +} \ No newline at end of file