Skip to content

Commit

Permalink
docs: Update documentation (#115)
Browse files Browse the repository at this point in the history
* C library definitions should allow naming overrides based on platform
Fixes #91

* Updates the documentation for the 0.2.0 release.
  • Loading branch information
markehammons authored Mar 25, 2023
1 parent 50de1f4 commit 1b6455f
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 66 deletions.
42 changes: 29 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,43 @@ It's designed to make use of Scala's type system, macros, and runtime multi-stag

## Quickstart

Slinc is published to Maven Central for Scala 3. It is built to take advantage of the cutting edge of Scala functionality and features, so your project will most certainly need to track the latest Scala version if you want to use the latest version of Slinc. Currently, the Scala version in use is `3.2.0`.
Slinc is published to Maven Central for Scala 3. It is built to take advantage of the cutting edge of Scala functionality and features, so your project will most certainly need to track the latest Scala version if you want to use the latest version of Slinc. Currently, the Scala version in use is `3.3.0-RC3`.

In order to test Slinc quick, one can use scala-cli:
### SBT setup

In your `build.sbt`:

```scala
libraryDependencies += "fr.hammons" %% "slinc-runtime" % "0.2.0"
//if forking and on Java 17
javaOptions ++= Seq("--add-modules=jdk.incubator.foreign", "--enable-native-access=ALL-UNNAMED")
```

in .jvmopts in the root of your build:
```
--add-modules=jdk.incubator.foreign
--enable-native-access=ALL-UNNAMED
```

For additional setup instructions, please refer to the configuration page.

Once you have your build system set up, you can create a new file and write the following code:

### test.scala
```scala
using lib "fr.hammons::slinc-runtime:0.1.1-72-1cedff"
import fr.hammons.slinc.runtime.given

import fr.hammons.slinc.runtime.{*,given}
case class div_t(quot: CInt, rem: CInt) derives Struct

case class div_t(quot: CInt, rem: CInt) derives Struct
trait MyLib derives Lib:
def div(numer: CInt, denom: CInt): div_t

object MyLib derives Library:
def abs(i: CInt): CInt = Library.binding
def div(numer: CInt, denom: CInt): div_t = Library.binding
val myLib = Lib.instance[MyLib]

@main def program =
println(MyLib.abs(-5)) // prints 5
println(MyLib.div(5,2)) // prints div_t(2,1)
@main def calc =
val (quot, rem) = Tuple.fromProduct(myLib.div(5,2))
println(s"Got a quotient of $quot and a remainder of $rem")
```

You can run this program with Java 19 via `scala-cli -j 19 -J --enable-native-access=ALL-UNNAMED test.scala` or with Java 17 via `scala-cli -j 17 -J --enable-native-access=ALL-UNNAMED -J --add-modules=jdk.incubator.foreign test.scala`.
This library relies on the user importing the runtime from `fr.hammons.slinc.runtime`.

To learn more about the library, refer to the documentation website for Slinc [here](https://slinc.hammons.fr/docs/index.html)
16 changes: 9 additions & 7 deletions core/docs/_docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ It's designed to make use of Scala's type system, macros, and runtime multi-stag

## Quickstart

Slinc is published to Maven Central for Scala 3. It is built to take advantage of the cutting edge of Scala functionality and features, so your project will most certainly need to track the latest Scala version if you want to use the latest version of Slinc. Currently, the Scala version in use is `3.2.1`.
Slinc is published to Maven Central for Scala 3. It is built to take advantage of the cutting edge of Scala functionality and features, so your project will most certainly need to track the latest Scala version if you want to use the latest version of Slinc. Currently, the Scala version in use is `3.3.0-RC3`.

### SBT setup

In your `build.sbt`:

```scala
libraryDependencies += "fr.hammons" %% "slinc-runtime" % "0.1.1-66-a2fa26-DIRTYd1a2c450"
libraryDependencies += "fr.hammons" %% "slinc-runtime" % "0.2.0"
//if forking and on Java 17
javaOptions ++= Seq("--add-modules=jdk.incubator.foreign", "--enable-native-access=ALL-UNNAMED")
```
Expand All @@ -31,15 +31,17 @@ For additional setup instructions, please refer to the configuration page.
Once you have your build system set up, you can create a new file and write the following code:

```scala
import fr.hammons.slinc.runtime.{*,given}
import fr.hammons.slinc.runtime.given

case class div_t(quot: Int, rem: Int) derives Struct
case class div_t(quot: CInt, rem: CInt) derives Struct

object MyLib derives Library:
def div(numer: Int, denom: Int): div_t = Library.binding
trait MyLib derives Lib:
def div(numer: CInt, denom: CInt): div_t

val myLib = Lib.instance[MyLib]

@main def calc =
val (quot, rem) = Tuple.fromProduct(MyLib.div(5,2))
val (quot, rem) = Tuple.fromProduct(myLib.div(5,2))
println(s"Got a quotient of $quot and a remainder of $rem")
```

Expand Down
61 changes: 61 additions & 0 deletions core/docs/_docs/reference/library-modules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
title: "Library Modules"
---

Library modules are groupings of functions in Slinc. Each can be configured in a number of ways.

A library module is a Scala `trait` that derives the `Lib` type class. Method declarations within it are used as a template for the bindings to C that the Slinc runtime will generate.

## Module Declarations

Module declarations are declarations of C library bindings without any entanglement in runtime details. This means that a module declaration only requires the use of the Slinc core library, and does not effect the runtime requirements of users of the module.

### Module naming

The type name of the module does not matter to Slinc.

### Method naming

The name of method declarations within a library module should reflect the name of the C function you wish to bind to. If a method name is not found in the C namespace, this will cause a runtime error when you try to summon the module implementation.

Example:

```scala
trait L derives Lib:
def abs(i: CInt): CInt
```

This library module `L` has a binding to `abs` in the C standard library namespace.

#### Naming overrides

You can override the C name lookup for a method declaration on a host dependent basis with the `@NameOverride` annotation. You provide the `@NameOverride` annotation with the alternative name, and an OS and architecture tuple where the name should be used. An example follows:

```scala
trait L derives Lib:
@NameOverride("_time64", OS.Windows -> Arch.X64)
def time(timer: Ptr[TimeT]): TimeT
```

This function is helpful when you have a function symbol that should be present on a platform, but has an alternative name for some reason. In the case with Windows, the `time` function in the C standard library is a macro that points to `_time64` on 64-bit platforms, and `_time32` on 32-bit platforms. Since macros do not exist as symbols in the C standard library namespace, this `NameOverride` makes the Slinc platform choose the right function name on Windows X64.

## Summoning Module Implementations

Module declarations have been shown above, but they are not useable without being summoned. Doing so requires the Slinc runtime on your classpath, and makes the JAR and class files generated dependent on a specific JVM.

To summon a module implementation, you use the `Lib.instance[?]` method as shown in the following example:

```scala
import fr.hammons.slinc.types.CInt
import fr.hammons.slinc.Lib
import fr.hammons.slinc.runtime.given

trait L derives Lib:
def abs(i: CInt): CInt

val l = Lib.instance[L]

@main def program = println(l.abs(4))
```

Note the assignment of the instance to a `val`. This is not strictly necessary, and `Lib.instance` will always return the same module instance, but re-summoning is more expensive than storing the summoned module implementation.
53 changes: 7 additions & 46 deletions core/docs/_docs/reference/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ title: Usage

## Introduction to Library Definitions

All bindings by Slinc take place in library objects, groups of like methods that reflect C functions. When defining a library object, name is not particularly important. One merely defines an object that derives `Library`. Method bindings are done via a method binding with a name matching the C function in question, and parameters with types that match the C function in question.
All bindings by Slinc take place in library traits, groups of like methods that reflect C functions. When defining a library trait, name is not particularly important. One merely defines an object that derives `Lib`. Method bindings are done via a method binding with a name matching the C function in question, and parameters with types that match the C function in question.

```scala
object StdLib derives Library:
def abs(i: Int): Int = Library.binding
trait StdLib derives Lib:
def abs(i: CInt): CInt
```

The above defines a binding to the C standard library's `abs` method. Since the C definition of `abs` is defined like `int abs(int i)`, the scala version of the method is defined with a single input that has the type `Int` and the return type `Int`. The method is defined with `Library.binding` which is a macro that connects the inputs and outputs to the C world.
The above defines a binding to the C standard library's `abs` method. Since the C definition of `abs` is defined like `int abs(int i)`, the scala version of the method is defined with a single input that has the type `CInt` and the return type `CInt`.

## Types

Expand Down Expand Up @@ -45,52 +45,13 @@ Using the built in JVM primitives for a binding is quick and easy if you know th
|TimeT|time_t|


Since these types are only guaranteed to be defined at runtime, interacting with them can be difficult. There are three ways at present to interact with these types:

* Assured Conversion
* Potential Conversion
* Platform focus

### Assured Conversion

Assured conversion is done via the `.as` extension method on a compatible type, or to a compatible type. This method is generally available when there's some baseline guarantee about the kind and width of the type in question. For example, `CLong` is at minimum a 32-bit wide integer type, so `Int.as[CLong]` exists. It is also (for now) at maximum a 64-bit wide integer type, so `CLong.as[Long]` exists.

### Potential Conversion

Potential conversion is generally more available than the assured conversions. Potential conversion is done via the `maybeAs` extension method that will return an `Option` result indicating whether the conversion is valid on the current host.

As an example, `Long.maybeAs[CLong]` will return `Some(l: CLong)` on X64 Linux, but `None` on X64 Windows.

### Platform Focus

The `platformFocus` method takes a platform instance and a section of code where the host dependent types have automatic conversions to and from their definitions on the host platform. As an example:

```scala
def labs(l: CLong): CLong = Library.binding

platformFocus(x64.Linux){
//this line works here
labs(-13l) == -13l //true
}

//this line doesn't work here, cause outside of the above zone
// CLong isn't equivalent to Long
labs(-13l) == -13l
```

The `platformFocus` method returns `Option[A]` where `A` is the return type of the platform focus zone. The method returns `None` if the platform selected for the zone doesn't match the host. This allows you to chain together platform specific code via `.orElse`.

The C types are meant to be analogues to the primitive types defined for C. In the table above, a number have equivalents to JVM types right now, but that may change in future versions of Slinc. If your wish is to write platform independent bindings to C libraries, then you should use the C types and forgo the standard JVM primitives. Usage of the standard JVM primitives will make your bindings brittle and platform specific at some point.
Since these types are only guaranteed to be defined at runtime, interacting with them can be difficult.

## Pointers

Pointers are represented in Slinc with the `Ptr` type. For example, `Ptr[Int]` is a pointer to native memory that should be readable as a JVM Int.

The pointer class' operations are powered by three type classes:

* `LayoutOf` - this type class provides layout information about the type in question, if it exists.
* `Send` - this type class shows how to copy data of type `A` from the JVM into native memory.
* `Receive` - this type class shows how to copy data of type `A` from native memory into the JVM heap.
The pointer class' operations are powered by the `DescriptorOf` typeclass.

The list of operations available on a `Ptr[A]`:

Expand All @@ -110,4 +71,4 @@ The analog for C structs in Slinc are case classes that derive the `Struct` type
case class div_t(quot: Int, rem: Int) derives Struct
```

These struct analogs can be composed with any type that has a Send and/or Receive defined for it.
These struct analogs can be composed with any type that has a `DescriptorOf` defined for it.
1 change: 1 addition & 0 deletions core/docs/sidebar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ subsection:
subsection:
- page: reference/usage.md
- page: reference/jdk-support.md
- page: reference/library-modules.md
- title: Contributing
directory: docs/contributing
page: contributing/contributing.md
1 change: 1 addition & 0 deletions core/src/fr/hammons/slinc/types/Basic.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ type CShort = Short
type CInt = Int
type CFloat = Float
type CDouble = Double
type CLongLong = Long

0 comments on commit 1b6455f

Please sign in to comment.