Instantly share code, notes, and snippets.

# sigilante/jetting-tutorial.md Secret

Last active August 11, 2021 02:44
Show Gist options
• Save sigilante/31cc7941a41b51dca5db6692a756fd64 to your computer and use it in GitHub Desktop.
Jetting Tutorial

# Supporting Urbit: Composing Jets

~lagrev-nocfep / Sigilante / Neal Davis

The permanent version of this gist has moved to `tutorials/jetting.md`.

## Objectives

The goals of this tutorial are for you to be able to:

1. Read existing jet code.
2. Produce a jet matching a Hoon gate with a single argument.
3. Produce a more complex jet involving multiple values and floating-point arithmetic.

A fully-implemented version of the `++factorial` jet is available at `natareo/urbit`.

## Jet Walkthrough: `++add`

Given a Hoon gate, how can a developer produce a matching C jet? Let us illustrate the process using a simple `|%` core. We assume the reader has achieved facility with both Hoon code and C code. This tutorial aims to communicate the practical process of producing a jet, and many `u3` noun concepts are only briefly discussed or alluded to.

To this end, we begin by examining the Hoon `++add` gate, which accepts two values in its sample.

The Hoon code for `++add` decrements one of these values and adds one to the other for each decrement until zero is reached. This is because all atoms in Hoon are unsigned integers and Nock has no simple addition operation. The source code for `++add` is located in `hoon.hoon`:

```|%
+|  %math
::
::  a: augend
|=  [a=@ b=@]
::  sum
^-  @
?:  =(0 a)  b
\$(a (dec a), b +(b))```

or in a more compact form (omitting the parent core and chapter label)

```++  add
|=  [a=@ b=@]  ^-  @
?:  =(0 a)  b
\$(a (dec a), b +(b))```

The jet hint `%add` allows Hoon to hint to the runtime that a jet may exist. By convention, the jet hint name matches the gate label. Jets must be registered elsewhere in the runtime source code for the Vere binary to know where to connect the hint; we elide that discussion until we take a look at jet implementation below.

The following C code implements `++add` as a significantly faster operation including handling of >31-bit atoms. It may be found in `urbit/pkg/urbit/jets/a/add.c`:

```u3_noun
u3_atom b)
{
if ( _(u3a_is_cat(a)) && _(u3a_is_cat(b)) ) {
c3_w c = a + b;

return u3i_words(1, &c);
}
else if ( 0 == a ) {
return u3k(b);
}
else {
mpz_t a_mp, b_mp;

u3r_mp(a_mp, a);
u3r_mp(b_mp, b);

mpz_clear(b_mp);

return u3i_mp(a_mp);
}
}
u3_noun
{
u3_noun a, b;

if ( (c3n == u3r_mean(cor, u3x_sam_2, &a, u3x_sam_3, &b, 0)) ||
(c3n == u3ud(a)) ||
(c3n == u3ud(b) && a != 0) )
{
return u3m_bail(c3__exit);
} else {
}
}```

The main entry point for a call into the function is `u3wa_add`. `u3w` functions are translator functions which accept the entire sample as a `u3_noun` (or Nock noun). `u3q` functions take custom combinations of nouns and atoms and generally correspond to unpacked samples.

`u3wa_add` defines two nouns `a` and `b` which will hold the unpacked arguments from the sample. The sample elements are copied out by reference into `a` from sample address 2 (`u3x_sam_2`) and into `b` from sample address 3 (`u3x_sam_3`). A couple of consistency checks are made; if these fail, `u3m_bail` yields a runtime error. Else `u3qa_add` is invoked on the C-style arguments.

`u3qa_add` has the task of adding two Urbit atoms. There is a catch, however! An atom may be a direct atom (meaning the value as an unsigned integer fits into 31 bits) or an indirect atom (anything higher than that). Direct atoms, called `cat`s, are indicated by the first bit being zero.

``````0ZZZ.ZZZZ.ZZZZ.ZZZZ.ZZZZ.ZZZZ.ZZZZ.ZZZZ
``````

Any atom value which may be represented as \$2^{31}-1 = 2.147.483.647\$ or less is a direct atom. The `Z` bits simply contain the value.

