Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions kklib/include/kklib/ref.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
25 changes: 24 additions & 1 deletion kklib/src/ref.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
16 changes: 16 additions & 0 deletions lib/std/core/types.kk
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ pub inline extern set( ^ref : ref<h,a>, assigned : a) : write<h> ()
cs inline "#1.Set(#2)"
js inline "((#1).value = #2)"


// Read the value of a reference.
pub inline extern ref/(!)<h,a,e>( ref : ref<h,a>, ?hdiv : hdiv<h,a,e>) : <read<h>|e> a
c inline "kk_ref_get(#1,kk_context())"
Expand All @@ -360,6 +361,21 @@ pub inline extern modify<h,a,b,e>( ref : ref<h,a>, f : forall<s> local-var<s,a>
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<h>` effect.
pub inline extern ref/update<h,a>( ^ref : ref<h,a>, update : a -> a): <read<h>,write<h>> ()
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<h>` effect.
pub inline extern local-var/update<h,a>( ^ref : local-var<h,a>, update : a -> a): <local<h>> ()
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<e,a>( action : forall<h> () -> <alloc<h>,read<h>,write<h> | e> a ) : e a
Expand Down
94 changes: 94 additions & 0 deletions test/cgen/string-buf.kk
Original file line number Diff line number Diff line change
@@ -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<list<char>>
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<char> ) : 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<list<string>>
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<string>
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)