Public API
The crate root (src/lib.rs) has these entry points. Each one reads the
source as a &str. The library reads no file, except through a
ModuleResolver when the source imports a module.
| Function | Returns | Use case |
|---|---|---|
compile_to_ir(src) | Result<IrModule, Vec<CompilerError>> | A backend (canonical) |
compile_to_ir_with_path(src, path) | Result<IrModule, Vec<CompilerError>> | A backend, with real source paths in the spans |
compile_to_ir_with_resolver(src, resolver) | Result<IrModule, Vec<CompilerError>> | A program of several modules, then MonomorphisePass |
compile_to_ir_with_path_and_resolver(src, path, resolver) | Result<IrModule, Vec<CompilerError>> | A program of several modules, with the source path, then MonomorphisePass |
compile_with_analyzer(src) | Result<(File, SemanticAnalyzer<FileSystemResolver>), Vec<CompilerError>> | LSP, AST-level tooling |
compile_with_analyzer_and_resolver(src, resolver) | Result<(File, SemanticAnalyzer<R>), Vec<CompilerError>> | The same, with a custom resolver |
parse_only(src) | Result<File, Vec<CompilerError>> | Parse with no semantic analysis |
compile_and_report(src, filename) | Result<IrModule, String> | CLI: compile_to_ir with the errors as a report |
compile_to_ir, compile_to_ir_with_path and compile_with_analyzer
resolve an import through a FileSystemResolver that starts at the
current directory. Give a resolver of your own for any other root:
a FileSystemResolver with another root, a MemoryResolver for
modules in memory, or a custom ModuleResolver. A ModuleResolver
must be Send and Sync,
because the compile runs on a thread of its own and takes the resolver
with it.
The stack of the caller does not matter. Each entry point runs its
work on a compile thread with a stack sized from the program, so a
caller on a small thread can compile the deepest program that the
parser accepts. An IrPass must be Send for the same reason:
Pipeline::run runs the passes on such a thread. See
The compile thread. On wasm32 this
is not true; see WebAssembly.
Each entry point that returns an IrModule gives one self-contained
module. The IR of each imported module links into it. See
Obtaining the IR.
compile_to_ir_with_resolver and compile_to_ir_with_path_and_resolver
run MonomorphisePass on the result. The other entry points do not:
their result can hold generic definitions. Add the pass through a
Pipeline when you need it.
WebAssembly
The library builds for wasm32-unknown-unknown and wasm32-wasip1.
The binary fvc does not run on WebAssembly.
A browser has no file system. Give the modules to a MemoryResolver,
each with a path relative to the root .. It finds a module the same
way as a FileSystemResolver with the root ., and it gives the same
errors.
wasm32 has no threads, so the compile runs on the stack of the
caller, not on a compile thread. Two stacks limit the depth of nesting
that a compile can take:
- The stack of the module, in its linear memory. It is 1 MiB by
default, and the linker flag
-C link-arg=-zstack-size=<bytes>sets another size. An overflow traps with "out of bounds memory access". - The native stack of the engine, which runs the frames of the
compiled code. The browser, or the runtime, sets it, and the linker
flag does not change it. wasmtime allows 512 KiB by default. An
overflow traps with "call stack exhausted" in wasmtime, and throws a
RangeErrorin a browser.
A stack overflow stops the whole module, so the compiler has lower
limits on wasm32:
| Limit | wasm32 | Other targets |
|---|---|---|
| The nesting score that the parser accepts | 96 | 1024 |
| The depth of an expression that the semantic pass accepts | 48 | 500 |
A deeper program is a compile error. These are the deepest programs
that compile on wasm32-wasip1, in a release build, and then run the
passes of a backend (DefunctionalisePass, ConstantFoldingPass and
DeadCodeEliminationPass):
| Shape | The deepest program | The depth that traps with no limit |
|---|---|---|
A call in a call, f(v: f(v: 1)) | 47 | 229 |
A call of a block, f(v: { f(v: { 1 }) }) | 47 | 105 |
| Parentheses | 46 | 236 |
| Blocks | 92 | 195 |
if in a branch | 46 | 173 |
match in an arm | 31 | 243 |
| Arrays | 46 | 246 |
The last column is for the default stacks of wasmtime. Each program at the limit compiles with a native stack of 256 KiB, half the default of wasmtime, and with the default stack of the module. Each engine has its own frame sizes, so measure the deepest program in the browser that you use. The numbers change with each release of the compiler.
scripts/wasm_stack.sh measures the depths with the probe in
tests/wasm-probe/. It needs wasmtime. Give it a size to measure another
stack of the module: scripts/wasm_stack.sh 2097152.
Cargo features
The crate has one feature, serde. It is off by default. It derives
serde::Serialize and serde::Deserialize on IrModule and on every
type in it. It also derives them on the AST types that the IR holds,
for example Literal, PrimitiveType and Span.
[dependencies]
formalang = { version = "0.0.16-beta", features = ["serde"] }
The JSON form of the IR is not a stable format. It can change in each
release. After you read a module, call IrModule::rebuild_indices
before a lookup by name.
The AST has no serialized form. File has no format_version, and
there is no File::to_json or File::from_json.
The symbol table
SemanticAnalyzer::symbols() returns the SymbolTable. IR lowering and
LSP tools read it. Two parts of its public surface changed:
SymbolTable::define_trait_impl(trait_name, struct_name, generics, trait_args, span)takestrait_args: Vec<Type>. These are the type arguments of a generic trait:[I32]forimpl Container<I32> for Box. Give an empty vector for a trait with no type parameters.TraitImplInfohas the fieldtrait_args: Vec<Type>with the same meaning.
A type has one impl of each trait instance. A second impl of the same
trait with the same trait_args for the same type is a duplicate. Two
impls with other trait_args, such as Container<I32> and
Container<String>, are two instances, and both are allowed.