```> `@ub`2.147.483.647
0b111.1111.1111.1111.1111.1111.1111.1111
> `@ux`2.147.483.647
0x7fff.ffff```

However, any atom with a value greater than this (including many cords, floating-point values, etc.) is an indirect atom (or `dog`) marked with a prefixed bit of one.

``````11YX.XXXX.XXXX.XXXX.XXXX.XXXX.XXXX.XXXX
``````

where bit 31 indicates indirectness, bit 30 is always set, and bit 29 (`Y`) indicates if the value is an atom or a cell. An indirect atom contains a pointer into the loom from bits 0–28 (bits `X`).

What does this mean for `u3qa_add`? It means that if the atoms are both direct atoms (`cat`s), the addition is straightforward and simply carried out in C. When converted back into an atom, a helper function `u3i_words` deals with the possibility of overflow and the concomitant transformation to a `dog`.

```c3_w c = a + b;               # c3_w is a 32-bit C word.

return u3i_words(1, &c);```

There's a second trivial case to handle one of the values being zero. (It is unclear to the author of this tutorial why both cases as-zero are not being handled; the speed change may be too trivial to matter.)

Finally, the general case of adding the values at two loom addresses is dealt with. This requires general pointer-based arithmetic with GMP multi-precision integer operations.

```mpz_t a_mp, b_mp;             # mpz_t is a GMP multi-precision integer type

u3r_mp(a_mp, a);              # read the atoms out of the loom into the MP type
u3r_mp(b_mp, b);

mpz_add(a_mp, a_mp, b_mp);    # carry out MP-correct addition
mpz_clear(b_mp);              # clear the now-unnecessary `b` value from memory

return u3i_mp(a_mp);          # write the value back into the loom and return it```

The procedure to solve the problem in the C jet does not need to follow the same algorithm as the Hoon code.

In general, jet code feels a bit heavy and formal. Jet code may call other jet code, however, so much as with Hoon layers of complexity can be appropriately encapsulated. Once you are used to the conventions of the u3 library, you will be in a good position to produce working and secure jet code.

## Jet Composition: Integer `++factorial`

Similar to how we encountered recursion way back in Hoon School to talk about gate mechanics, let us implement a C jet of the `++factorial` example code. We will call this library `trig` in a gesture to some subsequent functions you should implement as an exercise. Create a file `lib/trig.hoon` with the following contents:

```~%  %trig  ..part  ~
|%
:: Factorial, \$x!\$
::
++  factorial
~/  %factorial
|=  x=@ud  ^-  @ud
=/  t=@ud  1
|-  ^-  @rs
?:  =(x 0)  t
?:  =(x 1)  t
\$(x (sub x 1), t (mul t x))
--```

We will create a generator `gen/trig.hoon` which will help us quickly check the library's behavior.

``````/+  *trig
!:
:-  %say
|=  [[* eny=@uv *] [x=@rs n=@rs ~] ~]
::
~&  (factorial n)
~&  (absolute x)
~&  (exp x)
~&  (pow-n x n)
[%verb ~]
``````

We will further define a few unit tests as checks on arm behavior in `tests/lib/trig.hoon`:

```/+  *test, *trig
::
::::
::
|%
++  test-factorial  ^-  tang
;:  weld
%+  expect-eq
!>  1
!>  (factorial 0)
%+  expect-eq
!>  1
!>  (factorial 1)
%+  expect-eq
!>  120
!>  (factorial 5)
%+  expect-eq
!>  720
!>  (factorial 6)
==
--```

### Mise en place

Jet development can require frequent production of new Urbit binaries and , so let us pay attention to our working affordances. We will primarily work in the fakezod on the two files just mentioned, and in the `pkg/urbit` directory of the main Urbit repository, so we need a development process that allows us to quickly access each of these, move them into the appropriate location, and build necessary components. The basic development cycle will look like this:

1. Compose correct Hoon code.

2. Hint the Hoon code.

3. Register the jets in the Vere C code.

4. Compose the jets.

5. Compile and troubleshoot.

6. Repeat as necessary.

