Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement WASI system calls #112

Merged
merged 65 commits into from
May 8, 2020
Merged

Implement WASI system calls #112

merged 65 commits into from
May 8, 2020

Conversation

ospencer
Copy link
Member

@ospencer ospencer commented May 2, 2020

This PR implements system calls for Grain using the WebAssembly System Interface. This includes things like accessing system clocks, reading environment variables, and reading/writing to files.

We use @wasmer/wasi to make WASI calls within Node.js. While writing this PR, Node.js 14 was released, which ships with built-in support for WASI, with a similar API. After this lands, I'll make an issue to use the built-in WASI implementation for our Node runtime, but we'll still need to rely on @wasmer/wasi for the browser runtime.

New Modules

sys.gr

This module is meant to contain fairly thin wrappers around the WASI calls. It's not expected for users to interact with these directly, but rather with libraries built on top of these.

Environment

# Access command line arguments
# @returns Array<String> The positional string arguments to the process
argv : () -> Array<String>

# Access environment variables
# @returns Array<String> The environment variables supplied to the process
env : () -> Array<String>

Clocks

# Get the current time, in nanoseconds.
# Time value 0 corresponds with 1970-01-01T00:00:00Z.
# @returns Int64 The current time
realTime : () -> Int64

# Get the time of the system's high-resolution clock, in nanoseconds.
# This system clock cannot be adjusted and cannot have negative time jumps.
# The epoch of this clock is undefined, and thus time value 0 is meaningless.
# Useful for calculation of precise time intervals.
# @returns Int64 The current time
monotonicTime : () -> Int64

# Get the number of nanoseconds elapsed since the process began
# @returns Int64
processCpuTime : () -> Int64

# Get the number of nanoseconds elapsed since the thread began
# @returns Int64
threadCpuTime : () -> Int64

Files

# Open a file or directory
# @param workingDirectory: FileDescriptor The directory in which path resolution starts
# @param lookupFlags: List<LookupFlag> Flags which affect path resolution
# @param path: String The path to the file or directory
# @param openFlags: List<OpenFlag> Flags that decide how the path will be opened
# @param rights: List<Rights> The rights that dictate what may be done with the returned file descriptor
# @param rightsInheriting: List<Rights> The rights that dictate what may be done with file descriptors derived from this file descriptor
# @param flags: List<FdFlag> Flags which affect read/write operations on this file descriptor
# @returns FileDescriptor The opened file or directory
pathOpen : (FileDescriptor, List<LookupFlag>, String, List<OpenFlag>, List<Rights>, List<Rights>, List<FdFlag>) -> FileDescriptor

# Read from a file descriptor
# @param fd: FileDescriptor The file descriptor to read from
# @param nBytes: Number The maximum number of bytes to read from the file descriptor
# @returns (String, Number) The bytes read and the number of bytes read
fdRead : (FileDescriptor, Number) -> (String, Number)

# Read from a file descriptor without updating the file descriptor's offset
# @param fd: FileDescriptor The file descriptor to read from
# @param offset: Int64 The position within the file to begin reading
# @param nBytes: Number The maximum number of bytes to read from the file descriptor
# @returns (String, Number) The bytes read and the number of bytes read
fdPread : (FileDescriptor, Int64, Number) -> (String, Number)

# Write to a file descriptor
# @param fd: FileDescriptor The file descriptor to which data will be written
# @param buf: String The data to be written
# @returns Number The number of bytes written
fdWrite : (FileDescriptor, String) -> Number

# Read from a file descriptor without updating the file descriptor's offset
# @param fd: FileDescriptor The file descriptor to which data will be written
# @param buf: String The data to be written
# @param offset: Int64 The position within the file to begin writing
# @returns Number The number of bytes written
fdPwrite : (FileDescriptor, String, Int64) -> Number

# Allocate space within a file
# @param fd: FileDescriptor The file descriptor in which space will be allocated
# @param offset: Int64 The position within the file to begin writing
# @param nBytes: Int64 The number of bytes to allocate
fdAllocate : (FileDescriptor, Int64, Int64) -> Void

