From 6bbab56e5001bac144c6ef8fe73ae27df4695bb3 Mon Sep 17 00:00:00 2001 From: NeilBrown Date: Tue, 2 Apr 2019 11:06:08 +1100 Subject: [PATCH] updates --- Ocean-concurrent | 77 +++++++++++++++++++++++++ Ocean-types | 144 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 221 insertions(+) create mode 100644 Ocean-concurrent create mode 100644 Ocean-types diff --git a/Ocean-concurrent b/Ocean-concurrent new file mode 100644 index 0000000..00d7ea6 --- /dev/null +++ b/Ocean-concurrent @@ -0,0 +1,77 @@ + +Concurency is important to make full use of modern +hardware. +Lots of tasks really don't benefit: they are boring +data manipulation and UI management. But when you +have a CPU-intensive task, you really want to parallelize it. + +And you want data-structures that allow this. + +But if you don't have all that many CPUs, you want very +light-weight tasks to do the work. + +We definitely want data structures like bag and lists that +can be accessed concurrently safely, and we want dependencies +to be sufficiently clear that some normally-sequential steps +can run in parallel. +This is where message passing comes in - having a 'channel' +that is used to pass a message from one code block to another +can then be a simple variable, or can be a synchronization object. +You might need a lot of such objects, so they need to be created +dynamically - like stack-frames. + +I probably need to experiment with some specific examples: + - quick sort + - unordered search + - image scale + - .. + +The other perspective on concurrency is the need to be non-blocking, +even when using a single CPU. This needs to be more explicit, +probably with a fork/join model. This could look like +a 'continuation' where we store code in a variable, but +the code starts running immediately and the variable changes +value somehow when the code completes. + + +Having thought about this during my run this morning, my current position is: + +1/ we have locking primitives, fields which indicate what lock protects them, + and pointers which are typed to say what lock they own. + Locks are generally nreader-xor-1writer and spin or wait on a queue, + possibly dedicated, possibly chosed by hash. + a refcount is a lock that is mostly in the nreader state. + A 'writer' which frees the object might get queued at object creation. + + +2/ we have "completions" in the language which are a bunch of code + combined with some data state - a bit like an object, but described + dynamically in-line. + A completion can be created easily and might be: + - run immedately + - stored and run later + - run asynchronously. In this case the returned value is a queue + that is easily waited on. + + + A completion might return a 'channel' which returns a seqeunce of values. + In the run-immediately case, this is like a loop. + In the stored-and-run-later, it is like a generator. + + +A completion combines code and data. The data is sucked from the +environment. Any borrowed pointers need to have their owner too, and +ownership needs to be duplicated or moved or whatever. +For a generator, we need explicit "use" calls. So want them for regular completions +too.??? or is "return" cleaner because it doesn't go to a channel exactly...?? + +I need a clean syntax for this. + +foo := continuation +? no, I think that looks weird. I want it to be obvious from the start. +But where do we store the handle? + + cont foo:= code + + fork foo:= code + diff --git a/Ocean-types b/Ocean-types new file mode 100644 index 0000000..f0f9d92 --- /dev/null +++ b/Ocean-types @@ -0,0 +1,144 @@ + +Types have a per-module namespace. +This is pre-populated with + int i8 i16 uint u8 u64 etc + num float f64 f128 + Bool + byte + char string + +Types can be added with: + + struct name: content + record name: content + enum name: content + class name: content + + name is optional, and can list (parameters) and /attributes + +Types can be constructed with + + name(args) parameterized type + name^ reference type + name[size] array type + (args:: args) procedure type + (args:: type) function type + + +The content of struct and record are a list of: + fieldname: type + or + fieldname/attribute: type + + 'attribute' can: + indicate endianness - bigendian littleendian hostendian + 'const' ?? + identify a refcount, + protected by a given lock?? + + For enum, content is list of + name = value + where "= value" is optional + +Pointers: + to assign a pointer, use foo = stuff + to update what the pointer points to, use foo^ = stuff + to get a reference to store in a pointer.... + references are either borrowed or owned. + a name defined "type^" is a borrowed reference, a name defined + "type@" is owned. + borrowed references may be taken of anything, but only remain defined + as long as the owner remains defined. + owned references can only be taken of ownable objects, and + remain indefinitely. + + There are various ways to own an object: + - refcount or lock + - ownership of a containing object. + - ownership provided by class method + +non-type names (vars, constants) can be introduced with + +const prefix: name = value; ... +func: name(args::type): statements +proc: name(args::result): statements + +main: statements +init: +exit: + + +enum name: values + + +Plan: + decide on data structure + different types can't really be handled by a big switch now, + I probably need and object with function pointers. + free_value(), vtype_compat(), val_init(), + dup_value(), value_cmp(), print_value(), parse_value() + + parse_value only needed for args - str and num + val_init... + print_value - only needed for print and code-dump + dup_value - needed until pointers can make sense + vtype_compat - needed for various things + value_cmp - needed until we have object behaviours + + pre-defined + Bool + int - sizes + char + string + array + I think I need to disassociate the type from the storage. + So a 'value' is a parsed constant, but something new is needed for + the content of a variable. + const + struct + +---------- + + I currently have an enum of types that is used to test compatability + and for propagation. + This needs to change .... I guess I need a struct type* What goes in it? + - name + - scalar/record/struct/array/pointer/func/enum + - other details. + + Do I need forward declarations? Maybe I can just be lazy and + require everything to be declared eventually. + That isn't sufficient for: + - mutually recursive functions + - mutually recursive structures + + What about unions ??? + blend with enum: a tag with fields? + struct name: + x:int; y:int + + .Bool.true = a:char; b:char + .Bool.false = ...... + + Are enums just a fancy way of doing 'const'? + I could have + const: a=1; b=2; c; d + which defines module-wide consts. + And separately have + enum foo: a, b, c + which defines consts foo.a foo.b foo.c + But I don't really like foo entering the val namespace. + If bar:foo, then bar.a could be true/false depending on value of bar. + bar.a! could set it?? bar.a=true? + var = .foo.a + + How do we allocate new objects? + new(type) ??? + ptrname = new ?? + new(ptrname) + ptrname := new(type) + + Maybe ^= assigns a borrowed reference, and @= assigns an owned reference. + + allocating isn't really a top priority, so I should just focus on + non-allocated types. -- 2.43.0