You should consider using a terminal utility like `tmux` or `screen` which allows you to work in several locations on your file system simultaneously: one for file system operations (copying files in and out of the `home` directory), one for running the fakezod, and one for editing the files, or an IDE or text editor if preferred.

To start off, you should obtain a clean up-to-date copy of the Urbit repository, available on GitHub at `github.com/urbit/urbit`. Make a working directory `~/tlon`. Create a new branch within the repo named `trigjet`:

```cd
mkdir tlon
cd tlon
git clone https://github.com/urbit/urbit.git
cd urbit
git branch trigjet```

Test your build process to produce a local executable binary of Vere:

`make`

This invokes Nix to build the Urbit binary. Take note of where that binary is located (typically in `/tmp` on your main file system) and create a new fakezod using a downloaded pill.

```cd ..
wget https://bootstrap.urbit.org/urbit-v1.5.pill
<Nix build path>/bin/urbit -B urbit-v1.5.pill -F zod```

Inside of that fakezod, sync `%clay` to Unix,

`|mount %`

Then copy the entire `home` desk out so that you can work with it and copy it back in as necessary.

`cp -r zod/home .`

Save the foregoing library code in `home/lib` and the generator code in `home/gen`; also, don't forget the unit tests! Whenever you work in your preferred editor, you should work on the `home` copies, then move them back into the fakezod and synchronize before execution.

`cp -r home zod`
`|commit %home`

### Jet construction

Now that you have a developer cycle in place, let's examine what's necessary to produce a jet. A jet is a C function which replicates the behavior of a Hoon (Nock) gate. Jets have to be able to manipulate Urbit quantities within the binary, which requires both the proper affordances within the Hoon code (the interpreter hints) and support for manipulating Urbit nouns (atoms and cells) within C.

Jet hints must provide a trail of symbols for the interpreter to know how to match the Hoon arms to the corresponding C code. Think of these as breadcrumbs. The current jetting documentation demonstrates a three-deep jet; here we have a two-deep scenario. Specifically, we mark the outermost arm with `~%` and an explicit reference to the Arvo core (the parent of `part`). We mark the inner arms with `~/` because their parent symbol can be determined from the context. The `@tas` token will tell Vere which C code matches the arm. All symbols in the nesting hierarchy must be included.

```~%  %trig  ..part  ~
|%
++  factorial
~/  %factorial
|=  x=@ud  ^-  @ud
...
--```

We also need to add appropriate handles for the C code. This consists of several steps:

1. Register the jet symbols and function names in `tree.c`.

2. Declare function prototypes in headers `q.h` and `w.h`.

3. Produce functions for compilation and linking in the `pkg/urbit/jets/e` directory.

The first two steps are fairly mechanical and straightforward.

Register the jet symbols and function names. A jet registration may be carried out at in point in `tree.c`. The registration consists of marking the core

``````/* Jet registration of ++factorial arm under trig */
static u3j_harm _140_hex__trig_factorial_a[] = {{".2", u3we_trig_factorial, c3y}, {}};
/* Associated hash */
static c3_c* _140_hex__trig_factorial_ha[] = {
0
};

static u3j_core _140_hex__trig_d[] =
{ { "factorial", 7, _140_hex__trig_factorial_a, 0, _140_hex__trig_factorial_ha },
{}
};
static c3_c* _140_hex__trig_ha[] = {
"0bac9c3c43634bb86f6721bbcc444f69c83395f204ff69d3175f3821b1f679ba",
0
};

/* Core registration by token for trig */
static u3j_core _140_hex_d[] =
{ /* ... pre-existing jet registrations ... */
{ "trig",   31, 0, _140_hex__trig_d, _140_hex__trig_ha  },
{}
};
``````

The numeric component of the title, `140`, indicates the Hoon Kelvin version. Library jets of this nature are registered as `hex` jets, meaning they live within the Arvo core. Other, more inner layers of `%zuse` and `%lull` utilize `pen` and other three-letter jet tokens. The core is conventionally included here, then either a `d` suffix for the function association or a `ha` suffix for a jet hash. (Jet hashes are a way of “signing” code. They are not as of this writing actively used by the binary runtimes.)

The particular flavor of C mandated by the Vere kernel is quite lapidary, particularly when shorthand functions (such as `u3z`) are employed. In this code, we see the following `u3` elements:

1. `c3_c`, the platform C 8-bit `char` type

2. `c3y`, loobean true, `%.y` (similarly `c3n`, loobean false, `%.n`)

3. `u3j_core`, C representation of Hoon/Nock cores

4. `u3j_harm`, an actual C jet ("Hoon arm")

The numbers `7` and `31` refer to relative core addresses. In most cases---unless you're building a particularly complicated jet or modifying `%zuse` or `%lull`---you can follow the pattern laid out here. `".2"` is a label for the axis in the core `[battery sample]`, so just the battery. The text labels for the `|%` core and the arm are included at their appropriate points. Finally, the jet function entry point `u3we_trig_factorial` is registered.

For more information on u3, please check out the u3 summary below or the official documentation at “u3: Land of Nouns”.

Declare function prototypes in headers.

A `u3w` function is always the entry point for a jet. Every `u3w` function accepts a `u3noun` (a Hoon/Nock noun), validates it, and invokes the `u3q` function that implements the actual logic. The `u3q` function needs to accept the same number of atoms as the defining arm (since these same values will be extricated by the `u3w` function and passed to it).

In this case, we have cited `u3we_trig_factorial` in `tree.c` and now must declare both it and `u3qe_trig_factorial`:

In `w.h`:

``````u3_noun u3we_trig_factorial(u3_noun);
``````

In `q.h`:

``````u3_noun u3qe_trig_factorial(u3_atom);
``````

Produce functions for compilation and linking.

Given these function prototype declarations, all that remains is the actual definition of the function. Both functions will live in their own file; we find it the best convention to associate all arms of a core in a single file. In this case, create a file `pkg/urbit/jets/e/trig.c` and define all of your `trig` jets therein. (Here we show `++factorial` only.)

As with `++add`, we have to worry about direct and indirect atoms when carrying out arithmetic operations, prompting the use of GMP `mpz` operations.

``````/* jets/e/trig.c
**
*/
#include "all.h"
#include <stdio.h>      // helpful for debugging, removable after development

/* factorial of @ud integer
*/
u3_noun
u3qe_trig_factorial(u3_atom a)  /* @ud */
{
fprintf(stderr, "u3qe_trig_factorial\n\r");  // DELETE THIS LINE LATER
if (( 0 == a ) || ( 1 == a )) {
return 1;
}
else if ( _(u3a_is_cat(a))) {
c3_d c = ((c3_d) a) * ((c3_d) (a-1));

return u3i_chubs(1, &c);
}
else {
mpz_t a_mp, b_mp;

u3r_mp(a_mp, a);
mpz_sub(b_mp, a_mp, 1);
u3_atom b = u3qe_trigrs_factorial(u3i_mp(b_mp));
u3r_mp(b_mp, b);

mpz_mul(a_mp, a_mp, b_mp);
mpz_clear(b_mp);

return u3i_mp(a_mp);
}
}

u3_noun
u3we_trig_factorial(u3_noun cor)
{
fprintf(stderr, "u3we_trig_factorial\n\r");  // DELETE THIS LINE LATER
u3_noun a;

if ( c3n == u3r_mean(cor, u3x_sam, &a, 0) ||
c3n == u3ud(a) )
{
return u3m_bail(c3__exit);
}
else {
return u3qe_trig_factorial(a);
}
}
``````

This code merits ample discussion. Without focusing on the particular types used, read through the logic and look for the skeleton of a standard simple factorial algorithm.

`u3r` operations are used to extract Urbit-compatible types as C values.

`u3i` operations wrap C values back into Urbit-compatible types.

## u3 Overview

Before proceeding to compose a more complicated floating-point jet, we should step back and examine the zoo of u3 functions that jets use to formally structure atom access and manipulation.

#### `u3` functions.

`u3` defines a number of functions for extracting data from Urbit types into C types for ready manipulation, then wrapping those same values back up for Urbit to handle. These fall into several categories:

Prefix Mnemonic Source File Example of Function
`u3a_` Allocation `allocate.c` `u3a_malloc`
`u3e_` Event (persistence) `events.c` `u3e_foul`
`u3h_` Hash table `hashtable.c` `u3h_put`
`u3i_` Imprisonment (noun construction) `imprison.c`
`u3j_` Jet control `jets.c` `u3j_boot`
`u3k_` Jets (transfer semantics, C arguments) `[a-g]/*.c`
`u3l_` Logging `log.c` `u3l_log`
`u3m_` System management `manage.c` `u3m_bail`
`u3n_` Nock computation `nock.c` `u3nc`
`u3q_` Jets (retain semantics, C arguments) `[a-g]/*.c`
`u3r_` Retrieval; returns on error `retrieve.c` `u3r_word`
`u3t_` Profiling and tracing `trace.c` `u3t`
`u3v_` Arvo operations `vortex.c` `u3v_reclaim`
`u3w_` Jets (retain semantics, Nock core argument) `[a-g]/*.c`
`u3x_` Retrieval; crashes on error `xtract.c` `u3x_cell`
`u3z_` Memoize `zave.c` `u3z_uniq`

#### `u3` nouns.

The `u3` system allows you to extract Urbit nouns as atoms or cells. Atoms may come in one of two forms: either they fit in 31 bits or less of a 32-bit unsigned integer, or they require more space. In the former case, you will use the singular functions such as `u3r_word` and `u3a_word` to extract and store information. If the atom is larger than this, however, you need to treat it a bit more like a C array, using the plural functions `u3r_words` and `u3a_words`. (For native sizes larger than 32 bits, such as double-precision floating-point numbers, replace `word` with `chub` in these.)

An audit of the jet source code shows that the most commonly used `u3` functions include:

1. `u3a_free` frees memory allocated on the loom (Vere memory model).

2. `u3a_malloc` allocates memory on the loom (Vere memory model). (Never use regular C `malloc` in `u3`.)

3. `u3i_bytes` writes an array of bytes into an atom.

4. `u3i_chub` is the ≥32-bit equivalent of `u3i_word`.

5. `u3i_chubs` is the ≥32-bit equivalent of `u3i_words`.

6. `u3i_word` writes a single 31-bit or smaller atom.

7. `u3i_words` writes an array of 31-bit or smaller atoms.

8. `u3m_bail` produces an error and crashes the process.

9. `u3m_p` prints a message and a `u3` noun.

10. `u3r_at` retrieves data values stored at locations in the sample.

11. `u3r_byte` retrieves a byte from within an atom.

12. `u3r_bytes` retrieves multiple bytes from within an atom.

13. `u3r_cell` produces a cell `[a b]`.

14. `u3r_chub` is the >32-bit equivalent of `u3r_word`.

15. `u3r_chubs` is the >32-bit equivalent of `u3r_words`.

16. `u3r_mean` deconstructs a noun by axis address.

17. `u3r_met` reports the total size of an atom.

18. `u3r_trel` factors a noun into a three-element cell `[a b c]`.

19. `u3r_word` retrieves a value from an atom as a C `uint32_t`.

20. `u3r_words` is the multi-element (array) retriever like `u3r_word`.

#### u3 samples.

Defining jets which have a different sample size requires querying the correct nodes of the sample as binary tree:

``````1.  1 argument → `u3x_sam`

2.  2 arguments → `u3x_sam_2`, `u3x_sam_3`

3.  3 arguments → `u3x_sam_2`, `u3x_sam_6`, `u3x_sam_7`

4.  4 arguments → `u3x_sam_2`, `u3x_sam_6`, `u3x_sam_14`, `u3x_sam_15`

5.  5 arguments → `u3x_sam_2`, `u3x_sam_6`, `u3x_sam_14`, `u3x_sam_30`,
`u3x_sam_31`

6.  6 arguments → `u3x_sam_2`, `u3x_sam_6`, `u3x_sam_14`, `u3x_sam_30`,
`u3x_sam_62`, `u3x_sam_63`
``````

A more complex argument structure requires grabbing other entries; e.g.,

`|=  [u=@lms [ia=@ud ib=@ud] [ja=@ud jb=@ud]]`

requires

`u3x_sam_2, u3x_sam_12, u3x_sam_13, u3x_sam_14, u3x_sam_15`