# Close a file descriptor
# @param fd: FileDescriptor The file descriptor to close
fdClose : (FileDescriptor) -> Void

# Synchronize the data of a file to disk
# @param fd: FileDescriptor The file descriptor to synchronize
fdDatasync : (FileDescriptor) -> Void

# Synchronize the data and metadata of a file to disk
# @param fd: FileDescriptor The file descriptor to synchronize
fdSync : (FileDescriptor) -> Void

# Retrieve information about a file descriptor
# @param fd: FileDescriptor The file descriptor of which to retrieve information
# @returns Stats A record containing the filetype, flags, rights, and inheriting rights associated with the file descriptor
fdStats : (FileDescriptor) -> Stats

# Update the flags associated with a file descriptor
# @param fd: FileDescriptor The file descriptor to update flags
fdSetFlags : (FileDescriptor, List<FdFlag>) -> Void

# Update the rights associated with a file descriptor
# @param rights: List<Rights> Rights to apply to the file descriptor
# @param rightsInheriting: List<Rights> Inheriting rights to apply to the file descriptor
fdSetRights : (FileDescriptor, List<Rights>, List<Rights>) -> Void

# Retrieve information about a file
# @param fd: FileDescriptor The file descriptor of the file to retrieve information
# @returns Filestats A record containing the information about the file
fdFilestats : (FileDescriptor) -> Filestats

# Set (truncate) the size of a file
# @param fd: FileDescriptor The file descriptor of the file to truncate
# @param size: Int64 The number of bytes to retain in the file
fdSetSize : (FileDescriptor, Int64) -> Void

# Set the access (created) time of a file
# @param fd: FileDescriptor The file descriptor of the file to update
# @param timestamp: Int64 The time to set
fdSetAccessTime : (FileDescriptor, Int64) -> Void

# Set the access (created) time of a file to the current time
# @param fd: FileDescriptor The file descriptor of the file to update
fdSetAccessTimeNow : (FileDescriptor) -> Void

# Set the modified time of a file
# @param fd: FileDescriptor The file descriptor of the file to update
# @param timestamp: Int64 The time to set
fdSetModifiedTime : (FileDescriptor, Int64) -> Void

# Set the modified time of a file to the current time
# @param fd: FileDescriptor The file descriptor of the file to update
fdSetModifiedTimeNow : (FileDescriptor) -> Void

# Read the entires of a directory
# @param fd: FileDescriptor The directory to read
# @returns Array<DirectoryEntry> An array of records containing information about each entry in the directory
fdReaddir : (FileDescriptor) -> Array<DirectoryEntry>

# Atomically replace a file descriptor by renumbering another file descriptor
# @param from: FileDescriptor The file descriptor to renumber
# @param to: FileDescriptor The file descriptor to overwrite
fdRenumber : (FileDescriptor, FileDescriptor) -> Void

# Update a file descriptor's offset
# @param fd: FileDescriptor The file descriptor to operate on
# @param delta: Int64 The number of bytes to move the offset
# @param whence: The location from which the offset is relative
# @returns Int64 The new offset of the file descriptor, relative to the start of the file
fdSeek : (FileDescriptor, Int64, Whence) -> Int64

# Read a file descriptor's offset
# @param fd: FileDescriptor The file descriptor to read the offset
# @returns Int64 The offset of the file descriptor, relative to the start of the file
fdTell : (FileDescriptor) -> Int64


# Create a directory
# @param workingDirectory: FileDescriptor The directory in which path resolution starts
# @param path: String The path to the new directory
pathCreateDirectory : (FileDescriptor, String) -> Void

# Retrieve information about a file
# @param workingDirectory: FileDescriptor The directory in which path resolution starts
# @param flags: List<LookupFlag> Flags which affect path resolution
# @param path: String The path to retrieve information about
# @returns Filestats A record containing information about the file
pathFilestats : (FileDescriptor, List<LookupFlag>, String) -> Filestats

# Set the access (created) time of a file
# @param workingDirectory: FileDescriptor The directory in which path resolution starts
# @param flags: List<LookupFlag> Flags which affect path resolution
# @param path: String The path to set the time
# @param timestamp: Int64 The time to set
pathSetAccessTime : (FileDescriptor, List<LookupFlag>, String, Int64) -> Void

