1. 04 Aug, 2020 1 commit
  2. 27 Jul, 2020 1 commit
  3. 15 Jul, 2020 1 commit
    • Yorick Peterse's avatar
      Refactor various aspects of VM instructions · 0619de82
      Yorick Peterse authored
      == Instruction memory layout
      VM instructions now have a fixed size of 16 bytes, and no longer make
      use of a separate heap-allocated Vec for their arguments. This reduces
      memory usage, and should make for more cache-friendly instruction
      Each instruction is limited to six arguments, which is enough for all
      existing instructions. Instructions that need a variable number of
      arguments, such as SetArray, make use of register ranges. Instead of
      specifying all registers, they specify the first one and a length. The
      compiler in turn makes sure all argument registers are in a contiguous
      order. This approach is also taken by Lua, though unlike Lua we don't
      require the arguments to come after the register containing the block
      to run.
      Some instructions supported optional arguments, such as SetObject. These
      instructions have been modified to simply always require an argument.
      This simplifies the VM code, and in almost all cases the arguments were
      always specified anyway.
      For Inko's test suite, these changes reduce peak RSS usage from 27 MB
      down to 21 MB.
      == MoveResult instruction
      Values returned and thrown are handled differently. Instead of each
      ExecutionContext storing the register (of the parent frame) to write
      their result to, operations that return or throw a value now store the
      value in a per-process "result" variable. The MoveResult instruction
      moves this value into a register, setting the "result" variable to NULL.
      This approach is inspired by the Dalvik VM, and simplifies instructions
      such as Return and Throw.
      == Single instruction for pinning processes
      The instructions ProcessPinThread and ProcessUnpinThread have been
      merged into a single ProcessSetPinned instruction. This instruction
      behaves similar to ProcessSetBlocking.
      == Removed instructions
      The following VM instructions have been removed as they were not used:
      * SetPrototype
      * RemoveAttribute
      * BlockSetReceiver
      == Refactoring of instruction handlers
      The functions used for handling instructions have been refactored,
      renamed (after their instructions), and are now always inlined. This
      looks a bit funny at the moment, but it should make it easier for a
      future JIT to reuse these functions. The renaming also allows one to
      import specific functions, without having to worry about generic names
      such as "get" conflicting with other functions.
      == Bytecode parser cleanup
      The bytecode parser has been cleaned up a bit, and now limits various
      data sequences to the maximum u16 value; instead of some arbitrarily
      determined limit. The u16::MAX limit ensures that registers can address
      the values directly.
      == Argument changes
      The VM no longer supports keyword arguments and rest arguments. Instead,
      the compiler takes care of translating these to positional arguments.
      Keyword arguments are translated to positional arguments, with
      unspecified arguments being passed NULL pointers. The way this works is
      1. Create an array with a NULL value/pointer for every _expected_
         argument. This is achieved by reserving register 0 and using that.
      2. For every positional argument passed, fill its corresponding cell. So
         argument 1 fills cell 0, argument 2 fills cell 1, etc.
      3. For keyword arguments, look up its argument position and set the
         corresponding cell.
      4. Pass this array as arguments to the VM instruction.
      This is best illustrated with a simple example:
          def foo(a = 1, b = 2, c = 3) {}
          foo(b: 10)
      Here the arguments passed would be:
          [NULL, 10, NULL]
      The VM then checks if `b` and `c` are set, sees they are NULL, and
      assigns them their default values.
      Validating of argument counts is also removed from the VM, now that
      dynamic method calls are no longer supported.
  4. 06 Jul, 2020 3 commits
  5. 05 Jul, 2020 2 commits
    • Yorick Peterse's avatar
      Remove various methods from the VM Process type · 0fdaf451
      Yorick Peterse authored
      These methods can/should be used via a ExecutionContext.
    • Yorick Peterse's avatar
      Don't store line numbers in ExecutionContext · d78ebee2
      Yorick Peterse authored
      Line numbers can be obtained from the instructions, based on the current
      instruction index. This doesn't reduce memory usage due to padding, but
      it simplifies some of the instruction handling logic.
      Initially I made an attempt to change the setup for line numbers
      entirely: instead of storing absolute lines per instruction, I
      implemented a line offset table similar to the one used in Python.
      This ended up reducing the size of bytecode files by about 5%, at the
      cost of increasing memory usage by about 5%. Due to the added complexity
      of said setup, I decided to not make use of it at this time.
  6. 01 Jul, 2020 2 commits
  7. 22 Jun, 2020 1 commit
    • Yorick Peterse's avatar
      Update various VM dependencies · e62a4f8c
      Yorick Peterse authored
      This requires a few small changes here and there for new APIs introduced
      by some crates. We also replace "dirs" with "dirs-next", as the "dirs"
      project has been archived.
  8. 20 Jun, 2020 1 commit
    • Yorick Peterse's avatar
      Don't clone when finding binding parents · 5276839b
      Yorick Peterse authored
      Cloning bindings when traversing their ancestors is expensive when done
      often enough. Using references is an easy way to work around this, at
      the cost of requiring two methods: Binding::find_parent() and
  9. 18 Jun, 2020 2 commits
    • Yorick Peterse's avatar
      Fix generating of release tarballs · baf6d686
      Yorick Peterse authored
      Generating source archives for releases was broken, and apparently for
      quite a while as well. This should fix that.
    • Yorick Peterse's avatar
      Fix stdout.print/stderr.print panicking · 4868f9a9
      Yorick Peterse authored
      stdout.print and stderr.print perform two writes: one for the message,
      and one for a newline. The newline write was not wrapped in a "try"
      expression, causing any exceptions thrown to go unnoticed.
      Sending messages to optional types inside a "try" was also broken. When
      doing so, the VM would jump to the incorrect instruction and effectively
      ignore the "else" clause; bubbling up the error instead. This is fixed
      by having the compiler generate different code: instead of jumping to
      the next block (whatever that may be), we jump to a specific block.
      As part of this the signature of stdout.print/stderr.print is also
      changed. Instead of the argument being typed as `?ToString`, it's now a
      `ToString` with an empty String as the default value.
      This fixes #199
  10. 11 Jun, 2020 1 commit
  11. 10 Jun, 2020 1 commit
  12. 28 May, 2020 2 commits
    • Yorick Peterse's avatar
    • Yorick Peterse's avatar
      Replace the Dynamic type with the "Any" trait · 08385683
      Yorick Peterse authored
      The Any trait is a trait implemented by all objects automatically, and
      replaces all use of the Dynamic type. This means that Inko is now
      statically typed, instead of being gradually typed.
      The Any type is introduced as it can be cast to any other type. Using
      "Object" to represent something that could be anything does not work, as
      objects can't be casted to other objects. Any being implemented by all
      objects does allow one to cast it to an object.
      Argument types must now be known, meaning you either have to specify a
      default value, and explicit type, or both. In other words, this is no
      longer valid:
          def foo(number) { }
      For closures and lambdas the compiler still tries to infer the argument
      types based on how these blocks are used.
      The behaviour of omitting method return types also changed. Prior to
      this commit, leaving out a return type would result in it being inferred
      as Dynamic. Starting with this commit, omitting a method's return type
      results in:
      1. The compiler inferring the return type as Nil
      2. The compiler inserting a `return` at the end of the method, unless a
         `return` is already present
      Take for example this method:
          def init {
            @Number = 10
      Prior to this commit, the return type of `init` would be `Dynamic` and
      the value returned would be `10` (an `Integer`). In this commit this is
      changed so that `init` is inferred to return `Nil`, and the actual
      return value is also `Nil` (not `10`). This matches how dynamic return
      types were used in the standard library so far: to signal we don't
      really care about the return value.
      For closures and lambdas, the compiler still tries to infer the return
      type based on the body; defaulting to Nil if no other type could be
      == Reasons for moving to static typing
      Inko was gradually typed for a few reasons. First, when I started
      working on Inko I was leaning more towards making it a dynamic language
      similar to Ruby or Python. As I spent more time working on it, I
      realised I was starting to prefer a statically typed language more and
      I also felt that gradual typing would strike a balance between the rapid
      prototyping capabilities of a dynamic language, and the safety of a
      statically typed language. But in practise I found this not to be the
      case. For example, in dynamic languages (e.g. Ruby) I spend a lot of
      time jumping between running the code, and fixing silly errors such as
      the use of undefined local variables; something a statically typed
      language could easily detect.
      Inko using statically typed code as a baseline (instead of dynamically
      typed code) also made using dynamic types frustrating. Take this code
      for example:
          def add(a, b) {
            a + b
          let result = add(10, 20)
      Here `a` and `b` are dynamically typed, so is the return type. Now
      imagine we want to print the results:
          import std::stdio::stdout
          def add(a, b) {
            a + b
          let result = add(10, 20)
      This will fail to compile, as `stdout.print` expects a `ToString`, but
      `result` is a `Dynamic` and the compiler does not know if the runtime
      type implements `ToString`. This inevitably leads to a lot of cast
      expressions like so:
          import std::stdio::stdout
          def add(a, b) {
            a + b
          let result = add(10, 20)
          stdout.print(result as Integer)
      If we're going to require developers to cast dynamic types almost every
      time they use them, they won't be all that useful. And if they're not
      useful, we should just get rid of them.
      This doesn't mean gradual typing is a bad idea. In fact, I think it's a
      great way of making a dynamically typed language more safe. TypeScript
      is a good example: it takes JavaScript (dynamically typed), and adds
      static typing on top. But supporting gradual typing in a statically
      typed language just doesn't bring any benefits, and makes certain
      optimisations more difficult or even impossible. And thus dynamic typing
      is no more.
      Fixes #194
  13. 03 May, 2020 1 commit
    • Yorick Peterse's avatar
      Fix license scanning build · 177d616a
      Yorick Peterse authored
      License scanning was broken due to Cargo not being installed, and the
      configuration no longer working with newer versions of the license
      finder Gem.
  14. 30 Apr, 2020 4 commits
  15. 27 Feb, 2020 1 commit
    • Yorick Peterse's avatar
      Use VirtualBox for both Windows and macOS · 1aac792c
      Yorick Peterse authored
      Using Windows containers in Docker has been a painful experience. Slow
      networks, no support for transparent networks due to the host being a
      VPS, building large containers taking forever, the list goes on.
      Meanwhile it costs me roughly €50 per month to keep the Windows CI
      runner online.
      Since we already have a Mac Mini at MacStadium (for free), we can just
      re-use that Mac and run Windows inside a VirtualBox VM. This allows us
      to work around the various Docker issues on Windows, and should reduce
      our monthly infrastructure costs to more or less €0 (excluding the €1-2
      I pay to AWS every month).
      As part of this we also re-organise the runner tags a bit: the runners
      now use three tags:
      1. "inko"
      2. "vbox" (to indicate it's a virtualbox runner)
      3. "macos" or "windows", based on the type of OS
      This ensures we don't accidentally pick up a shared runner that just
      uses a "windows" or "macos" tag.
  16. 23 Feb, 2020 1 commit
    • Yorick Peterse's avatar
      Upgrade the "time" crate to 0.2.x · 5352ec7f
      Yorick Peterse authored
      Version 0.2.x of the "time" crate comes with an API that is quite
      different from version 0.1.x. This commit upgrades Inko to this new AP.
      As version 0.2.x does not support checking if DST is active, support for
      this has been removed. DST support is tricky, as it can vary based on
      where you are, politics (e.g. some countries may drop it), etc.
      As part of upgrading to time 0.2.x, some of the internals for obtaining
      timestamps has been changed. In particular, obtaining a timestamp and
      UTC offset has been merged into a single VM instruction. This should
      ensure that no funny business happens when obtaining a timestamp but not
      obtaining the UTC offset until a later point in time. The downside is
      having to allocate an array object, but the overhead of this should be
      This fixes #188
  17. 05 Feb, 2020 1 commit
    • Yorick Peterse's avatar
      Remove type parameters when re-opening objects · d5c10f8b
      Yorick Peterse authored
      When implementing a trait for an object or when re-opening an object,
      the compiler required that you specify the type parameter names of the
      object. For example, to implement the Equal trait for Triple you would
          impl Equal for Triple!(A, B, C) {
      The original idea of this syntax was to make it more clear that type
      parameters used in the body originated from the object, not the trait.
      Over time, I realised that this doesn't actually help and in fact makes
      the compiler's life more difficult in a bunch of places. In this commit,
      we remove the need for specifying the type parameters, allowing you to
      write the following instead:
          impl Equal for Triple {
  18. 31 Jan, 2020 1 commit
    • Yorick Peterse's avatar
      Update Cargo files for Rust 1.41 · 4541d246
      Yorick Peterse authored
      Rust 1.41 introduces a new more compact format for Cargo.lock. This
      commit takes advantage of that change, and bumps some crate versions
      while we're at it.
  19. 23 Jan, 2020 1 commit
    • Yorick Peterse's avatar
      First-class modules and remove top-level object · 278a1652
      Yorick Peterse authored
      Modules are now first-class objects in the VM. This allows us to remove
      the top-level object, clean up core::bootstrap, and remove
      core::globals (in favour of using core::bootstrap directly). As part of
      this we also remove Trait.implement(), instead implementing traits using
      compiler generated code. Trait.implement() could not be safely used
      anyway, as it only copied over default methods; without verifying if all
      required methods are implemented.
      All of this makes it easier to implement the self-hosting compiler: the
      top-level object no longer needs to be supported, and we can treat
      core::bootstrap like any other module. This in turn allows us to use a
      more efficient way of storing type information in various places.
  20. 22 Jan, 2020 4 commits
    • Yorick Peterse's avatar
      Clean up Nil by splitting it into NilType and Nil · a97f10be
      Yorick Peterse authored
      NilType is now the base object/prototype that Nil is an instance of,
      instead of Nil being a direct instance of Object. This allows us to
      implement traits and redefine methods on the type of Nil (= NilType),
      instead of Nil itself. This in turn allows the compiler to use a more
      efficient way of representing types versus instances.
      In type signatures you can still use Nil (or NilType, though Nil is
      preferred), as this signals that you expect or return something
      compatible with Nil, which is only Nil itself.
    • Yorick Peterse's avatar
      Fixed broken/outdated compiler test · b3e718f6
      Yorick Peterse authored
    • Yorick Peterse's avatar
      Restore Boolean "if" instruction cleanup · ddedead0
      Yorick Peterse authored
      By introducing the virtual "if" instruction we can clean up Boolean so
      that we no longer have to refine the True and False instances. If we
      also clean up Nil in a similar way it becomes easier to rewrite the
      compiler in Inko, as it no longer needs to support the refining of
      random objects.
      This was originally introduced in
      ecd11a07, but later reverted. In this
      commit we re-introduce these changes. Refining True and False makes
      various compiler internals in the self-hosting compiler less efficient,
      as it has to support refining any object even when this only happens for
      True, False, and Nil. By cleaning up True and False we can reduce this
      down to just Nil, making it easier to find a way of cleaning that up
    • Yorick Peterse's avatar
      Don't use shared Windows runners · d30f885c
      Yorick Peterse authored
      With the release of shared Windows runners on GitLab.com, using runners
      with _just_ the "windows" tag results in CI using these shared runners.
      The shared runners don't have the right software installed, and are
      still in beta. To prevent using these, we now also require the runners
      to specify the "inko" tag.
  21. 11 Jan, 2020 1 commit
    • Yorick Peterse's avatar
      Remove support for constant receivers · f46694ef
      Yorick Peterse authored
      This removes support for syntax such as `A::B` where `A` and `B` are
      constants. Inko has not allowed defining nested types or constants in
      types for a while, and this syntax was only used in a few places to
      bootstrap the runtime. Removing support for this syntax simplifies both
      the parser and compiler quite a bit.
  22. 07 Jan, 2020 1 commit
  23. 05 Jan, 2020 2 commits
  24. 04 Jan, 2020 1 commit
    • Yorick Peterse's avatar
      Add Array.reverse_iter · f6bf5de0
      Yorick Peterse authored
      The method Array.reverse_iter returns an Iterator that iterates over the
      values in an Array in reverse order.
  25. 02 Jan, 2020 3 commits
    • Yorick Peterse's avatar
      Remove support for extending traits · 4af9354f
      Yorick Peterse authored
      Now that the Ruby compiler defines types using multiple passes, there no
      longer is a need for forward declaring and extending traits. Extending
      traits could also lead to odd behaviour when adding default methods. For
          trait A {}
          object Foo {}
          impl A for Foo {}
          trait A {
            def thing -> Integer {
      Here type "Foo" would not have access to method "thing", because it is
      added after "A" is implemented for "Foo".
      Removing support for this entirely solves this problem, and we no longer
      need it anyway.
    • Yorick Peterse's avatar
      Remove forward trait declaration from std::net::ip · 9f152024
      Yorick Peterse authored
      This is no longer needed now that the Ruby compiler defines types in
      multiple passes.
    • Yorick Peterse's avatar
      Rename a few AST nodes to use the pattern DefineX · 679ccf7b
      Yorick Peterse authored
      This ensures all these nodes use the same naming pattern
      (DefineAttribute, DefineMethod, etc), instead of sometimes being called
      DefineX while other times being called XDefinition.