## Jet Composition: Floating-Point `++factorial`

Let us examine jet composition using a more complicated floating-point operation. The Urbit runtime uses SoftFloat to provide a reference software implementation of floating-point mathematics. This is slower than hardware FP but more portable.

This library `lib/trig-rs.hoon` provides a few transcendental functions useful in many mathematical calculations. The `~%` "sigcen" rune registers the jets (with explicit arguments, necessary at the highest level of inclusion). The `~/` "sigfas" rune indicates which arms will be jetted.

```::  Transcendental functions library, compatible with @rs
::
=/  tau  .6.28318530717
=/  pi   .3.14159265358
=/  e    .2.718281828
=/  rtol  .1e-5
~%  %trig  ..part  ~
|%
:: Factorial, \$x!\$
::
++  factorial
~/  %factorial
|=  x=@rs  ^-  @rs
=/  t=@rs  .1
|-  ^-  @rs
?:  =(x .0)  t
?:  =(x .1)  t
\$(x (sub:rs x .1), t (mul:rs t x))
:: Absolute value, \$|x|\$
::
++  absolute
|=  x=@rs  ^-  @rs
?:  (gth:rs x .0)
x
(sub:rs .0 x)
:: Exponential function, \$\exp(x)\$
::
++  exp
~/  %exp
|=  x=@rs  ^-  @rs
=/  rtol  .1e-5
=/  p   .1
=/  po  .-1
=/  i   .1
|-  ^-  @rs
?:  (lth:rs (absolute (sub:rs po p)) rtol)
p
\$(i (add:rs i .1), p (add:rs p (div:rs (pow-n x i) (factorial i))), po p)
:: Integer power, \$x^n\$
::
++  pow-n
~/  %pow-n
|=  [x=@rs n=@rs]  ^-  @rs
?:  =(n .0)  .1
=/  p  x
|-  ^-  @rs
?:  (lth:rs n .2)
p
::~&  [n p]
\$(n (sub:rs n .1), p (mul:rs p x))
--```

We will create a generator which will pull the arms and slam each gate such that we can assess the library's behavior. Later on we will create unit tests to validate the behavior of both the unjetted and jetted code.

``````/+  *trig-rs
!:
:-  %say
|=  [[* eny=@uv *] [x=@rs n=@rs ~] ~]
::
~&  (factorial n)
~&  (absolute x)
~&  (exp x)
~&  (pow-n x n)
[%verb ~]
``````

We will further define a few unit tests as checks on arm behavior:

``````/+  *test, *trig-rs
::
::::
::
|%
++  test-factorial  ^-  tang
;:  weld
%+  expect-eq
!>  .1
!>  (factorial .0)
%+  expect-eq
!>  .1
!>  (factorial .1)
%+  expect-eq
!>  .120
!>  (factorial .5)
%+  expect-eq
!>  .720
!>  (factorial .6)
==
--
``````
##### Jet composition.

As before, the jet hints must provide a trail of symbols for the interpreter to know how to match the Hoon arms to the corresponding C code.

``````~%  %trig-rs  ..part  ~
|%
++  factorial
~/  %factorial
|=  x=@rs  ^-  @rs
...
++  exp
~/  %exp
|=  x=@rs  ^-  @rs
...
++  pow-n
~/  %pow-n
|=  [x=@rs n=@rs]  ^-  @rs
...
--
``````

As before:

1. Register the jet symbols and function names in `tree.c`.

2. Declare function prototypes in headers `q.h` and `w.h`.

3. Produce functions for compilation and linking in the `pkg/urbit/jets/e` directory.

Register the jet symbols and function names. A jet registration may be carried out at in point in `tree.c`. The registration consists of marking the core

``````/* Jet registration of ++factorial arm under trig-rs */
static u3j_harm _140_hex__trigrs_factorial_a[] = {{".2", u3we_trigrs_factorial, c3y}, {}};
/* Associated hash */
static c3_c* _140_hex__trigrs_factorial_ha[] = {
0
};

static u3j_core _140_hex__trigrs_d[] =
{ { "factorial", 7, _140_hex__trigrs_factorial_a, 0, _140_hex__trigrs_factorial_ha },
{}
};
static c3_c* _140_hex__trigrs_ha[] = {
"0bac9c3c43634bb86f6721bbcc444f69c83395f204ff69d3175f3821b1f679ba",
0
};

/* Core registration by token for trigrs */
static u3j_core _140_hex_d[] =
{ /* ... pre-existing jet registrations ... */
{ "trig-rs",   31, 0, _140_hex__trigrs_d, _140_hex__trigrs_ha  },
{}
};
``````

Declare function prototypes in headers.

We must declare `u3we_trigrs_factorial` and `u3qe_trigrs_factorial`:

In `w.h`:

``````u3_noun u3we_trigrs_factorial(u3_noun);
``````

In `q.h`:

``````u3_noun u3qe_trigrs_factorial(u3_atom);
``````

Produce functions for compilation and linking.

Given these function prototype declarations, all that remains is the actual definition of the function. Both functions will live in their own file; we find it the best convention to associate all arms of a core in a single file. In this case, create a file `pkg/urbit/jets/e/trig-rs.c` and define all of your `trig-rs` jets therein. (Here we show `++factorial` only.)

``````/* jets/e/trig-rs.c
**
*/
#include "all.h"
#include <softfloat.h>  // necessary for working with software-defined floats
#include <stdio.h>      // helpful for debugging, removable after development
#include <math.h>       // provides library fabs() and ceil()

union sing {
float32_t s;    //struct containing v, uint_32
c3_w c;         //uint_32
float b;        //float_32, compiler-native, useful for debugging printfs
};

/* ancillary functions
*/
bool isclose(float a,
float b)
{
float atol = 1e-6;
return ((float)fabs(a - b) <= atol);
}

/* factorial of @rs single-precision floating-point value
*/
u3_noun
u3qe_trigrs_factorial(u3_atom u)  /* @rs */
{
fprintf(stderr, "u3qe_trigrs_factorial\n\r");  // DELETE THIS LINE LATER
union sing a, b, c, e;
u3_atom bb;
a.c = u3r_word(0, u);  // extricate value from atom as 32-bit word

if (ceil(a.b) != a.b) {
// raise an error if the float has a nonzero fractional part
return u3m_bail(c3__exit);
}

if (isclose(a.b, 0.0)) {
a.b = (float)1.0;
return u3i_words(1, &a.c);
}
else if (isclose(a.b, 1.0)) {
a.b = (float)1.0;
return u3i_words(1, &a.c);
}
else {
// naive recursive algorithm
b.b = a.b - 1.0;
bb = u3i_words(1, &b.c);
c.c = u3r_word(0, u3qe_trig_factorial(bb));
e.s = f32_mul(a.s, c.s);
u3m_p("result", u3i_words(1, &e.c));  // DELETE THIS LINE LATER
return u3i_words(1, &e.c);
}
}

u3_noun
u3we_trigrs_factorial(u3_noun cor)
{
fprintf(stderr, "u3we_trigrs_factorial\n\r");  // DELETE THIS LINE LATER
u3_noun a;

if ( c3n == u3r_mean(cor, u3x_sam, &a, 0) ||
c3n == u3ud(a) )
{
return u3m_bail(c3__exit);
}
else {
return u3qe_trigrs_factorial(a);
}
}
``````

This code deviates from the integer implementation in two ways: because all `@rs` atoms are guaranteed to be 32-bits, we can assume that `c3_w` can always contain them; and we are using software-defined floating-point operations with SoftFloat.

We have made use of `u3r_word` to convert a 32-bit (really, 31-bit or smaller) Hoon atom (`@ud`) into a C `uint32_t` or `c3_w`. This unsigned integer may be interpreted as a floating-point value (similar to a cast to `@rs`) by the expedient of a C `union`, which allows multiple interpretations of the same bit pattern of data; in this case, as an unsigned integer, as a SoftFloat `struct`, and as a C single-precision `float`.

`f32_mul` and its sisters (`f32_add`, `f64_mul`, `f128_div`, etc.) are floating-point operations defined in software. These are not as efficient as native hardware operations would be, but allow Urbit to guarantee cross-platform compatibility of operations and not rely on hardware-specific implementations. Currently all Urbit floating-point operations involving `@r` values use SoftFloat.

