diff --git a/kklib/include/kklib/ref.h b/kklib/include/kklib/ref.h index 7038009d9..c096b29fe 100644 --- a/kklib/include/kklib/ref.h +++ b/kklib/include/kklib/ref.h @@ -22,6 +22,7 @@ typedef kk_datatype_ptr_t kk_ref_t; kk_decl_export kk_box_t kk_ref_get_thread_shared(struct kk_ref_s* r, kk_context_t* ctx); kk_decl_export kk_box_t kk_ref_swap_thread_shared_borrow(struct kk_ref_s* r, kk_box_t value); kk_decl_export kk_unit_t kk_ref_vector_assign_borrow(kk_ref_t r, kk_integer_t idx, kk_box_t value, kk_context_t* ctx); +kk_decl_export kk_unit_t kk_ref_update_borrow(kk_ref_t _r, kk_function_t update, kk_context_t* ctx); static inline kk_decl_const kk_box_t kk_ref_box(kk_ref_t r, kk_context_t* ctx) { kk_unused(ctx); diff --git a/kklib/src/ref.c b/kklib/src/ref.c index e8e9a8c5c..67a8a4453 100644 --- a/kklib/src/ref.c +++ b/kklib/src/ref.c @@ -46,4 +46,27 @@ kk_decl_export kk_box_t kk_ref_swap_thread_shared_borrow(struct kk_ref_s* r, kk_ return b; } - +// TODO: inline this function? +// Update is owned since it is likely a closure, copy is borrowed since it is likely a static function. +kk_decl_export kk_unit_t kk_ref_update_borrow(kk_ref_t _r, kk_function_t update, kk_context_t* ctx) { + struct kk_ref_s* r = kk_datatype_as_assert(struct kk_ref_s*, _r, KK_TAG_REF, ctx); + if kk_likely(!kk_block_is_thread_shared(&r->_block)) { + // fast path + kk_box_t b; + b.box = kk_atomic_load_relaxed(&r->value); + // kk_assert_internal(kk_datatype_is_unique(v,ctx)); The returned value should be unique (in place update), but `update` is not constrained to do that + kk_box_t newb = kk_function_call(kk_box_t, (kk_function_t, kk_box_t, kk_context_t*), update, (update, b, ctx), ctx); + // Reference types should not change the box (in place updates) + // However, `update` could be used for other purposes (allocating a new value, extending a current one, etc.) + kk_atomic_store_relaxed(&r->value, newb.box); + return kk_Unit; + } + else { + // Update is dropped in the other branch (since ownership is passed to the called function) but we need to drop it here as well. + kk_function_drop(update, ctx); + // It doesn't matter anyways, because we don't support updates to a thread-shared reference. + // thread shared + kk_unsupported_external("kk_ref_update_borrow with a thread-shared reference"); + } + return kk_Unit; +} \ No newline at end of file diff --git a/lib/std/core/types.kk b/lib/std/core/types.kk index e99324646..fa6becbe0 100644 --- a/lib/std/core/types.kk +++ b/lib/std/core/types.kk @@ -345,6 +345,7 @@ pub inline extern set( ^ref : ref, assigned : a) : write () cs inline "#1.Set(#2)" js inline "((#1).value = #2)" + // Read the value of a reference. pub inline extern ref/(!)( ref : ref, ?hdiv : hdiv) : |e> a c inline "kk_ref_get(#1,kk_context())" @@ -360,6 +361,21 @@ pub inline extern modify( ref : ref, f : forall local-var c inline "kk_ref_modify(#1,#2,kk_context())" js inline "((#2)(#1))" + +// Update the value of a reference. +// This function passes the ownership to the given function, requiring that the function be total and return the modified value. +// Because it is a total function we are guaranteed that it will not try to access the reference within `update` due to the `st` effect. +pub inline extern ref/update( ^ref : ref, update : a -> a): ,write> () + c "kk_ref_update_borrow" + js inline "(#2({...#1}))" + +// Update the value of a local var. +// This function passes the ownership to the given function, requiring that the function be total and return the modified value. +// Because it is a total function we are guaranteed that it will not try to access the local within `update` due to the `local` effect. +pub inline extern local-var/update( ^ref : local-var, update : a -> a): > () + c "kk_ref_update_borrow" + js inline "(#2({...#1}))" + // If a heap effect is unobservable, the heap effect can be erased by using the `run` fun. // See also: _State in Haskell, by Simon Peyton Jones and John Launchbury_. pub extern run( action : forall () -> ,read,write | e> a ) : e a diff --git a/test/cgen/string-buf.kk b/test/cgen/string-buf.kk new file mode 100644 index 000000000..5a0b2ce83 --- /dev/null +++ b/test/cgen/string-buf.kk @@ -0,0 +1,94 @@ +import std/time + +// Working directly with strings by appending: +alias strbuf0 = string +fun strbuf0/empty(): strbuf0 + "" +pub fun strbuf0/append( buf : strbuf0, s : string ) : strbuf0 + buf ++ s +pub fun strbuf0/finalize( buf : strbuf0 ) : string + buf + +// List of chars, using constructor contexts: +alias strbuf1 = ctx> +fun strbuf1/empty(): strbuf1 + ctx hole +pub fun list-char/append( buf : strbuf1, c : char ) : strbuf1 + buf ++ ctx Cons(c,hole) +pub fun list-char/append-chars( buf : strbuf1, cs : list ) : strbuf1 + match cs + Nil -> buf + Cons(c,cc) -> buf.append(c).append-chars(cc) +pub fun strbuf1/append( buf : strbuf1, s : string ) : strbuf1 + buf.append-chars(list(s)) +pub fun strbuf1/finalize( buf : strbuf1 ) : string + (buf ++. Nil).string + +// List of strings, using constructor contexts +alias strbuf2 = ctx> +fun strbuf2/empty(): strbuf2 + ctx hole +pub fun strbuf2/append( buf : strbuf2, s : string ) : strbuf2 + buf ++ ctx Cons(s,hole) +pub fun strbuf2/finalize( buf : strbuf2 ) : string + (buf ++. Nil).join + +// List of strings, using final reversing: +alias strbuf3 = list +fun strbuf3/empty(): strbuf3 + Nil +pub fun strbuf3/append( buf : strbuf3, s : string ) : strbuf3 + Cons(s,buf) +pub fun strbuf3/finalize( buf : strbuf3 ) : string + buf.reverse-join + +// Using local variables: +pub fun bm-localvar(count: int, empty: a, ?append: (a, string) -> a, ?finalize: a -> string) + val y = + var buf := empty + for(1,count) fn(_) + std/core/types/@byref(buf).update fn(b) + b.append("PARIS ") + buf + y.finalize.keep.ignore + +pub fun bm-localvar0(count) bm-localvar(count, strbuf0/empty()) +pub fun bm-localvar1(count) bm-localvar(count, strbuf1/empty()) +pub fun bm-localvar2(count) bm-localvar(count, strbuf2/empty()) +pub fun bm-localvar3(count) bm-localvar(count, strbuf3/empty()) + + +// Without local variables, but functional: +pub fun bm-functional(count, empty, ?append, ?finalize) + fun func(remaining, buf) + if ( remaining <= 0 ) then return buf + func(remaining - 1, buf.append("PARIS ")) + func(count, empty).finalize.keep.ignore + +pub fun bm-functional0(count) bm-functional(count, strbuf0/empty()) +pub fun bm-functional1(count) bm-functional(count, strbuf1/empty()) +pub fun bm-functional2(count) bm-functional(count, strbuf2/empty()) +pub fun bm-functional3(count) bm-functional(count, strbuf3/empty()) + +// Execute single benchmark multiple times: +pub fun benchmark(bm-func) + val start = now-in() + bm-func() + val end = now-in() + end - start + +// Execute benchmarks: +pub fun benchmarks( count : int ) + println("functional0: " ++ show(benchmark{bm-functional0(count)})) + println("functional1: " ++ show(benchmark{bm-functional1(count)})) + println("functional2: " ++ show(benchmark{bm-functional2(count)})) + println("functional3: " ++ show(benchmark{bm-functional3(count)})) + println("localvar0: " ++ show(benchmark{bm-localvar0(count)})) + println("localvar1: " ++ show(benchmark{bm-localvar1(count)})) + println("localvar2: " ++ show(benchmark{bm-localvar2(count)})) + println("localvar3: " ++ show(benchmark{bm-localvar3(count)})) + +pub fun main() + benchmarks(100000) + println("Double:") + benchmarks(200000) \ No newline at end of file