# Set the access (created) time of a file to the current time
# @param workingDirectory: FileDescriptor The directory in which path resolution starts
# @param flags: List<LookupFlag> Flags which affect path resolution
# @param path: String The path to set the time
pathSetAccessTimeNow : (FileDescriptor, List<LookupFlag>, String) -> Void

# Set the modified time of a file
# @param workingDirectory: FileDescriptor The directory in which path resolution starts
# @param flags: List<LookupFlag> Flags which affect path resolution
# @param path: String The path to set the time
# @param timestamp: Int64 The time to set
pathSetModifiedTime : (FileDescriptor, List<LookupFlag>, String, Int64) -> Void

# Set the modified time of a file to the current time
# @param workingDirectory: FileDescriptor The directory in which path resolution starts
# @param flags: List<LookupFlag> Flags which affect path resolution
# @param path: String The path to set the time
pathSetModifiedTimeNow : (FileDescriptor, List<LookupFlag>, String) -> Void

# Create a hard link
# @param sourceWorkingDirectory: FileDescriptor The directory in which the source path resolution starts
# @param flags: List<LookupFlag> Flags which affect path resolution
# @param sourcePath: String The path to the source of the link
# @param targetWorkingDirectory: FileDescriptor The directory in which the target path resolution starts
# @param targetPath: String The path to the target of the link
pathLink : (FileDescriptor, List<LookupFlag>, String, FileDescriptor, String) -> Void

# Create a symlink
# @param workingDirectory: FileDescriptor The directory in which path resolution starts
# @param sourcePath: String The path to the source of the link
# @param targetPath: String The path to the target of the link
pathSymlink : (FileDescriptor, String, String) -> Void

# Unlink a file
# @param workingDirectory: FileDescriptor The directory in which path resolution starts
# @param path: String The path of the file to unlink
pathUnlink : (FileDescriptor, String) -> Void

# Read the contents of a symbolic link
# @param workingDirectory: FileDescriptor The directory in which path resolution starts
# @param path: String The path to the symlink
# @param nBytes: The number of bytes to read
# @returns (String, Number) The bytes read and the number of bytes read
pathReadlink : (FileDescriptor, String, Number) -> (String, Number)

# Remove a directory
# @param workingDirectory: FileDescriptor The directory in which path resolution starts
# @param path: String The path to the directory to remove
pathRemoveDirectory : (FileDescriptor, String) -> Void

# Rename a file or directory
# @param sourceWorkingDirectory: FileDescriptor The directory in which the source path resolution starts
# @param sourcePath: String The path of the file to rename
# @param targetWorkingDirectory: FileDescriptor The directory in which the target path resolution starts
# @param targetPath: String The new path of the file
pathRename : (FileDescriptor, String, FileDescriptor, String) -> Void

Process

# Terminate the process normally
# @param code: Number The value to exit with. An exit code of 0 is considered normal, with other values having meaning depending on the platform
exit : (Number) -> Void

# Send a signal to the process of the calling thread
# @param signal: Signal The signal to send
sigRaise : (Signal) -> Void

# Yield execution to the calling thread
schedYield : () -> Void

Random

# Produce a random number. This function can be slow, so it's best to seed a generator if lots of random data is needed
# @returns Number
random : () -> Number

stdint64.gr

This module provides some basic operations over the Int64 type.

fromNumber : Number -> Int64
toNumber : Int64 -> Number

lnot : Int64 -> Int64

land : (Int64, Int64) -> Int64
lor : (Int64, Int64) -> Int64
lxor : (Int64, Int64) -> Int64

lsl : (Int64, Number) -> Int64
lsr : (Int64, Number) -> Int64
asr : (Int64, Number) -> Int64

gt : (Int64, Int64) -> Bool
gte : (Int64, Int64) -> Bool
lt : (Int64, Int64) -> Bool
lte : (Int64, Int64) -> Bool

Updated Modules

lists.gr

Added for_each and for_each_i.

arrays.gr

Added map.

Improvements