### Compiling and using the jet.

With this one jet for `++factorial` in place, compile the jet and take note of where Nix produces the binary.

`make`

Copy the affected files back into the ship's pier:

```cp home/lib/trig-rs.hoon zod/home/lib
cp home/gen/trig-rs.hoon zod/home/gen```

Restart your fakezod using the new Urbit binary and synchronize these to the `%home` desk:

`|commit %home`

If all has gone well to this point, you are prepared to test the jet using the `%say` generator from earlier:

`+trig 5`

Among the other output values, you should observe the `stderr` messages emitted by the jet functions each time they are called.

1. The type union remains necessary to easily convert the floating-point result back into an unsigned integer atom.
``````/* integer power of @rs single-precision floating-point value
*/
u3_noun
u3qe_trigrs_pow_n(u3_atom x,  /* @rs */
u3_atom n)  /* @rs */
{
fprintf(stderr, "u3qe_trig_pow_n\n\r");
union sing x_, n_, f_;
x_.c = u3r_word(0, x);  // extricate value from atom as 32-bit word
n_.c = u3r_word(0, n);

f_.b = (float)pow(x_, n_);

return u3i_words(1, &f_.c);
}

u3_noun
u3w_trigrs_pow_n(u3_noun cor)
{
fprintf(stderr, "u3w_trig_pow_n\n\r");
u3_noun a, b;

if ( c3n == u3r_mean(cor, u3x_sam_2, &a,
u3x_sam_3, &b, 0) ||
c3n == u3ud(a) || c3n == u3ud(b) )
{
return u3m_bail(c3__exit);
}
else {
return u3q_trigrs_pow_n(a, b);
}
}
``````

We leave the implementation of the other jets to the reader as an exercise. (Please do not skip this: the exercise will both solidify your understanding and raise new important situational questions.)

Again, the C jet code need not follow the same logic as the Hoon source code; in this case, we simply use the built-in `math.h` `pow` function. (We could—arguably should—have used SoftFloat functions.)

## Pills.

A pill is a Nock "binary blob", really a parsed Hoon abstract syntax tree. Pills are used to bypass the bootstrapping procedure for a new ship, and are particularly helpful when jetting code in `hoon.hoon`, `%zuse`, `%lull`, or the main Arvo vanes.

The legacy jetting tutorial contains specific instructions on how to compile a pill and work with a more rapid setup for jetting with fakezods.

You don't strictly need to use pills in producing jets, but it can speed up your development cycle.

## Unit testing.

All nontrivial code should be thoroughly tested to ensure software quality. To rigorously verify the jet's behavior and performance, we will combine live testing in a single Urbit session, comparative behavior between a reference Urbit binary and our modified binary, and unit testing.

1. Live spot checks rely on you modifying the generator `trig-rs.hoon` and observing whether the jet works as expected.

When producing a library, one may use the `-build-file` thread to build and load a library core through a face. Two fakezods can be operated side-by-side in order to verify consistency between the Hoon and C code.

```> =trig-rs -build-file %/lib/trig-rs/hoon
> (exp:trig-rs .5)```
2. Comparison to the reference Urbit binary can be done with a second fakezod and the same Hoon library and generator.

3. Unit tests rely on using the `-test` thread and will be covered subsequently.

``````> -test %/tests/lib/trig-rs ~
``````

## Et cetera.

We omit from the current discussion a few salient points:

1. Reference counting with transfer and retain semantics. (For everything the new developer does outside of real Hoon shovel work, one will use transfer semantics.)

2. The structure of memory: the loom, with outer and inner roads.

3. Many details of C-side atom declaration and manipulation from the `u3` library.

We commend to the reader the exercise of selecting particular Hoon-language library functions provided with the system, such as `++cut`, locating the corresponding jet code

and learning in detail how particular operations are realized in `u3` C. Note in particular that jets do not need to follow the same solution algorithm and logic as the Hoon code; they merely need to reliably produce the same result. At the current time, the feasibility of automatic jet verification is an open research question.

to join this conversation on GitHub. Already have an account? Sign in to comment