-
Notifications
You must be signed in to change notification settings - Fork 3
Implementation Specific Behavior
This page describes ways in which this implementation differs from Steve Dekorte's original C implementation of Io. As shorthand on this page, "iolang" means this implementation, and "Io" means the original implementation. (The same rule holds in most places throughout the source code as well.)
Io has three different implementations for the scheduler: Fibers on Windows, ucontext on supported UNIX-derived operating systems, and setjmp everywhere else - none of which are parallel. iolang uses goroutines, leveraging the built-in scheduler in the Go runtime, and true parallelism is allowed.
In Io, only one coroutine is executing at a time, which means that every other coroutine is yielding. This means that the Scheduler yieldingCoros
list contains every coroutine other than the current one. One Io programming pattern involves using the size of that list to implement a wait group, yielding until it is smaller than a chosen size.
In iolang, because there is no way to determine which goroutines are currently executing, and because there may be any number of them executing in parallel, all coroutines are considered to be constantly executing unless they explicitly wait on another coroutine (e.g. by requesting the value of a Future). Scheduler yieldingCoros
is not a list; instead, it is a Scheduler CFunction which iterates through all active coroutines. It is implemented to provide the same behavior as Io's, i.e. returning every coroutine except the one which called the method. However, it blocks new coroutines from launching or finishing while scanning through the coroutines, so its performance will be very poor in highly concurrent programs.
Programs which depend on the single-execution behavior of Io by using Scheduler yieldingCoros
(or Coroutine yieldingCoros
) to wait on other coroutines will probably perform poorly in iolang; there is a new Scheduler coroCount
method that simulates Scheduler yieldingCoros size
much more efficiently. Using something like if(Scheduler hasLocalSlot("coroCount") not, Scheduler coroCount := method(yieldingCoros size))
will make cross-compatibility easier. Any new methods to manipulate the scheduler in Io by modifying yieldingCoros
will fail in iolang, because in iolang, yieldingCoros
is a CFunction that constructs a list.
Sequence has several significant differences. The primary differences regard encoding. String literals are encoded in UTF-8 instead of ASCII. iolang uses UTF-16 and UTF-32 instead of UCS-2 and UCS-4. The Sequence methods asUCS2
and asUCS4
do not exist, and setEncoding
will not accept "ucs2"
or "ucs4"
as an argument. iolang accepts Latin-1 as a (preferred) synonym for ASCII. There is a new asLatin1
method analogous to asUTF8
and friends to convert to Latin-1.
iolang's sequences generally have worse performance in both memory usage and speed of operations than Io's. Io uses type punning of the underlying data in Sequence objects for efficient conversions between item types. iolang avoids this due to stronger typing in Go, which means that converting to a new data type involves copying the entire sequence. Additionally, when converting to a larger item type, iolang will discard any bytes that don't fit into a complete element. "abc" asMutable setItemType("uint16") size
will be 2 in Io (reading past the end of the allocated array) but 1 in iolang. (I might eventually change iolang's behavior in this regard to match Io's, zero-padding the data as needed to convert the entire buffer.)
In iolang, converting a Sequence to a string is a particularly expensive operation if the encoding is not UTF-8. Most methods that treat the Sequence as a string, including asString
and anything that involves "characters" rather than "elements," convert to string internally. If you plan to use more than a few of the methods implemented in sequence-string.go
(initSequence()
in sequence.go
organizes methods by file), it's a good idea to convert to UTF-8 as early as possible.
Some Sequence methods are not implemented. Instead of the four ordered comparison operators, iolang implements compare, which the Object implementations of <
, >=
, &c. use. The translate
method is not implemented, because I can't figure out what it's supposed to do for multi-byte item types and the Io interpreter segfaults when I try it. The cPrint
method is not implemented because it seems to be the same as Object print
.
Aside from the above, some Sequence methods have slightly different behavior:
-
pack
andunpack
treat thes
format without a size prefix as indicating a nul-terminated string. Io instead encodes a string of length 1. -
dotProduct
does not require its receiver to be mutable. Io's does. -
meanSquare
performs all its calculations in typefloat64
. Io squares the sequence values in the sequence's type, then converts tofloat64
before summing. -
normalize
requires its receiver to be numeric. Io's does not. -
lastPathComponent
removes trailing slashes, returns.
for the empty string, and returns the system's path separator for a string of only slashes. Io in these cases respectively preserves trailing slashes, returns the empty string, and returns the same number of slashes. -
pathComponent
has the same differences aslastPathComponent
, plus it treats a trailing slash as a new path element. For"a/b/c/" pathComponent
, iolang returnsa/b/c
, but Io returnsa/b
.
Io implements "proxies" as objects that "become" other objects by replacing the object at the proxy's address. This is possible and (relatively) safe because Io does not evaluate messages in parallel. In iolang, changing an object's type indicator is subject to dangerous races, where one parallel coroutine executing a CFunction might verify an object's type, another coroutine causes the object to become a different type, and then the CFunction executes. Or, the type indicator could change during the type check, which would cause unpredictable behavior. Rather than synchronizing every CFunction activation, iolang does not provide true proxies. This has a few consequences that hopefully won't impact programs:
- Future objects remain Future objects forever. Once the promised value is available, the Future's
Activate
method activates that instead, and itsforward
slot delegates to the value. This means that thegetSlot
method will return the Future itself, which in turn means that activatable objects (usually just methods) inside Futures can't be accessed other than to activate them. Additionally, usinggetSlot("slotContainingAFuture") type
will always produce "Future". - There is no FutureProxy type.
- There is no
Object become
method.
The Date methods zone
and convertToZone
don't exist; iolang provides convertToLocation
using location names that Go's time.LoadLocation
accepts (e.g. "America/New_York"), and location
that returns either "Local" or "UTC", instead.
cpuSecondsToRun
measures real time, not CPU time.
The differences below are unlikely to cause issues with real-world programs.
iolang's lexer behaves differently with certain forms of numeric literals:
- Io parses
0xabcdefghi
as a single hexadecimal number with the value 0xabcdef. Theghi
component remains in the message text, but it is ignored for the message's cachedResult. iolang parses the same text as a hexadecimal token0xabcdef
and an identifier tokenghi
. - Io parses
1234.
as an identifier. iolang parses it as a number with 1234 as its value. - Io parses
1234e
as an identifier. iolang produces an error with this form. - Io parses
1234x
as a number1234
and an identifierx
. iolang produces an error with this form.
-
-
calls its argument'snegate
method in iolang, so that it doesn't depend on its type. In Io, the argument must be Number. - There is a new
asGoRepr
method, primarily for debugging. -
continue
accepts an argument in iolang, which replaces the loop's evaluation result ifcontinue
causes the loop to exit. In Io, any argument tocontinue
is ignored, and ifcontinue
causes the loop to exit, then the last loop result is returned. (Or it should;for(x, 0, 2, continue)
seems to return the current coroutine.) -
shallowCopy
makes a shallow copy of the receiver's protos in iolang. In Io, the result has no protos.