Hex, Octal, and Binary literals

0xffffff
0o77777
0b101010101

Visual Number Separators

Underscores are now allowed in number literals.

let oneMillion = 1_000_000

Int32 and Int64 Literals

42l
99999999999999999L

Void constant

void

Bug Fixes

  • One sided if statements now work
  • The files foo and bar make inconsistent assumptions over interface baz error while running tests no longer occurs. NOTE: This is not yet fully fixed, and could still happen when running compiling and running normal programs, though this will no longer happen while running the tests. A bug will be filed once this PR lands.
  • Printing would break for data types if not all data types in a file were exported, now fixed
  • Printing ADT variants relied on order defined, now fixed

Other Noteworthy Changes

To support running in WASI environments, GRAIN$MAIN has been renamed to _start.

memory is now exported from all Grain modules under the name memory.

Everything Else

While a fair number of these new system calls will work as-is, many of them rely on some bugfixes I made that have yet to be released. As soon as a new release is cut, we'll upgrade to the proper version. In the meantime, say, to develop libraries on top of these calls, the user will need to manually build and link in wasmer-js.

@ospencer ospencer requested a review from peblair May 2, 2020 17:48
@ospencer ospencer self-assigned this May 2, 2020
@ospencer
Copy link
Member Author

ospencer commented May 2, 2020

sock_recv, sock_send, and sock_shutdown are all missing because the specification is still in flux. We'll implement them (maybe) once decisions are final.

Copy link
Member

@peblair peblair left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks mostly good; just a couple of comments.

@@ -1,4 +1,5 @@
#!/usr/bin/env node
#!/bin/sh
// 2>/dev/null; exec /usr/bin/env node --experimental-wasm-bigint "$0" "$@"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heh. Basically, we need the experimental-wasm-bigint flag for @wasmer/wasi to properly pass int64s to/from JS, but Linux machines don't allow you to pass more than one argument to a shebang.

https://unix.stackexchange.com/questions/65235/universal-node-js-shebang/65295#65295

This is temporary until it's not experimental anymore.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha, that makes sense. Maybe a comment to explain so that we don't forget?

@@ -129,6 +129,9 @@ let wrap_int64 n = add_dummy_loc (Values.I64Value.to_value n)
let wrap_float32 n = add_dummy_loc (Values.F32Value.to_value n)
let wrap_float64 n = add_dummy_loc (Values.F64Value.to_value n)

let grain_number_max = 0x3fffffff
let grain_number_min = -0x3fffffff
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to write this without the negative? Or at least a comment with the full hex of the negative

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The native numbers in OCaml extend past 32 bits, so we can't write out the literal just here, but we could do Int32.to_int 0xC0000001l if you'd like that better?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. Maybe just putting (* 0xC0000001 *) next to it would be sufficient. I mostly just want it so that we can quickly identify what the memory layout of the number is, so it's not important that the actual source has the proper constant.

@@ -96,6 +108,8 @@ rule token = parse
| comment { process_newlines lexbuf; token lexbuf }
| blank { token lexbuf }
| newline_chars { process_newlines lexbuf; EOL }
| (signed_int as x) 'l' { INT32 (Int32.of_string x) }
| (signed_int as x) 'L' { INT64 (Int64.of_string x) }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where's the inspiration for this syntax from?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's the same syntax as C/C++/Java/OCaml. JavaScript uses the n suffix for BigInt but that doesn't feel quite right since other languages use n for whatever the system's native int size is.

Some references:
https://docs.microsoft.com/en-us/cpp/c-language/c-floating-point-constants?view=vs-2019
https://en.wikiversity.org/wiki/Advanced_Java/Declaring_float_and_long_Literals

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, cool. Looks good.

@@ -105,6 +119,7 @@ rule token = parse
| "else" { ELSE }
| "true" { TRUE }
| "false" { FALSE }
| "void" { VOID }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😨

@ospencer ospencer requested a review from peblair May 7, 2020 17:00
@ospencer ospencer merged commit 81510d0 into master May 8, 2020
@ospencer ospencer deleted the wasi branch May 8, 2020 00:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants