forked from M-Labs/nac3
1
0
Fork 0

Compare commits

..

1 Commits

Author SHA1 Message Date
z78078 74b9416174 nac3core: add fields initialization check for parent class 2022-08-17 17:44:17 +08:00
120 changed files with 9229 additions and 27915 deletions

View File

@ -1 +0,0 @@
doc-valid-idents = ["CPython", "NumPy", ".."]

2
.gitignore vendored
View File

@ -1,3 +1,3 @@
__pycache__ __pycache__
/target /target
nix/windows/msys2 windows/msys2

View File

@ -1,24 +0,0 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
default_stages: [commit]
repos:
- repo: local
hooks:
- id: nac3-cargo-fmt
name: nac3 cargo format
entry: cargo
language: system
types: [file, rust]
pass_filenames: false
description: Runs cargo fmt on the codebase.
args: [fmt]
- id: nac3-cargo-clippy
name: nac3 cargo clippy
entry: cargo
language: system
types: [file, rust]
pass_filenames: false
description: Runs cargo clippy on the codebase.
args: [clippy, --tests]

851
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,6 @@ members = [
"nac3artiq", "nac3artiq",
"runkernel", "runkernel",
] ]
resolver = "2"
[profile.release] [profile.release]
debug = true debug = true

View File

@ -23,19 +23,21 @@ After setting up Nix as above, use ``nix shell git+https://github.com/m-labs/art
### Windows ### Windows
Install [MSYS2](https://www.msys2.org/), and open "MSYS2 CLANG64". Edit ``/etc/pacman.conf`` to add: Install [MSYS2](https://www.msys2.org/), and open "MSYS2 MinGW x64". Edit ``/etc/pacman.conf`` to add:
``` ```
[artiq] [artiq]
SigLevel = Optional TrustAll SigLevel = Optional TrustAll
Server = https://msys2.m-labs.hk/artiq-nac3 Server = https://lab.m-labs.hk/msys2
``` ```
Then run the following commands: Then run the following commands:
``` ```
pacman -Syu pacman -Syu
pacman -S mingw-w64-clang-x86_64-artiq pacman -S mingw-w64-x86_64-artiq
``` ```
Note: This build of NAC3 cannot be used with Anaconda Python nor the python.org binaries for Windows. Those Python versions are compiled with Visual Studio (MSVC) and their ABI is incompatible with the GNU ABI used in this build. We have no plans to support Visual Studio nor the MSVC ABI. If you need a MSVC build, please install the requisite bloated spyware from Microsoft and compile NAC3 yourself.
## For developers ## For developers
This repository contains: This repository contains:
@ -51,12 +53,3 @@ Use ``nix develop`` in this repository to enter a development shell.
If you are using a different shell than bash you can use e.g. ``nix develop --command fish``. If you are using a different shell than bash you can use e.g. ``nix develop --command fish``.
Build NAC3 with ``cargo build --release``. See the demonstrations in ``nac3artiq`` and ``nac3standalone``. Build NAC3 with ``cargo build --release``. See the demonstrations in ``nac3artiq`` and ``nac3standalone``.
### Pre-Commit Hooks
You are strongly recommended to use the provided pre-commit hooks to automatically reformat files and check for non-optimal Rust practices using Clippy. Run `pre-commit install` to install the hook and `pre-commit` will automatically run `cargo fmt` and `cargo clippy` for you.
Several things to note:
- If `cargo fmt` or `cargo clippy` returns an error, the pre-commit hook will fail. You should fix all errors before trying to commit again.
- If `cargo fmt` reformats some files, the pre-commit hook will also fail. You should review the changes and, if satisfied, try to commit again.

View File

@ -2,16 +2,16 @@
"nodes": { "nodes": {
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1720418205, "lastModified": 1659689094,
"narHash": "sha256-cPJoFPXU44GlhWg4pUk9oUPqurPlCFZ11ZQPk21GTPU=", "narHash": "sha256-cXrWxpPYpV1PeEhtpQf9W++8aCgwzxpx2PzfszPofJE=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "655a58a72a6601292512670343087c2d75d859c1", "rev": "697fc6ae98d077f6448cada3ecd63465c48c6af5",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "NixOS", "owner": "NixOS",
"ref": "nixos-unstable", "ref": "master",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }

View File

@ -1,7 +1,7 @@
{ {
description = "The third-generation ARTIQ compiler"; description = "The third-generation ARTIQ compiler";
inputs.nixpkgs.url = github:NixOS/nixpkgs/nixos-unstable; inputs.nixpkgs.url = github:NixOS/nixpkgs/master;
outputs = { self, nixpkgs }: outputs = { self, nixpkgs }:
let let
@ -9,13 +9,6 @@
in rec { in rec {
packages.x86_64-linux = rec { packages.x86_64-linux = rec {
llvm-nac3 = pkgs.callPackage ./nix/llvm {}; llvm-nac3 = pkgs.callPackage ./nix/llvm {};
llvm-tools-irrt = pkgs.runCommandNoCC "llvm-tools-irrt" {}
''
mkdir -p $out/bin
ln -s ${pkgs.llvmPackages_14.clang-unwrapped}/bin/clang $out/bin/clang-irrt
ln -s ${pkgs.llvmPackages_14.clang}/bin/clang $out/bin/clang-irrt-test
ln -s ${pkgs.llvmPackages_14.llvm.out}/bin/llvm-as $out/bin/llvm-as-irrt
'';
nac3artiq = pkgs.python3Packages.toPythonModule ( nac3artiq = pkgs.python3Packages.toPythonModule (
pkgs.rustPlatform.buildRustPackage rec { pkgs.rustPlatform.buildRustPackage rec {
name = "nac3artiq"; name = "nac3artiq";
@ -23,12 +16,14 @@
src = self; src = self;
cargoLock = { cargoLock = {
lockFile = ./Cargo.lock; lockFile = ./Cargo.lock;
outputHashes = {
"inkwell-0.1.0" = "sha256-+ih3SO0n6YmZ/mcf+rLDwPAy/1MEZ/A+tI4pM1pUhvU=";
};
}; };
cargoTestFlags = [ "--features" "test" ];
passthru.cargoLock = cargoLock; passthru.cargoLock = cargoLock;
nativeBuildInputs = [ pkgs.python3 pkgs.llvmPackages_14.clang llvm-tools-irrt pkgs.llvmPackages_14.llvm.out llvm-nac3 ]; nativeBuildInputs = [ pkgs.python3 pkgs.llvmPackages_14.clang-unwrapped pkgs.llvmPackages_14.llvm.out llvm-nac3 ];
buildInputs = [ pkgs.python3 llvm-nac3 ]; buildInputs = [ pkgs.python3 llvm-nac3 ];
checkInputs = [ (pkgs.python3.withPackages(ps: [ ps.numpy ps.scipy ])) ]; checkInputs = [ (pkgs.python3.withPackages(ps: [ ps.numpy ])) ];
checkPhase = checkPhase =
'' ''
echo "Checking nac3standalone demos..." echo "Checking nac3standalone demos..."
@ -68,7 +63,7 @@
name = "nac3artiq-instrumented"; name = "nac3artiq-instrumented";
src = self; src = self;
inherit (nac3artiq) cargoLock; inherit (nac3artiq) cargoLock;
nativeBuildInputs = [ pkgs.python3 packages.x86_64-linux.llvm-tools-irrt llvm-nac3-instrumented ]; nativeBuildInputs = [ pkgs.python3 pkgs.llvmPackages_14.clang-unwrapped pkgs.llvmPackages_14.llvm.out llvm-nac3-instrumented ];
buildInputs = [ pkgs.python3 llvm-nac3-instrumented ]; buildInputs = [ pkgs.python3 llvm-nac3-instrumented ];
cargoBuildFlags = [ "--package" "nac3artiq" "--features" "init-llvm-profile" ]; cargoBuildFlags = [ "--package" "nac3artiq" "--features" "init-llvm-profile" ];
doCheck = false; doCheck = false;
@ -96,12 +91,12 @@
(pkgs.fetchFromGitHub { (pkgs.fetchFromGitHub {
owner = "m-labs"; owner = "m-labs";
repo = "artiq"; repo = "artiq";
rev = "923ca3377d42c815f979983134ec549dc39d3ca0"; rev = "dd57fdc530baf926a5f354dc1c2bd90564affd96";
sha256 = "sha256-oJoEeNEeNFSUyh6jXG8Tzp6qHVikeHS0CzfE+mODPgw="; sha256 = "sha256-hcqVcToYWkc3oDFkKr9wZUF65ydiSYVHdmiGiu2Mc1c=";
}) })
]; ];
buildInputs = [ buildInputs = [
(python3-mimalloc.withPackages(ps: [ ps.numpy ps.scipy ps.jsonschema ps.lmdb nac3artiq-instrumented ])) (python3-mimalloc.withPackages(ps: [ ps.numpy ps.jsonschema nac3artiq-instrumented ]))
pkgs.llvmPackages_14.llvm.out pkgs.llvmPackages_14.llvm.out
]; ];
phases = [ "buildPhase" "installPhase" ]; phases = [ "buildPhase" "installPhase" ];
@ -130,7 +125,7 @@
name = "nac3artiq-pgo"; name = "nac3artiq-pgo";
src = self; src = self;
inherit (nac3artiq) cargoLock; inherit (nac3artiq) cargoLock;
nativeBuildInputs = [ pkgs.python3 packages.x86_64-linux.llvm-tools-irrt llvm-nac3-pgo ]; nativeBuildInputs = [ pkgs.python3 pkgs.llvmPackages_14.clang-unwrapped pkgs.llvmPackages_14.llvm.out llvm-nac3-pgo ];
buildInputs = [ pkgs.python3 llvm-nac3-pgo ]; buildInputs = [ pkgs.python3 llvm-nac3-pgo ];
cargoBuildFlags = [ "--package" "nac3artiq" ]; cargoBuildFlags = [ "--package" "nac3artiq" ];
cargoTestFlags = [ "--package" "nac3ast" "--package" "nac3parser" "--package" "nac3core" "--package" "nac3artiq" ]; cargoTestFlags = [ "--package" "nac3ast" "--package" "nac3parser" "--package" "nac3core" "--package" "nac3artiq" ];
@ -146,27 +141,23 @@
packages.x86_64-w64-mingw32 = import ./nix/windows { inherit pkgs; }; packages.x86_64-w64-mingw32 = import ./nix/windows { inherit pkgs; };
devShells.x86_64-linux.default = pkgs.mkShell { devShell.x86_64-linux = pkgs.mkShell {
name = "nac3-dev-shell"; name = "nac3-dev-shell";
buildInputs = with pkgs; [ buildInputs = with pkgs; [
# build dependencies # build dependencies
packages.x86_64-linux.llvm-nac3 packages.x86_64-linux.llvm-nac3
llvmPackages_14.clang llvmPackages_14.llvm.out # for running nac3standalone demos llvmPackages_14.clang-unwrapped # IRRT
packages.x86_64-linux.llvm-tools-irrt pkgs.llvmPackages_14.llvm.out # IRRT
cargo cargo
rustc rustc
# runtime dependencies # runtime dependencies
lld_14 # for running kernels on the host lld_14 # for running kernels on the host
(packages.x86_64-linux.python3-mimalloc.withPackages(ps: [ ps.numpy ps.scipy ])) (packages.x86_64-linux.python3-mimalloc.withPackages(ps: [ ps.numpy ]))
# development tools # development tools
cargo-insta cargo-insta
clippy clippy
pre-commit
rustfmt rustfmt
rust-analyzer
]; ];
# https://nixos.wiki/wiki/Rust#Shell.nix_example
RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}";
}; };
devShells.x86_64-linux.msys2 = pkgs.mkShell { devShells.x86_64-linux.msys2 = pkgs.mkShell {
name = "nac3-dev-shell-msys2"; name = "nac3-dev-shell-msys2";

View File

@ -2,23 +2,22 @@
name = "nac3artiq" name = "nac3artiq"
version = "0.1.0" version = "0.1.0"
authors = ["M-Labs"] authors = ["M-Labs"]
edition = "2021" edition = "2018"
[lib] [lib]
name = "nac3artiq" name = "nac3artiq"
crate-type = ["cdylib"] crate-type = ["cdylib"]
[dependencies] [dependencies]
itertools = "0.13" pyo3 = { version = "0.16", features = ["extension-module"] }
pyo3 = { version = "0.21", features = ["extension-module", "gil-refs"] }
parking_lot = "0.12" parking_lot = "0.12"
tempfile = "3.10" tempfile = "3"
nac3parser = { path = "../nac3parser" } nac3parser = { path = "../nac3parser" }
nac3core = { path = "../nac3core" } nac3core = { path = "../nac3core" }
nac3ld = { path = "../nac3ld" } nac3ld = { path = "../nac3ld" }
[dependencies.inkwell] [dependencies.inkwell]
version = "0.4" git = "https://github.com/TheDan64/inkwell.git"
default-features = false default-features = false
features = ["llvm14-0", "target-x86", "target-arm", "target-riscv", "no-libffi-linking"] features = ["llvm14-0", "target-x86", "target-arm", "target-riscv", "no-libffi-linking"]

View File

@ -18,13 +18,6 @@ class EmbeddingMap:
"SPIError", "SPIError",
"0:ZeroDivisionError", "0:ZeroDivisionError",
"0:IndexError", "0:IndexError",
"0:ValueError",
"0:RuntimeError",
"0:AssertionError",
"0:KeyError",
"0:NotImplementedError",
"0:OverflowError",
"0:IOError",
"0:UnwrapNoneError"]) "0:UnwrapNoneError"])
def preallocate_runtime_exception_names(self, names): def preallocate_runtime_exception_names(self, names):

View File

@ -10,7 +10,7 @@ from embedding_map import EmbeddingMap
__all__ = [ __all__ = [
"Kernel", "KernelInvariant", "virtual", "ConstGeneric", "Kernel", "KernelInvariant", "virtual",
"Option", "Some", "none", "UnwrapNoneError", "Option", "Some", "none", "UnwrapNoneError",
"round64", "floor64", "ceil64", "round64", "floor64", "ceil64",
"extern", "kernel", "portable", "nac3", "extern", "kernel", "portable", "nac3",
@ -67,12 +67,6 @@ def Some(v: T) -> Option[T]:
none = Option(None) none = Option(None)
class _ConstGenericMarker:
pass
def ConstGeneric(name, constraint):
return TypeVar(name, _ConstGenericMarker, constraint)
def round64(x): def round64(x):
return round(x) return round(x)
@ -86,13 +80,7 @@ def ceil64(x):
import device_db import device_db
core_arguments = device_db.device_db["core"]["arguments"] core_arguments = device_db.device_db["core"]["arguments"]
artiq_builtins = { compiler = nac3artiq.NAC3(core_arguments["target"])
"none": none,
"virtual": virtual,
"_ConstGenericMarker": _ConstGenericMarker,
"Option": Option,
}
compiler = nac3artiq.NAC3(core_arguments["target"], artiq_builtins)
allow_registration = True allow_registration = True
# Delay NAC3 analysis until all referenced variables are supposed to exist on the CPython side. # Delay NAC3 analysis until all referenced variables are supposed to exist on the CPython side.
registered_functions = set() registered_functions = set()

View File

@ -1,24 +0,0 @@
from min_artiq import *
from numpy import int32
@nac3
class Demo:
core: KernelInvariant[Core]
attr1: KernelInvariant[str]
attr2: KernelInvariant[int32]
def __init__(self):
self.core = Core()
self.attr2 = 32
self.attr1 = "SAMPLE"
@kernel
def run(self):
print_int32(self.attr2)
self.attr1
if __name__ == "__main__":
Demo().run()

View File

@ -1,40 +0,0 @@
from min_artiq import *
from numpy import int32
@nac3
class Demo:
attr1: KernelInvariant[int32] = 2
attr2: int32 = 4
attr3: Kernel[int32]
@kernel
def __init__(self):
self.attr3 = 8
@nac3
class NAC3Devices:
core: KernelInvariant[Core]
attr4: KernelInvariant[int32] = 16
def __init__(self):
self.core = Core()
@kernel
def run(self):
Demo.attr1 # Supported
# Demo.attr2 # Field not accessible on Kernel
# Demo.attr3 # Only attributes can be accessed in this way
# Demo.attr1 = 2 # Attributes are immutable
self.attr4 # Attributes can be accessed within class
obj = Demo()
obj.attr1 # Attributes can be accessed by class objects
NAC3Devices.attr4 # Attributes accessible for classes without __init__
if __name__ == "__main__":
NAC3Devices().run()

View File

@ -1,13 +1,12 @@
use nac3core::{ use nac3core::{
codegen::{ codegen::{
expr::gen_call, expr::gen_call,
llvm_intrinsics::{call_int_smax, call_stackrestore, call_stacksave},
stmt::{gen_block, gen_with}, stmt::{gen_block, gen_with},
CodeGenContext, CodeGenerator, CodeGenContext, CodeGenerator,
}, },
symbol_resolver::ValueEnum, symbol_resolver::ValueEnum,
toplevel::{helper::PrimDef, numpy::unpack_ndarray_var_tys, DefinitionId, GenCall}, toplevel::{DefinitionId, GenCall},
typecheck::typedef::{iter_type_vars, FunSignature, FuncArg, Type, TypeEnum, VarMap}, typecheck::typedef::{FunSignature, FuncArg, Type, TypeEnum}
}; };
use nac3parser::ast::{Expr, ExprKind, Located, Stmt, StmtKind, StrRef}; use nac3parser::ast::{Expr, ExprKind, Located, Stmt, StmtKind, StrRef};
@ -16,10 +15,7 @@ use inkwell::{
context::Context, module::Linkage, types::IntType, values::BasicValueEnum, AddressSpace, context::Context, module::Linkage, types::IntType, values::BasicValueEnum, AddressSpace,
}; };
use pyo3::{ use pyo3::{PyObject, PyResult, Python, types::{PyDict, PyList}};
types::{PyDict, PyList},
PyObject, PyResult, Python,
};
use crate::{symbol_resolver::InnerResolver, timeline::TimeFns}; use crate::{symbol_resolver::InnerResolver, timeline::TimeFns};
@ -30,45 +26,13 @@ use std::{
sync::Arc, sync::Arc,
}; };
/// The parallelism mode within a block.
#[derive(Copy, Clone, Eq, PartialEq)]
enum ParallelMode {
/// No parallelism is currently registered for this context.
None,
/// Legacy (or shallow) parallelism. Default before NAC3.
///
/// Each statement within the `with` block is treated as statements to be executed in parallel.
Legacy,
/// Deep parallelism. Default since NAC3.
///
/// Each function call within the `with` block (except those within a nested `sequential` block)
/// are treated to be executed in parallel.
Deep,
}
pub struct ArtiqCodeGenerator<'a> { pub struct ArtiqCodeGenerator<'a> {
name: String, name: String,
/// The size of a `size_t` variable in bits.
size_t: u32, size_t: u32,
/// Monotonic counter for naming `start`/`stop` variables used by `with parallel` blocks.
name_counter: u32, name_counter: u32,
/// Variable for tracking the start of a `with parallel` block.
start: Option<Expr<Option<Type>>>, start: Option<Expr<Option<Type>>>,
/// Variable for tracking the end of a `with parallel` block.
end: Option<Expr<Option<Type>>>, end: Option<Expr<Option<Type>>>,
timeline: &'a (dyn TimeFns + Sync), timeline: &'a (dyn TimeFns + Sync),
/// The [`ParallelMode`] of the current parallel context.
///
/// The current parallel context refers to the nearest `with parallel` or `with legacy_parallel`
/// statement, which is used to determine when and how the timeline should be updated.
parallel_mode: ParallelMode,
} }
impl<'a> ArtiqCodeGenerator<'a> { impl<'a> ArtiqCodeGenerator<'a> {
@ -78,74 +42,7 @@ impl<'a> ArtiqCodeGenerator<'a> {
timeline: &'a (dyn TimeFns + Sync), timeline: &'a (dyn TimeFns + Sync),
) -> ArtiqCodeGenerator<'a> { ) -> ArtiqCodeGenerator<'a> {
assert!(size_t == 32 || size_t == 64); assert!(size_t == 32 || size_t == 64);
ArtiqCodeGenerator { ArtiqCodeGenerator { name, size_t, name_counter: 0, start: None, end: None, timeline }
name,
size_t,
name_counter: 0,
start: None,
end: None,
timeline,
parallel_mode: ParallelMode::None,
}
}
/// If the generator is currently in a direct-`parallel` block context, emits IR that resets the
/// position of the timeline to the initial timeline position before entering the `parallel`
/// block.
///
/// Direct-`parallel` block context refers to when the generator is generating statements whose
/// closest parent `with` statement is a `with parallel` block.
fn timeline_reset_start(&mut self, ctx: &mut CodeGenContext<'_, '_>) -> Result<(), String> {
if let Some(start) = self.start.clone() {
let start_val = self.gen_expr(ctx, &start)?.unwrap().to_basic_value_enum(
ctx,
self,
start.custom.unwrap(),
)?;
self.timeline.emit_at_mu(ctx, start_val);
}
Ok(())
}
/// If the generator is currently in a `parallel` block context, emits IR that updates the
/// maximum end position of the `parallel` block as specified by the timeline `end` value.
///
/// In general the `end` parameter should be set to `self.end` for updating the maximum end
/// position for the current `parallel` block. Other values can be passed in to update the
/// maximum end position for other `parallel` blocks.
///
/// `parallel`-block context refers to when the generator is generating statements within a
/// (possibly indirect) `parallel` block.
///
/// * `store_name` - The LLVM value name for the pointer to `end`. `.addr` will be appended to
/// the end of the provided value name.
fn timeline_update_end_max(
&mut self,
ctx: &mut CodeGenContext<'_, '_>,
end: Option<Expr<Option<Type>>>,
store_name: Option<&str>,
) -> Result<(), String> {
if let Some(end) = end {
let old_end = self.gen_expr(ctx, &end)?.unwrap().to_basic_value_enum(
ctx,
self,
end.custom.unwrap(),
)?;
let now = self.timeline.emit_now_mu(ctx);
let max =
call_int_smax(ctx, old_end.into_int_value(), now.into_int_value(), Some("smax"));
let end_store = self
.gen_store_target(
ctx,
&end,
store_name.map(|name| format!("{name}.addr")).as_deref(),
)?
.unwrap();
ctx.builder.build_store(end_store, max).unwrap();
}
Ok(())
} }
} }
@ -162,203 +59,183 @@ impl<'b> CodeGenerator for ArtiqCodeGenerator<'b> {
} }
} }
fn gen_block<'ctx, 'a, 'c, I: Iterator<Item = &'c Stmt<Option<Type>>>>( fn gen_call<'ctx, 'a>(
&mut self, &mut self,
ctx: &mut CodeGenContext<'ctx, 'a>, ctx: &mut CodeGenContext<'ctx, 'a>,
stmts: I,
) -> Result<(), String>
where
Self: Sized,
{
// Legacy parallel emits timeline end-update/timeline-reset after each top-level statement
// in the parallel block
if self.parallel_mode == ParallelMode::Legacy {
for stmt in stmts {
self.gen_stmt(ctx, stmt)?;
if ctx.is_terminated() {
break;
}
self.timeline_update_end_max(ctx, self.end.clone(), Some("end"))?;
self.timeline_reset_start(ctx)?;
}
Ok(())
} else {
gen_block(self, ctx, stmts)
}
}
fn gen_call<'ctx>(
&mut self,
ctx: &mut CodeGenContext<'ctx, '_>,
obj: Option<(Type, ValueEnum<'ctx>)>, obj: Option<(Type, ValueEnum<'ctx>)>,
fun: (&FunSignature, DefinitionId), fun: (&FunSignature, DefinitionId),
params: Vec<(Option<StrRef>, ValueEnum<'ctx>)>, params: Vec<(Option<StrRef>, ValueEnum<'ctx>)>,
) -> Result<Option<BasicValueEnum<'ctx>>, String> { ) -> Result<Option<BasicValueEnum<'ctx>>, String> {
let result = gen_call(self, ctx, obj, fun, params)?; let result = gen_call(self, ctx, obj, fun, params)?;
if let Some(end) = self.end.clone() {
// Deep parallel emits timeline end-update/timeline-reset after each function call let old_end = self.gen_expr(ctx, &end)?.unwrap().to_basic_value_enum(ctx, self, end.custom.unwrap())?;
if self.parallel_mode == ParallelMode::Deep { let now = self.timeline.emit_now_mu(ctx);
self.timeline_update_end_max(ctx, self.end.clone(), Some("end"))?; let smax = ctx.module.get_function("llvm.smax.i64").unwrap_or_else(|| {
self.timeline_reset_start(ctx)?; let i64 = ctx.ctx.i64_type();
ctx.module.add_function(
"llvm.smax.i64",
i64.fn_type(&[i64.into(), i64.into()], false),
None,
)
});
let max = ctx
.builder
.build_call(smax, &[old_end.into(), now.into()], "smax")
.try_as_basic_value()
.left()
.unwrap();
let end_store = self.gen_store_target(ctx, &end)?;
ctx.builder.build_store(end_store, max);
}
if let Some(start) = self.start.clone() {
let start_val = self.gen_expr(ctx, &start)?.unwrap().to_basic_value_enum(ctx, self, start.custom.unwrap())?;
self.timeline.emit_at_mu(ctx, start_val);
} }
Ok(result) Ok(result)
} }
fn gen_with( fn gen_with<'ctx, 'a>(
&mut self, &mut self,
ctx: &mut CodeGenContext<'_, '_>, ctx: &mut CodeGenContext<'ctx, 'a>,
stmt: &Stmt<Option<Type>>, stmt: &Stmt<Option<Type>>,
) -> Result<(), String> { ) -> Result<(), String> {
let StmtKind::With { items, body, .. } = &stmt.node else { unreachable!() }; if let StmtKind::With { items, body, .. } = &stmt.node {
if items.len() == 1 && items[0].optional_vars.is_none() {
let item = &items[0];
// Behavior of parallel and sequential:
// Each function call (indirectly, can be inside a sequential block) within a parallel
// block will update the end variable to the maximum now_mu in the block.
// Each function call directly inside a parallel block will reset the timeline after
// execution. A parallel block within a sequential block (or not within any block) will
// set the timeline to the max now_mu within the block (and the outer max now_mu will also
// be updated).
//
// Implementation: We track the start and end separately.
// - If there is a start variable, it indicates that we are directly inside a
// parallel block and we have to reset the timeline after every function call.
// - If there is a end variable, it indicates that we are (indirectly) inside a
// parallel block, and we should update the max end value.
if let ExprKind::Name { id, ctx: name_ctx } = &item.context_expr.node {
if id == &"parallel".into() {
let old_start = self.start.take();
let old_end = self.end.take();
let now = if let Some(old_start) = &old_start {
self.gen_expr(ctx, old_start)?.unwrap().to_basic_value_enum(ctx, self, old_start.custom.unwrap())?
} else {
self.timeline.emit_now_mu(ctx)
};
// Emulate variable allocation, as we need to use the CodeGenContext
// HashMap to store our variable due to lifetime limitation
// Note: we should be able to store variables directly if generic
// associative type is used by limiting the lifetime of CodeGenerator to
// the LLVM Context.
// The name is guaranteed to be unique as users cannot use this as variable
// name.
self.start = old_start.clone().map_or_else(
|| {
let start = format!("with-{}-start", self.name_counter).into();
let start_expr = Located {
// location does not matter at this point
location: stmt.location,
node: ExprKind::Name { id: start, ctx: name_ctx.clone() },
custom: Some(ctx.primitives.int64),
};
let start = self.gen_store_target(ctx, &start_expr)?;
ctx.builder.build_store(start, now);
Ok(Some(start_expr)) as Result<_, String>
},
|v| Ok(Some(v)),
)?;
let end = format!("with-{}-end", self.name_counter).into();
let end_expr = Located {
// location does not matter at this point
location: stmt.location,
node: ExprKind::Name { id: end, ctx: name_ctx.clone() },
custom: Some(ctx.primitives.int64),
};
let end = self.gen_store_target(ctx, &end_expr)?;
ctx.builder.build_store(end, now);
self.end = Some(end_expr);
self.name_counter += 1;
gen_block(self, ctx, body.iter())?;
let current = ctx.builder.get_insert_block().unwrap();
// if the current block is terminated, move before the terminator
// we want to set the timeline before reaching the terminator
// TODO: This may be unsound if there are multiple exit paths in the
// block... e.g.
// if ...:
// return
// Perhaps we can fix this by using actual with block?
let reset_position = if let Some(terminator) = current.get_terminator() {
ctx.builder.position_before(&terminator);
true
} else {
false
};
// set duration
let end_expr = self.end.take().unwrap();
let end_val = self
.gen_expr(ctx, &end_expr)?
.unwrap()
.to_basic_value_enum(ctx, self, end_expr.custom.unwrap())?;
if items.len() == 1 && items[0].optional_vars.is_none() { // inside a sequential block
let item = &items[0]; if old_start.is_none() {
self.timeline.emit_at_mu(ctx, end_val);
// Behavior of parallel and sequential: }
// Each function call (indirectly, can be inside a sequential block) within a parallel // inside a parallel block, should update the outer max now_mu
// block will update the end variable to the maximum now_mu in the block. if let Some(old_end) = &old_end {
// Each function call directly inside a parallel block will reset the timeline after let outer_end_val = self
// execution. A parallel block within a sequential block (or not within any block) will .gen_expr(ctx, old_end)?
// set the timeline to the max now_mu within the block (and the outer max now_mu will also .unwrap()
// be updated). .to_basic_value_enum(ctx, self, old_end.custom.unwrap())?;
// let smax =
// Implementation: We track the start and end separately. ctx.module.get_function("llvm.smax.i64").unwrap_or_else(|| {
// - If there is a start variable, it indicates that we are directly inside a let i64 = ctx.ctx.i64_type();
// parallel block and we have to reset the timeline after every function call. ctx.module.add_function(
// - If there is a end variable, it indicates that we are (indirectly) inside a "llvm.smax.i64",
// parallel block, and we should update the max end value. i64.fn_type(&[i64.into(), i64.into()], false),
if let ExprKind::Name { id, ctx: name_ctx } = &item.context_expr.node { None,
if id == &"parallel".into() || id == &"legacy_parallel".into() { )
let old_start = self.start.take(); });
let old_end = self.end.take(); let max = ctx
let old_parallel_mode = self.parallel_mode; .builder
.build_call(smax, &[end_val.into(), outer_end_val.into()], "smax")
let now = if let Some(old_start) = &old_start { .try_as_basic_value()
self.gen_expr(ctx, old_start)?.unwrap().to_basic_value_enum( .left()
ctx,
self,
old_start.custom.unwrap(),
)?
} else {
self.timeline.emit_now_mu(ctx)
};
// Emulate variable allocation, as we need to use the CodeGenContext
// HashMap to store our variable due to lifetime limitation
// Note: we should be able to store variables directly if generic
// associative type is used by limiting the lifetime of CodeGenerator to
// the LLVM Context.
// The name is guaranteed to be unique as users cannot use this as variable
// name.
self.start = old_start.clone().map_or_else(
|| {
let start = format!("with-{}-start", self.name_counter).into();
let start_expr = Located {
// location does not matter at this point
location: stmt.location,
node: ExprKind::Name { id: start, ctx: *name_ctx },
custom: Some(ctx.primitives.int64),
};
let start = self
.gen_store_target(ctx, &start_expr, Some("start.addr"))?
.unwrap(); .unwrap();
ctx.builder.build_store(start, now).unwrap(); let outer_end = self.gen_store_target(ctx, old_end)?;
Ok(Some(start_expr)) as Result<_, String> ctx.builder.build_store(outer_end, max);
}, }
|v| Ok(Some(v)), self.start = old_start;
)?; self.end = old_end;
let end = format!("with-{}-end", self.name_counter).into(); if reset_position {
let end_expr = Located { ctx.builder.position_at_end(current);
// location does not matter at this point }
location: stmt.location, return Ok(());
node: ExprKind::Name { id: end, ctx: *name_ctx }, } else if id == &"sequential".into() {
custom: Some(ctx.primitives.int64), let start = self.start.take();
}; for stmt in body.iter() {
let end = self.gen_store_target(ctx, &end_expr, Some("end.addr"))?.unwrap(); self.gen_stmt(ctx, stmt)?;
ctx.builder.build_store(end, now).unwrap(); if ctx.is_terminated() {
self.end = Some(end_expr); break;
self.name_counter += 1; }
self.parallel_mode = match id.to_string().as_str() { }
"parallel" => ParallelMode::Deep, self.start = start;
"legacy_parallel" => ParallelMode::Legacy, return Ok(());
_ => unreachable!(),
};
self.gen_block(ctx, body.iter())?;
let current = ctx.builder.get_insert_block().unwrap();
// if the current block is terminated, move before the terminator
// we want to set the timeline before reaching the terminator
// TODO: This may be unsound if there are multiple exit paths in the
// block... e.g.
// if ...:
// return
// Perhaps we can fix this by using actual with block?
let reset_position = if let Some(terminator) = current.get_terminator() {
ctx.builder.position_before(&terminator);
true
} else {
false
};
// set duration
let end_expr = self.end.take().unwrap();
let end_val = self.gen_expr(ctx, &end_expr)?.unwrap().to_basic_value_enum(
ctx,
self,
end_expr.custom.unwrap(),
)?;
// inside a sequential block
if old_start.is_none() {
self.timeline.emit_at_mu(ctx, end_val);
} }
// inside a parallel block, should update the outer max now_mu
self.timeline_update_end_max(ctx, old_end.clone(), Some("outer.end"))?;
self.parallel_mode = old_parallel_mode;
self.end = old_end;
self.start = old_start;
if reset_position {
ctx.builder.position_at_end(current);
}
return Ok(());
} else if id == &"sequential".into() {
// For deep parallel, temporarily take away start to avoid function calls in
// the block from resetting the timeline.
// This does not affect legacy parallel, as the timeline will be reset after
// this block finishes execution.
let start = self.start.take();
self.gen_block(ctx, body.iter())?;
self.start = start;
// Reset the timeline when we are exiting the sequential block
// Legacy parallel does not need this, since it will be reset after codegen
// for this statement is completed
if self.parallel_mode == ParallelMode::Deep {
self.timeline_reset_start(ctx)?;
}
return Ok(());
} }
} }
// not parallel/sequential
gen_with(self, ctx, stmt)
} else {
unreachable!()
} }
// not parallel/sequential
gen_with(self, ctx, stmt)
} }
} }
fn gen_rpc_tag( fn gen_rpc_tag<'ctx, 'a>(
ctx: &mut CodeGenContext<'_, '_>, ctx: &mut CodeGenContext<'ctx, 'a>,
ty: Type, ty: Type,
buffer: &mut Vec<u8>, buffer: &mut Vec<u8>,
) -> Result<(), String> { ) -> Result<(), String> {
@ -393,32 +270,9 @@ fn gen_rpc_tag(
gen_rpc_tag(ctx, *ty, buffer)?; gen_rpc_tag(ctx, *ty, buffer)?;
} }
} }
TObj { obj_id, params, .. } if *obj_id == PrimDef::List.id() => { TList { ty } => {
let ty = iter_type_vars(params).next().unwrap().ty;
buffer.push(b'l'); buffer.push(b'l');
gen_rpc_tag(ctx, ty, buffer)?; gen_rpc_tag(ctx, *ty, buffer)?;
}
TObj { obj_id, .. } if *obj_id == PrimDef::NDArray.id() => {
let (ndarray_dtype, ndarray_ndims) = unpack_ndarray_var_tys(&mut ctx.unifier, ty);
let ndarray_ndims = if let TLiteral { values, .. } =
&*ctx.unifier.get_ty_immutable(ndarray_ndims)
{
if values.len() != 1 {
return Err(format!("NDArray types with multiple literal bounds for ndims is not supported: {}", ctx.unifier.stringify(ty)));
}
let value = values[0].clone();
u64::try_from(value.clone())
.map_err(|()| format!("Expected u64 for ndarray.ndims, got {value}"))?
} else {
unreachable!()
};
assert!((0u64..=u64::from(u8::MAX)).contains(&ndarray_ndims));
buffer.push(b'a');
buffer.push((ndarray_ndims & 0xFF) as u8);
gen_rpc_tag(ctx, ndarray_dtype, buffer)?;
} }
_ => return Err(format!("Unsupported type: {:?}", ctx.unifier.stringify(ty))), _ => return Err(format!("Unsupported type: {:?}", ctx.unifier.stringify(ty))),
} }
@ -426,26 +280,26 @@ fn gen_rpc_tag(
Ok(()) Ok(())
} }
fn rpc_codegen_callback_fn<'ctx>( fn rpc_codegen_callback_fn<'ctx, 'a>(
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, 'a>,
obj: Option<(Type, ValueEnum<'ctx>)>, obj: Option<(Type, ValueEnum<'ctx>)>,
fun: (&FunSignature, DefinitionId), fun: (&FunSignature, DefinitionId),
args: Vec<(Option<StrRef>, ValueEnum<'ctx>)>, args: Vec<(Option<StrRef>, ValueEnum<'ctx>)>,
generator: &mut dyn CodeGenerator, generator: &mut dyn CodeGenerator,
) -> Result<Option<BasicValueEnum<'ctx>>, String> { ) -> Result<Option<BasicValueEnum<'ctx>>, String> {
let ptr_type = ctx.ctx.i8_type().ptr_type(AddressSpace::default()); let ptr_type = ctx.ctx.i8_type().ptr_type(inkwell::AddressSpace::Generic);
let size_type = generator.get_size_type(ctx.ctx); let size_type = generator.get_size_type(ctx.ctx);
let int8 = ctx.ctx.i8_type(); let int8 = ctx.ctx.i8_type();
let int32 = ctx.ctx.i32_type(); let int32 = ctx.ctx.i32_type();
let tag_ptr_type = ctx.ctx.struct_type(&[ptr_type.into(), size_type.into()], false); let tag_ptr_type = ctx.ctx.struct_type(&[ptr_type.into(), size_type.into()], false);
let service_id = int32.const_int(fun.1 .0 as u64, false); let service_id = int32.const_int(fun.1.0 as u64, false);
// -- setup rpc tags // -- setup rpc tags
let mut tag = Vec::new(); let mut tag = Vec::new();
if obj.is_some() { if obj.is_some() {
tag.push(b'O'); tag.push(b'O');
} }
for arg in &fun.0.args { for arg in fun.0.args.iter() {
gen_rpc_tag(ctx, arg.ty, &mut tag)?; gen_rpc_tag(ctx, arg.ty, &mut tag)?;
} }
tag.push(b':'); tag.push(b':');
@ -465,7 +319,7 @@ fn rpc_codegen_callback_fn<'ctx>(
format!("tagptr{}", fun.1 .0).as_str(), format!("tagptr{}", fun.1 .0).as_str(),
); );
tag_arr_ptr.set_initializer(&int8.const_array( tag_arr_ptr.set_initializer(&int8.const_array(
&tag.iter().map(|v| int8.const_int(u64::from(*v), false)).collect::<Vec<_>>(), &tag.iter().map(|v| int8.const_int(*v as u64, false)).collect::<Vec<_>>(),
)); ));
tag_arr_ptr.set_linkage(Linkage::Private); tag_arr_ptr.set_linkage(Linkage::Private);
let tag_ptr = ctx.module.add_global(tag_ptr_type, None, &hash); let tag_ptr = ctx.module.add_global(tag_ptr_type, None, &hash);
@ -481,28 +335,38 @@ fn rpc_codegen_callback_fn<'ctx>(
}) })
.as_pointer_value(); .as_pointer_value();
let arg_length = args.len() + usize::from(obj.is_some()); let arg_length = args.len() + if obj.is_some() { 1 } else { 0 };
let stackptr = call_stacksave(ctx, Some("rpc.stack")); let stacksave = ctx.module.get_function("llvm.stacksave").unwrap_or_else(|| {
let args_ptr = ctx ctx.module.add_function("llvm.stacksave", ptr_type.fn_type(&[], false), None)
.builder });
.build_array_alloca( let stackrestore = ctx.module.get_function("llvm.stackrestore").unwrap_or_else(|| {
ptr_type, ctx.module.add_function(
ctx.ctx.i32_type().const_int(arg_length as u64, false), "llvm.stackrestore",
"argptr", ctx.ctx.void_type().fn_type(&[ptr_type.into()], false),
None,
) )
.unwrap(); });
let stackptr = ctx.builder.build_call(stacksave, &[], "rpc.stack");
let args_ptr = ctx.builder.build_array_alloca(
ptr_type,
ctx.ctx.i32_type().const_int(arg_length as u64, false),
"argptr",
);
// -- rpc args handling // -- rpc args handling
let mut keys = fun.0.args.clone(); let mut keys = fun.0.args.clone();
let mut mapping = HashMap::new(); let mut mapping = HashMap::new();
for (key, value) in args { for (key, value) in args.into_iter() {
mapping.insert(key.unwrap_or_else(|| keys.remove(0).name), value); mapping.insert(key.unwrap_or_else(|| keys.remove(0).name), value);
} }
// default value handling // default value handling
for k in keys { for k in keys.into_iter() {
mapping mapping.insert(
.insert(k.name, ctx.gen_symbol_val(generator, &k.default_value.unwrap(), k.ty).into()); k.name,
ctx.gen_symbol_val(generator, &k.default_value.unwrap(), k.ty).into()
);
} }
// reorder the parameters // reorder the parameters
let mut real_params = fun let mut real_params = fun
@ -521,19 +385,17 @@ fn rpc_codegen_callback_fn<'ctx>(
} }
for (i, arg) in real_params.iter().enumerate() { for (i, arg) in real_params.iter().enumerate() {
let arg_slot = let arg_slot = ctx.builder.build_alloca(arg.get_type(), &format!("rpc.arg{}", i));
generator.gen_var_alloc(ctx, arg.get_type(), Some(&format!("rpc.arg{i}"))).unwrap(); ctx.builder.build_store(arg_slot, *arg);
ctx.builder.build_store(arg_slot, *arg).unwrap(); let arg_slot = ctx.builder.build_bitcast(arg_slot, ptr_type, "rpc.arg");
let arg_slot = ctx.builder.build_bitcast(arg_slot, ptr_type, "rpc.arg").unwrap();
let arg_ptr = unsafe { let arg_ptr = unsafe {
ctx.builder.build_gep( ctx.builder.build_gep(
args_ptr, args_ptr,
&[int32.const_int(i as u64, false)], &[int32.const_int(i as u64, false)],
&format!("rpc.arg{i}"), &format!("rpc.arg{}", i),
) )
} };
.unwrap(); ctx.builder.build_store(arg_ptr, arg_slot);
ctx.builder.build_store(arg_ptr, arg_slot).unwrap();
} }
// call // call
@ -543,20 +405,26 @@ fn rpc_codegen_callback_fn<'ctx>(
ctx.ctx.void_type().fn_type( ctx.ctx.void_type().fn_type(
&[ &[
int32.into(), int32.into(),
tag_ptr_type.ptr_type(AddressSpace::default()).into(), tag_ptr_type.ptr_type(AddressSpace::Generic).into(),
ptr_type.ptr_type(AddressSpace::default()).into(), ptr_type.ptr_type(AddressSpace::Generic).into(),
], ],
false, false,
), ),
None, None,
) )
}); });
ctx.builder ctx.builder.build_call(
.build_call(rpc_send, &[service_id.into(), tag_ptr.into(), args_ptr.into()], "rpc.send") rpc_send,
.unwrap(); &[service_id.into(), tag_ptr.into(), args_ptr.into()],
"rpc.send",
);
// reclaim stack space used by arguments // reclaim stack space used by arguments
call_stackrestore(ctx, stackptr); ctx.builder.build_call(
stackrestore,
&[stackptr.try_as_basic_value().unwrap_left().into()],
"rpc.stackrestore",
);
// -- receive value: // -- receive value:
// T result = { // T result = {
@ -582,91 +450,86 @@ fn rpc_codegen_callback_fn<'ctx>(
let alloc_bb = ctx.ctx.append_basic_block(current_function, "rpc.continue"); let alloc_bb = ctx.ctx.append_basic_block(current_function, "rpc.continue");
let tail_bb = ctx.ctx.append_basic_block(current_function, "rpc.tail"); let tail_bb = ctx.ctx.append_basic_block(current_function, "rpc.tail");
let ret_ty = ctx.get_llvm_abi_type(generator, fun.0.ret); let ret_ty = ctx.get_llvm_type(generator, fun.0.ret);
let need_load = !ret_ty.is_pointer_type(); let need_load = !ret_ty.is_pointer_type();
let slot = ctx.builder.build_alloca(ret_ty, "rpc.ret.slot").unwrap(); let slot = ctx.builder.build_alloca(ret_ty, "rpc.ret.slot");
let slotgen = ctx.builder.build_bitcast(slot, ptr_type, "rpc.ret.ptr").unwrap(); let slotgen = ctx.builder.build_bitcast(slot, ptr_type, "rpc.ret.ptr");
ctx.builder.build_unconditional_branch(head_bb).unwrap(); ctx.builder.build_unconditional_branch(head_bb);
ctx.builder.position_at_end(head_bb); ctx.builder.position_at_end(head_bb);
let phi = ctx.builder.build_phi(ptr_type, "rpc.ptr").unwrap(); let phi = ctx.builder.build_phi(ptr_type, "rpc.ptr");
phi.add_incoming(&[(&slotgen, prehead_bb)]); phi.add_incoming(&[(&slotgen, prehead_bb)]);
let alloc_size = ctx let alloc_size = ctx
.build_call_or_invoke(rpc_recv, &[phi.as_basic_value()], "rpc.size.next") .build_call_or_invoke(rpc_recv, &[phi.as_basic_value()], "rpc.size.next")
.unwrap() .unwrap()
.into_int_value(); .into_int_value();
let is_done = ctx let is_done = ctx.builder.build_int_compare(
.builder inkwell::IntPredicate::EQ,
.build_int_compare(inkwell::IntPredicate::EQ, int32.const_zero(), alloc_size, "rpc.done") int32.const_zero(),
.unwrap(); alloc_size,
"rpc.done",
);
ctx.builder.build_conditional_branch(is_done, tail_bb, alloc_bb).unwrap(); ctx.builder.build_conditional_branch(is_done, tail_bb, alloc_bb);
ctx.builder.position_at_end(alloc_bb); ctx.builder.position_at_end(alloc_bb);
let alloc_ptr = ctx.builder.build_array_alloca(ptr_type, alloc_size, "rpc.alloc").unwrap(); let alloc_ptr = ctx.builder.build_array_alloca(ptr_type, alloc_size, "rpc.alloc");
let alloc_ptr = ctx.builder.build_bitcast(alloc_ptr, ptr_type, "rpc.alloc.ptr").unwrap(); let alloc_ptr = ctx.builder.build_bitcast(alloc_ptr, ptr_type, "rpc.alloc.ptr");
phi.add_incoming(&[(&alloc_ptr, alloc_bb)]); phi.add_incoming(&[(&alloc_ptr, alloc_bb)]);
ctx.builder.build_unconditional_branch(head_bb).unwrap(); ctx.builder.build_unconditional_branch(head_bb);
ctx.builder.position_at_end(tail_bb); ctx.builder.position_at_end(tail_bb);
let result = ctx.builder.build_load(slot, "rpc.result").unwrap(); let result = ctx.builder.build_load(slot, "rpc.result");
if need_load { if need_load {
call_stackrestore(ctx, stackptr); ctx.builder.build_call(
stackrestore,
&[stackptr.try_as_basic_value().unwrap_left().into()],
"rpc.stackrestore",
);
} }
Ok(Some(result)) Ok(Some(result))
} }
pub fn attributes_writeback( pub fn attributes_writeback<'ctx, 'a>(
ctx: &mut CodeGenContext<'_, '_>, ctx: &mut CodeGenContext<'ctx, 'a>,
generator: &mut dyn CodeGenerator, generator: &mut dyn CodeGenerator,
inner_resolver: &InnerResolver, inner_resolver: &InnerResolver,
host_attributes: &PyObject, host_attributes: PyObject,
) -> Result<(), String> { ) -> Result<(), String> {
Python::with_gil(|py| -> PyResult<Result<(), String>> { Python::with_gil(|py| -> PyResult<Result<(), String>> {
let host_attributes: &PyList = host_attributes.downcast(py)?; let host_attributes = host_attributes.cast_as::<PyList>(py)?;
let top_levels = ctx.top_level.definitions.read(); let top_levels = ctx.top_level.definitions.read();
let globals = inner_resolver.global_value_ids.read(); let globals = inner_resolver.global_value_ids.read();
let int32 = ctx.ctx.i32_type(); let int32 = ctx.ctx.i32_type();
let zero = int32.const_zero(); let zero = int32.const_zero();
let mut values = Vec::new(); let mut values = Vec::new();
let mut scratch_buffer = Vec::new(); let mut scratch_buffer = Vec::new();
for val in (*globals).values() { for (_, val) in globals.iter() {
let val = val.as_ref(py); let val = val.as_ref(py);
let ty = inner_resolver.get_obj_type( let ty = inner_resolver.get_obj_type(py, val, &mut ctx.unifier, &top_levels, &ctx.primitives)?;
py,
val,
&mut ctx.unifier,
&top_levels,
&ctx.primitives,
)?;
if let Err(ty) = ty { if let Err(ty) = ty {
return Ok(Err(ty)); return Ok(Err(ty))
} }
let ty = ty.unwrap(); let ty = ty.unwrap();
match &*ctx.unifier.get_ty(ty) { match &*ctx.unifier.get_ty(ty) {
TypeEnum::TObj { fields, obj_id, .. } TypeEnum::TObj { fields, obj_id, .. }
if *obj_id != ctx.primitives.option.obj_id(&ctx.unifier).unwrap() => if *obj_id != ctx.primitives.option.get_obj_id(&ctx.unifier) =>
{ {
// we only care about primitive attributes // we only care about primitive attributes
// for non-primitive attributes, they should be in another global // for non-primitive attributes, they should be in another global
let mut attributes = Vec::new(); let mut attributes = Vec::new();
let obj = inner_resolver.get_obj_value(py, val, ctx, generator, ty)?.unwrap(); let obj = inner_resolver.get_obj_value(py, val, ctx, generator, ty)?.unwrap();
for (name, (field_ty, is_mutable)) in fields { for (name, (field_ty, is_mutable)) in fields.iter() {
if !is_mutable { if !is_mutable {
continue; continue
} }
if gen_rpc_tag(ctx, *field_ty, &mut scratch_buffer).is_ok() { if gen_rpc_tag(ctx, *field_ty, &mut scratch_buffer).is_ok() {
attributes.push(name.to_string()); attributes.push(name.to_string());
let (index, _) = ctx.get_attr_index(ty, *name); let index = ctx.get_attr_index(ty, *name);
values.push(( values.push((*field_ty, ctx.build_gep_and_load(
*field_ty, obj.into_pointer_value(),
ctx.build_gep_and_load( &[zero, int32.const_int(index as u64, false)])));
obj.into_pointer_value(),
&[zero, int32.const_int(index as u64, false)],
None,
),
));
} }
} }
if !attributes.is_empty() { if !attributes.is_empty() {
@ -675,46 +538,33 @@ pub fn attributes_writeback(
pydict.set_item("fields", attributes)?; pydict.set_item("fields", attributes)?;
host_attributes.append(pydict)?; host_attributes.append(pydict)?;
} }
} },
TypeEnum::TObj { obj_id, params, .. } if *obj_id == PrimDef::List.id() => { TypeEnum::TList { ty: elem_ty } => {
let elem_ty = iter_type_vars(params).next().unwrap().ty; if gen_rpc_tag(ctx, *elem_ty, &mut scratch_buffer).is_ok() {
if gen_rpc_tag(ctx, elem_ty, &mut scratch_buffer).is_ok() {
let pydict = PyDict::new(py); let pydict = PyDict::new(py);
pydict.set_item("obj", val)?; pydict.set_item("obj", val)?;
host_attributes.append(pydict)?; host_attributes.append(pydict)?;
values.push(( values.push((ty, inner_resolver.get_obj_value(py, val, ctx, generator, ty)?.unwrap()));
ty,
inner_resolver.get_obj_value(py, val, ctx, generator, ty)?.unwrap(),
));
} }
} },
_ => {} _ => {}
} }
} }
let fun = FunSignature { let fun = FunSignature {
args: values args: values.iter().enumerate().map(|(i, (ty, _))| FuncArg {
.iter() name: i.to_string().into(),
.enumerate() ty: *ty,
.map(|(i, (ty, _))| FuncArg { default_value: None
name: i.to_string().into(), }).collect(),
ty: *ty,
default_value: None,
})
.collect(),
ret: ctx.primitives.none, ret: ctx.primitives.none,
vars: VarMap::default(), vars: Default::default()
}; };
let args: Vec<_> = let args: Vec<_> = values.into_iter().map(|(_, val)| (None, ValueEnum::Dynamic(val))).collect();
values.into_iter().map(|(_, val)| (None, ValueEnum::Dynamic(val))).collect(); if let Err(e) = rpc_codegen_callback_fn(ctx, None, (&fun, DefinitionId(0)), args, generator) {
if let Err(e) =
rpc_codegen_callback_fn(ctx, None, (&fun, PrimDef::Int32.id()), args, generator)
{
return Ok(Err(e)); return Ok(Err(e));
} }
Ok(Ok(())) Ok(Ok(()))
}) }).unwrap()?;
.unwrap()?;
Ok(()) Ok(())
} }

View File

@ -1,21 +1,3 @@
#![deny(
future_incompatible,
let_underscore,
nonstandard_style,
rust_2024_compatibility,
clippy::all
)]
#![warn(clippy::pedantic)]
#![allow(
unsafe_op_in_unsafe_fn,
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::enum_glob_use,
clippy::similar_names,
clippy::too_many_lines,
clippy::wildcard_imports
)]
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::fs; use std::fs;
use std::io::Write; use std::io::Write;
@ -26,22 +8,20 @@ use std::sync::Arc;
use inkwell::{ use inkwell::{
memory_buffer::MemoryBuffer, memory_buffer::MemoryBuffer,
module::{Linkage, Module}, module::{Linkage, Module},
passes::PassBuilderOptions, passes::{PassManager, PassManagerBuilder},
support::is_multithreaded,
targets::*, targets::*,
OptimizationLevel, OptimizationLevel,
}; };
use itertools::Itertools; use nac3core::codegen::gen_func_impl;
use nac3core::codegen::{gen_func_impl, CodeGenLLVMOptions, CodeGenTargetMachineOptions};
use nac3core::toplevel::builtins::get_exn_constructor; use nac3core::toplevel::builtins::get_exn_constructor;
use nac3core::typecheck::typedef::{TypeEnum, Unifier, VarMap}; use nac3core::typecheck::typedef::{TypeEnum, Unifier};
use nac3parser::{ use nac3parser::{
ast::{ExprKind, Stmt, StmtKind, StrRef}, ast::{self, ExprKind, Stmt, StmtKind, StrRef},
parser::parse_program, parser::{self, parse_program},
}; };
use pyo3::create_exception;
use pyo3::prelude::*; use pyo3::prelude::*;
use pyo3::{exceptions, types::PyBytes, types::PyDict, types::PySet}; use pyo3::{exceptions, types::PyBytes, types::PyDict, types::PySet};
use pyo3::create_exception;
use parking_lot::{Mutex, RwLock}; use parking_lot::{Mutex, RwLock};
@ -64,7 +44,7 @@ use tempfile::{self, TempDir};
use crate::codegen::attributes_writeback; use crate::codegen::attributes_writeback;
use crate::{ use crate::{
codegen::{rpc_codegen_callback, ArtiqCodeGenerator}, codegen::{rpc_codegen_callback, ArtiqCodeGenerator},
symbol_resolver::{DeferredEvaluationStore, InnerResolver, PythonHelper, Resolver}, symbol_resolver::{InnerResolver, PythonHelper, Resolver, DeferredEvaluationStore},
}; };
mod codegen; mod codegen;
@ -81,17 +61,6 @@ enum Isa {
CortexA9, CortexA9,
} }
impl Isa {
/// Returns the number of bits in `size_t` for the [`Isa`].
fn get_size_type(self) -> u32 {
if self == Isa::Host {
64u32
} else {
32u32
}
}
}
#[derive(Clone)] #[derive(Clone)]
pub struct PrimitivePythonId { pub struct PrimitivePythonId {
int: u64, int: u64,
@ -102,14 +71,9 @@ pub struct PrimitivePythonId {
float: u64, float: u64,
float64: u64, float64: u64,
bool: u64, bool: u64,
np_bool_: u64,
string: u64,
np_str_: u64,
list: u64, list: u64,
ndarray: u64,
tuple: u64, tuple: u64,
typevar: u64, typevar: u64,
const_generic_marker: u64,
none: u64, none: u64,
exception: u64, exception: u64,
generic_alias: (u64, u64), generic_alias: (u64, u64),
@ -133,9 +97,7 @@ struct Nac3 {
top_levels: Vec<TopLevelComponent>, top_levels: Vec<TopLevelComponent>,
string_store: Arc<RwLock<HashMap<String, i32>>>, string_store: Arc<RwLock<HashMap<String, i32>>>,
exception_ids: Arc<RwLock<HashMap<usize, usize>>>, exception_ids: Arc<RwLock<HashMap<usize, usize>>>,
deferred_eval_store: DeferredEvaluationStore, deferred_eval_store: DeferredEvaluationStore
/// LLVM-related options for code generation.
llvm_options: CodeGenLLVMOptions,
} }
create_exception!(nac3artiq, CompileError, exceptions::PyException); create_exception!(nac3artiq, CompileError, exceptions::PyException);
@ -143,7 +105,7 @@ create_exception!(nac3artiq, CompileError, exceptions::PyException);
impl Nac3 { impl Nac3 {
fn register_module( fn register_module(
&mut self, &mut self,
module: &PyObject, module: PyObject,
registered_class_ids: &HashSet<u64>, registered_class_ids: &HashSet<u64>,
) -> PyResult<()> { ) -> PyResult<()> {
let (module_name, source_file) = Python::with_gil(|py| -> PyResult<(String, String)> { let (module_name, source_file) = Python::with_gil(|py| -> PyResult<(String, String)> {
@ -152,16 +114,18 @@ impl Nac3 {
})?; })?;
let source = fs::read_to_string(&source_file).map_err(|e| { let source = fs::read_to_string(&source_file).map_err(|e| {
exceptions::PyIOError::new_err(format!("failed to read input file: {e}")) exceptions::PyIOError::new_err(format!("failed to read input file: {}", e))
})?; })?;
let parser_result = parse_program(&source, source_file.into()) let parser_result = parser::parse_program(&source, source_file.into())
.map_err(|e| exceptions::PySyntaxError::new_err(format!("parse error: {e}")))?; .map_err(|e| exceptions::PySyntaxError::new_err(format!("parse error: {}", e)))?;
for mut stmt in parser_result { for mut stmt in parser_result.into_iter() {
let include = match stmt.node { let include = match stmt.node {
StmtKind::ClassDef { ref decorator_list, ref mut body, ref mut bases, .. } => { ast::StmtKind::ClassDef {
ref decorator_list, ref mut body, ref mut bases, ..
} => {
let nac3_class = decorator_list.iter().any(|decorator| { let nac3_class = decorator_list.iter().any(|decorator| {
if let ExprKind::Name { id, .. } = decorator.node { if let ast::ExprKind::Name { id, .. } = decorator.node {
id.to_string() == "nac3" id.to_string() == "nac3"
} else { } else {
false false
@ -175,12 +139,11 @@ impl Nac3 {
Python::with_gil(|py| -> PyResult<bool> { Python::with_gil(|py| -> PyResult<bool> {
let id_fn = PyModule::import(py, "builtins")?.getattr("id")?; let id_fn = PyModule::import(py, "builtins")?.getattr("id")?;
match &base.node { match &base.node {
ExprKind::Name { id, .. } => { ast::ExprKind::Name { id, .. } => {
if *id == "Exception".into() { if *id == "Exception".into() {
Ok(true) Ok(true)
} else { } else {
let base_obj = let base_obj = module.getattr(py, id.to_string())?;
module.getattr(py, id.to_string().as_str())?;
let base_id = id_fn.call1((base_obj,))?.extract()?; let base_id = id_fn.call1((base_obj,))?.extract()?;
Ok(registered_class_ids.contains(&base_id)) Ok(registered_class_ids.contains(&base_id))
} }
@ -191,9 +154,9 @@ impl Nac3 {
.unwrap() .unwrap()
}); });
body.retain(|stmt| { body.retain(|stmt| {
if let StmtKind::FunctionDef { ref decorator_list, .. } = stmt.node { if let ast::StmtKind::FunctionDef { ref decorator_list, .. } = stmt.node {
decorator_list.iter().any(|decorator| { decorator_list.iter().any(|decorator| {
if let ExprKind::Name { id, .. } = decorator.node { if let ast::ExprKind::Name { id, .. } = decorator.node {
id.to_string() == "kernel" id.to_string() == "kernel"
|| id.to_string() == "portable" || id.to_string() == "portable"
|| id.to_string() == "rpc" || id.to_string() == "rpc"
@ -207,9 +170,9 @@ impl Nac3 {
}); });
true true
} }
StmtKind::FunctionDef { ref decorator_list, .. } => { ast::StmtKind::FunctionDef { ref decorator_list, .. } => {
decorator_list.iter().any(|decorator| { decorator_list.iter().any(|decorator| {
if let ExprKind::Name { id, .. } = decorator.node { if let ast::ExprKind::Name { id, .. } = decorator.node {
let id = id.to_string(); let id = id.to_string();
id == "extern" || id == "portable" || id == "kernel" || id == "rpc" id == "extern" || id == "portable" || id == "kernel" || id == "rpc"
} else { } else {
@ -230,7 +193,7 @@ impl Nac3 {
fn report_modinit( fn report_modinit(
arg_names: &[String], arg_names: &[String],
method_name: &str, method_name: &str,
resolver: &Arc<dyn SymbolResolver + Send + Sync>, resolver: Arc<dyn SymbolResolver + Send + Sync>,
top_level_defs: &[Arc<RwLock<TopLevelDef>>], top_level_defs: &[Arc<RwLock<TopLevelDef>>],
unifier: &mut Unifier, unifier: &mut Unifier,
primitives: &PrimitiveStore, primitives: &PrimitiveStore,
@ -238,7 +201,7 @@ impl Nac3 {
let base_ty = let base_ty =
match resolver.get_symbol_type(unifier, top_level_defs, primitives, "base".into()) { match resolver.get_symbol_type(unifier, top_level_defs, primitives, "base".into()) {
Ok(ty) => ty, Ok(ty) => ty,
Err(e) => return Some(format!("type error inside object launching kernel: {e}")), Err(e) => return Some(format!("type error inside object launching kernel: {}", e)),
}; };
let fun_ty = if method_name.is_empty() { let fun_ty = if method_name.is_empty() {
@ -248,7 +211,8 @@ impl Nac3 {
Some(t) => t.0, Some(t) => t.0,
None => { None => {
return Some(format!( return Some(format!(
"object launching kernel does not have method `{method_name}`" "object launching kernel does not have method `{}`",
method_name
)) ))
} }
} }
@ -269,7 +233,8 @@ impl Nac3 {
Some(n) => n, Some(n) => n,
None if default_value.is_none() => { None if default_value.is_none() => {
return Some(format!( return Some(format!(
"argument `{name}` not provided when launching kernel function" "argument `{}` not provided when launching kernel function",
name
)) ))
} }
_ => break, _ => break,
@ -283,14 +248,16 @@ impl Nac3 {
Ok(t) => t, Ok(t) => t,
Err(e) => { Err(e) => {
return Some(format!( return Some(format!(
"type error ({e}) at parameter #{i} when calling kernel function" "type error ({}) at parameter #{} when calling kernel function",
e, i
)) ))
} }
}; };
if let Err(e) = unifier.unify(in_ty, *ty) { if let Err(e) = unifier.unify(in_ty, *ty) {
return Some(format!( return Some(format!(
"type error ({}) at parameter #{i} when calling kernel function", "type error ({}) at parameter #{} when calling kernel function",
e.to_display(unifier), e.to_display(unifier).to_string(),
i
)); ));
} }
} }
@ -309,11 +276,9 @@ impl Nac3 {
py: Python, py: Python,
link_fn: &dyn Fn(&Module) -> PyResult<T>, link_fn: &dyn Fn(&Module) -> PyResult<T>,
) -> PyResult<T> { ) -> PyResult<T> {
let size_t = self.isa.get_size_type();
let (mut composer, mut builtins_def, mut builtins_ty) = TopLevelComposer::new( let (mut composer, mut builtins_def, mut builtins_ty) = TopLevelComposer::new(
self.builtins.clone(), self.builtins.clone(),
ComposerConfig { kernel_ann: Some("Kernel"), kernel_invariant_ann: "KernelInvariant" }, ComposerConfig { kernel_ann: Some("Kernel"), kernel_invariant_ann: "KernelInvariant" },
size_t,
); );
let builtins = PyModule::import(py, "builtins")?; let builtins = PyModule::import(py, "builtins")?;
@ -354,16 +319,15 @@ impl Nac3 {
let mut module_to_resolver_cache: HashMap<u64, _> = HashMap::new(); let mut module_to_resolver_cache: HashMap<u64, _> = HashMap::new();
let mut rpc_ids = vec![]; let mut rpc_ids = vec![];
for (stmt, path, module) in &self.top_levels { for (stmt, path, module) in self.top_levels.iter() {
let py_module: &PyAny = module.extract(py)?; let py_module: &PyAny = module.extract(py)?;
let module_id: u64 = id_fn.call1((py_module,))?.extract()?; let module_id: u64 = id_fn.call1((py_module,))?.extract()?;
let helper = helper.clone(); let helper = helper.clone();
let class_obj; let class_obj;
if let StmtKind::ClassDef { name, .. } = &stmt.node { if let StmtKind::ClassDef { name, .. } = &stmt.node {
let class = py_module.getattr(name.to_string().as_str()).unwrap(); let class = py_module.getattr(name.to_string()).unwrap();
if issubclass.call1((class, exn_class)).unwrap().extract().unwrap() if issubclass.call1((class, exn_class)).unwrap().extract().unwrap() &&
&& class.getattr("artiq_builtin").is_err() class.getattr("artiq_builtin").is_err() {
{
class_obj = Some(class); class_obj = Some(class);
} else { } else {
class_obj = None; class_obj = None;
@ -375,8 +339,8 @@ impl Nac3 {
module_to_resolver_cache.get(&module_id).cloned().unwrap_or_else(|| { module_to_resolver_cache.get(&module_id).cloned().unwrap_or_else(|| {
let mut name_to_pyid: HashMap<StrRef, u64> = HashMap::new(); let mut name_to_pyid: HashMap<StrRef, u64> = HashMap::new();
let members: &PyDict = let members: &PyDict =
py_module.getattr("__dict__").unwrap().downcast().unwrap(); py_module.getattr("__dict__").unwrap().cast_as().unwrap();
for (key, val) in members { for (key, val) in members.iter() {
let key: &str = key.extract().unwrap(); let key: &str = key.extract().unwrap();
let val = id_fn.call1((val,)).unwrap().extract().unwrap(); let val = id_fn.call1((val,)).unwrap().extract().unwrap();
name_to_pyid.insert(key.into(), val); name_to_pyid.insert(key.into(), val);
@ -388,12 +352,12 @@ impl Nac3 {
pyid_to_type: pyid_to_type.clone(), pyid_to_type: pyid_to_type.clone(),
primitive_ids: self.primitive_ids.clone(), primitive_ids: self.primitive_ids.clone(),
global_value_ids: global_value_ids.clone(), global_value_ids: global_value_ids.clone(),
class_names: Mutex::default(), class_names: Default::default(),
name_to_pyid: name_to_pyid.clone(), name_to_pyid: name_to_pyid.clone(),
module: module.clone(), module: module.clone(),
id_to_pyval: RwLock::default(), id_to_pyval: Default::default(),
id_to_primitive: RwLock::default(), id_to_primitive: Default::default(),
field_to_val: RwLock::default(), field_to_val: Default::default(),
helper, helper,
string_store: self.string_store.clone(), string_store: self.string_store.clone(),
exception_ids: self.exception_ids.clone(), exception_ids: self.exception_ids.clone(),
@ -407,27 +371,28 @@ impl Nac3 {
}); });
let (name, def_id, ty) = composer let (name, def_id, ty) = composer
.register_top_level(stmt.clone(), Some(resolver.clone()), path, false) .register_top_level(stmt.clone(), Some(resolver.clone()), path.clone(), false)
.map_err(|e| { .map_err(|e| {
CompileError::new_err(format!("compilation failed\n----------\n{e}")) CompileError::new_err(format!(
"compilation failed\n----------\n{}",
e
))
})?; })?;
if let Some(class_obj) = class_obj { if let Some(class_obj) = class_obj {
self.exception_ids self.exception_ids.write().insert(def_id.0, store_obj.call1(py, (class_obj, ))?.extract(py)?);
.write()
.insert(def_id.0, store_obj.call1(py, (class_obj,))?.extract(py)?);
} }
match &stmt.node { match &stmt.node {
StmtKind::FunctionDef { decorator_list, .. } => { StmtKind::FunctionDef { decorator_list, .. } => {
if decorator_list.iter().any(|decorator| matches!(decorator.node, ExprKind::Name { id, .. } if id == "rpc".into())) { if decorator_list.iter().any(|decorator| matches!(decorator.node, ExprKind::Name { id, .. } if id == "rpc".into())) {
store_fun.call1(py, (def_id.0.into_py(py), module.getattr(py, name.to_string().as_str()).unwrap())).unwrap(); store_fun.call1(py, (def_id.0.into_py(py), module.getattr(py, name.to_string()).unwrap())).unwrap();
rpc_ids.push((None, def_id)); rpc_ids.push((None, def_id));
} }
} }
StmtKind::ClassDef { name, body, .. } => { StmtKind::ClassDef { name, body, .. } => {
let class_name = name.to_string(); let class_name = name.to_string();
let class_obj = module.getattr(py, class_name.as_str()).unwrap(); let class_obj = module.getattr(py, &class_name).unwrap();
for stmt in body { for stmt in body.iter() {
if let StmtKind::FunctionDef { name, decorator_list, .. } = &stmt.node { if let StmtKind::FunctionDef { name, decorator_list, .. } = &stmt.node {
if decorator_list.iter().any(|decorator| matches!(decorator.node, ExprKind::Name { id, .. } if id == "rpc".into())) { if decorator_list.iter().any(|decorator| matches!(decorator.node, ExprKind::Name { id, .. } if id == "rpc".into())) {
if name == &"__init__".into() { if name == &"__init__".into() {
@ -461,7 +426,7 @@ impl Nac3 {
name_to_pyid.insert("base".into(), id_fun.call1((obj,))?.extract()?); name_to_pyid.insert("base".into(), id_fun.call1((obj,))?.extract()?);
let mut arg_names = vec![]; let mut arg_names = vec![];
for (i, arg) in args.into_iter().enumerate() { for (i, arg) in args.into_iter().enumerate() {
let name = format!("tmp{i}"); let name = format!("tmp{}", i);
module.add(&name, arg)?; module.add(&name, arg)?;
name_to_pyid.insert(name.clone().into(), id_fun.call1((arg,))?.extract()?); name_to_pyid.insert(name.clone().into(), id_fun.call1((arg,))?.extract()?);
arg_names.push(name); arg_names.push(name);
@ -480,10 +445,10 @@ impl Nac3 {
pyid_to_type: pyid_to_type.clone(), pyid_to_type: pyid_to_type.clone(),
primitive_ids: self.primitive_ids.clone(), primitive_ids: self.primitive_ids.clone(),
global_value_ids: global_value_ids.clone(), global_value_ids: global_value_ids.clone(),
class_names: Mutex::default(), class_names: Default::default(),
id_to_pyval: RwLock::default(), id_to_pyval: Default::default(),
id_to_primitive: RwLock::default(), id_to_primitive: Default::default(),
field_to_val: RwLock::default(), field_to_val: Default::default(),
name_to_pyid, name_to_pyid,
module: module.to_object(py), module: module.to_object(py),
helper, helper,
@ -491,52 +456,47 @@ impl Nac3 {
exception_ids: self.exception_ids.clone(), exception_ids: self.exception_ids.clone(),
deferred_eval_store: self.deferred_eval_store.clone(), deferred_eval_store: self.deferred_eval_store.clone(),
}); });
let resolver = let resolver = Arc::new(Resolver(inner_resolver.clone())) as Arc<dyn SymbolResolver + Send + Sync>;
Arc::new(Resolver(inner_resolver.clone())) as Arc<dyn SymbolResolver + Send + Sync>;
let (_, def_id, _) = composer let (_, def_id, _) = composer
.register_top_level(synthesized.pop().unwrap(), Some(resolver.clone()), "", false) .register_top_level(synthesized.pop().unwrap(), Some(resolver.clone()), "".into(), false)
.unwrap(); .unwrap();
let fun_signature = let fun_signature =
FunSignature { args: vec![], ret: self.primitive.none, vars: VarMap::new() }; FunSignature { args: vec![], ret: self.primitive.none, vars: HashMap::new() };
let mut store = ConcreteTypeStore::new(); let mut store = ConcreteTypeStore::new();
let mut cache = HashMap::new(); let mut cache = HashMap::new();
let signature = store.from_signature( let signature =
&mut composer.unifier, store.from_signature(&mut composer.unifier, &self.primitive, &fun_signature, &mut cache);
&self.primitive,
&fun_signature,
&mut cache,
);
let signature = store.add_cty(signature); let signature = store.add_cty(signature);
if let Err(e) = composer.start_analysis(true) { if let Err(e) = composer.start_analysis(true) {
// report error of __modinit__ separately // report error of __modinit__ separately
return if e.iter().any(|err| err.contains("<nac3_synthesized_modinit>")) { if !e.contains("<nac3_synthesized_modinit>") {
return Err(CompileError::new_err(format!(
"compilation failed\n----------\n{}",
e
)));
} else {
let msg = Self::report_modinit( let msg = Self::report_modinit(
&arg_names, &arg_names,
method_name, method_name,
&resolver, resolver.clone(),
&composer.extract_def_list(), &composer.extract_def_list(),
&mut composer.unifier, &mut composer.unifier,
&self.primitive, &self.primitive,
); );
Err(CompileError::new_err(format!( return Err(CompileError::new_err(format!(
"compilation failed\n----------\n{}", "compilation failed\n----------\n{}",
msg.unwrap_or(e.iter().sorted().join("\n----------\n")) msg.unwrap_or(e)
))) )));
} else { }
Err(CompileError::new_err(format!(
"compilation failed\n----------\n{}",
e.iter().sorted().join("\n----------\n"),
)))
};
} }
let top_level = Arc::new(composer.make_top_level_context()); let top_level = Arc::new(composer.make_top_level_context());
{ {
let rpc_codegen = rpc_codegen_callback(); let rpc_codegen = rpc_codegen_callback();
let defs = top_level.definitions.read(); let defs = top_level.definitions.read();
for (class_data, id) in &rpc_ids { for (class_data, id) in rpc_ids.iter() {
let mut def = defs[id.0].write(); let mut def = defs[id.0].write();
match &mut *def { match &mut *def {
TopLevelDef::Function { codegen_callback, .. } => { TopLevelDef::Function { codegen_callback, .. } => {
@ -544,7 +504,7 @@ impl Nac3 {
} }
TopLevelDef::Class { methods, .. } => { TopLevelDef::Class { methods, .. } => {
let (class_def, method_name) = class_data.as_ref().unwrap(); let (class_def, method_name) = class_data.as_ref().unwrap();
for (name, _, id) in &*methods { for (name, _, id) in methods.iter() {
if name != method_name { if name != method_name {
continue; continue;
} }
@ -557,9 +517,7 @@ impl Nac3 {
py, py,
( (
id.0.into_py(py), id.0.into_py(py),
class_def class_def.getattr(py, name.to_string()).unwrap(),
.getattr(py, name.to_string().as_str())
.unwrap(),
), ),
) )
.unwrap(); .unwrap();
@ -573,18 +531,18 @@ impl Nac3 {
let instance = { let instance = {
let defs = top_level.definitions.read(); let defs = top_level.definitions.read();
let mut definition = defs[def_id.0].write(); let mut definition = defs[def_id.0].write();
let TopLevelDef::Function { instance_to_stmt, instance_to_symbol, .. } = if let TopLevelDef::Function { instance_to_stmt, instance_to_symbol, .. } =
&mut *definition &mut *definition
else { {
instance_to_symbol.insert("".to_string(), "__modinit__".into());
instance_to_stmt[""].clone()
} else {
unreachable!() unreachable!()
}; }
instance_to_symbol.insert(String::new(), "__modinit__".into());
instance_to_stmt[""].clone()
}; };
let task = CodeGenTask { let task = CodeGenTask {
subst: Vec::default(), subst: Default::default(),
symbol_name: "__modinit__".to_string(), symbol_name: "__modinit__".to_string(),
body: instance.body, body: instance.body,
signature, signature,
@ -597,26 +555,22 @@ impl Nac3 {
let mut store = ConcreteTypeStore::new(); let mut store = ConcreteTypeStore::new();
let mut cache = HashMap::new(); let mut cache = HashMap::new();
let signature = store.from_signature( let signature =
&mut composer.unifier, store.from_signature(&mut composer.unifier, &self.primitive, &fun_signature, &mut cache);
&self.primitive,
&fun_signature,
&mut cache,
);
let signature = store.add_cty(signature); let signature = store.add_cty(signature);
let attributes_writeback_task = CodeGenTask { let attributes_writeback_task = CodeGenTask {
subst: Vec::default(), subst: Default::default(),
symbol_name: "attributes_writeback".to_string(), symbol_name: "attributes_writeback".to_string(),
body: Arc::new(Vec::default()), body: Arc::new(Default::default()),
signature, signature,
resolver, resolver,
store, store,
unifier_index: instance.unifier_id, unifier_index: instance.unifier_id,
calls: Arc::new(HashMap::default()), calls: Arc::new(Default::default()),
id: 0, id: 0,
}; };
let membuffers: Arc<Mutex<Vec<Vec<u8>>>> = Arc::default(); let membuffers: Arc<Mutex<Vec<Vec<u8>>>> = Default::default();
let membuffer = membuffers.clone(); let membuffer = membuffers.clone();
@ -626,8 +580,7 @@ impl Nac3 {
membuffer.lock().push(buffer); membuffer.lock().push(buffer);
}))); })));
let size_t = if self.isa == Isa::Host { 64 } else { 32 }; let size_t = if self.isa == Isa::Host { 64 } else { 32 };
let num_threads = if is_multithreaded() { 4 } else { 1 }; let thread_names: Vec<String> = (0..4).map(|_| "main".to_string()).collect();
let thread_names: Vec<String> = (0..num_threads).map(|_| "main".to_string()).collect();
let threads: Vec<_> = thread_names let threads: Vec<_> = thread_names
.iter() .iter()
.map(|s| Box::new(ArtiqCodeGenerator::new(s.to_string(), size_t, self.time_fns))) .map(|s| Box::new(ArtiqCodeGenerator::new(s.to_string(), size_t, self.time_fns)))
@ -635,28 +588,18 @@ impl Nac3 {
let membuffer = membuffers.clone(); let membuffer = membuffers.clone();
py.allow_threads(|| { py.allow_threads(|| {
let (registry, handles) = let (registry, handles) = WorkerRegistry::create_workers(threads, top_level.clone(), f);
WorkerRegistry::create_workers(threads, top_level.clone(), &self.llvm_options, &f);
registry.add_task(task); registry.add_task(task);
registry.wait_tasks_complete(handles); registry.wait_tasks_complete(handles);
let mut generator = let mut generator = ArtiqCodeGenerator::new("attributes_writeback".to_string(), size_t, self.time_fns);
ArtiqCodeGenerator::new("attributes_writeback".to_string(), size_t, self.time_fns);
let context = inkwell::context::Context::create(); let context = inkwell::context::Context::create();
let module = context.create_module("attributes_writeback"); let module = context.create_module("attributes_writeback");
let builder = context.create_builder(); let builder = context.create_builder();
let (_, module, _) = gen_func_impl( let (_, module, _) = gen_func_impl(&context, &mut generator, &registry, builder, module,
&context, attributes_writeback_task, |generator, ctx| {
&mut generator, attributes_writeback(ctx, generator, inner_resolver.as_ref(), host_attributes)
&registry, }).unwrap();
builder,
module,
attributes_writeback_task,
|generator, ctx| {
attributes_writeback(ctx, generator, inner_resolver.as_ref(), &host_attributes)
},
)
.unwrap();
let buffer = module.write_bitcode_to_memory(); let buffer = module.write_bitcode_to_memory();
let buffer = buffer.as_slice().into(); let buffer = buffer.as_slice().into();
membuffer.lock().push(buffer); membuffer.lock().push(buffer);
@ -672,24 +615,13 @@ impl Nac3 {
.create_module_from_ir(MemoryBuffer::create_from_memory_range(buffer, "main")) .create_module_from_ir(MemoryBuffer::create_from_memory_range(buffer, "main"))
.unwrap(); .unwrap();
main.link_in_module(other).map_err(|err| CompileError::new_err(err.to_string()))?; main.link_in_module(other)
.map_err(|err| CompileError::new_err(err.to_string()))?;
} }
let builder = context.create_builder(); let builder = context.create_builder();
let modinit_return = main let modinit_return = main.get_function("__modinit__").unwrap().get_last_basic_block().unwrap().get_terminator().unwrap();
.get_function("__modinit__")
.unwrap()
.get_last_basic_block()
.unwrap()
.get_terminator()
.unwrap();
builder.position_before(&modinit_return); builder.position_before(&modinit_return);
builder builder.build_call(main.get_function("attributes_writeback").unwrap(), &[], "attributes_writeback");
.build_call(
main.get_function("attributes_writeback").unwrap(),
&[],
"attributes_writeback",
)
.unwrap();
main.link_in_module(load_irrt(&context)) main.link_in_module(load_irrt(&context))
.map_err(|err| CompileError::new_err(err.to_string()))?; .map_err(|err| CompileError::new_err(err.to_string()))?;
@ -697,13 +629,16 @@ impl Nac3 {
let mut function_iter = main.get_first_function(); let mut function_iter = main.get_first_function();
while let Some(func) = function_iter { while let Some(func) = function_iter {
if func.count_basic_blocks() > 0 && func.get_name().to_str().unwrap() != "__modinit__" { if func.count_basic_blocks() > 0 && func.get_name().to_str().unwrap() != "__modinit__" {
func.set_linkage(Linkage::Private); func.set_linkage(inkwell::module::Linkage::Private);
} }
function_iter = func.get_next_function(); function_iter = func.get_next_function();
} }
// Demote all global variables that will not be referenced in the kernel to private // Demote all global variables that will not be referenced in the kernel to private
let preserved_symbols: Vec<&'static [u8]> = vec![b"typeinfo", b"now"]; let preserved_symbols: Vec<&'static [u8]> = vec![
b"typeinfo",
b"now",
];
let mut global_option = main.get_first_global(); let mut global_option = main.get_first_global();
while let Some(global) = global_option { while let Some(global) = global_option {
if !preserved_symbols.contains(&(global.get_name().to_bytes())) { if !preserved_symbols.contains(&(global.get_name().to_bytes())) {
@ -712,73 +647,52 @@ impl Nac3 {
global_option = global.get_next_global(); global_option = global.get_next_global();
} }
let target_machine = self let builder = PassManagerBuilder::create();
.llvm_options builder.set_optimization_level(OptimizationLevel::Aggressive);
.target let passes = PassManager::create(());
.create_target_machine(self.llvm_options.opt_level) builder.set_inliner_with_threshold(255);
.expect("couldn't create target machine"); builder.populate_module_pass_manager(&passes);
passes.run_on(&main);
let pass_options = PassBuilderOptions::create();
pass_options.set_merge_functions(true);
let passes = format!("default<O{}>", self.llvm_options.opt_level as u32);
let result = main.run_passes(passes.as_str(), &target_machine, pass_options);
if let Err(err) = result {
panic!("Failed to run optimization for module `main`: {}", err.to_string());
}
link_fn(&main) link_fn(&main)
} }
/// Returns the [`TargetTriple`] used for compiling to [isa]. fn get_llvm_target_machine(
fn get_llvm_target_triple(isa: Isa) -> TargetTriple { &self,
match isa { ) -> TargetMachine {
Isa::Host => TargetMachine::get_default_triple(), let (triple, features) = match self.isa {
Isa::RiscV32G | Isa::RiscV32IMA => TargetTriple::create("riscv32-unknown-linux"), Isa::Host => (
Isa::CortexA9 => TargetTriple::create("armv7-unknown-linux-gnueabihf"), TargetMachine::get_default_triple(),
} TargetMachine::get_host_cpu_features().to_string(),
} ),
Isa::RiscV32G => {
/// Returns the [`String`] representing the target CPU used for compiling to [isa]. (TargetTriple::create("riscv32-unknown-linux"), "+a,+m,+f,+d".to_string())
fn get_llvm_target_cpu(isa: Isa) -> String { }
match isa { Isa::RiscV32IMA => (TargetTriple::create("riscv32-unknown-linux"), "+a,+m".to_string()),
Isa::Host => TargetMachine::get_host_cpu_name().to_string(), Isa::CortexA9 => (
Isa::RiscV32G | Isa::RiscV32IMA => "generic-rv32".to_string(), TargetTriple::create("armv7-unknown-linux-gnueabihf"),
Isa::CortexA9 => "cortex-a9".to_string(), "+dsp,+fp16,+neon,+vfp3,+long-calls".to_string(),
} ),
} };
let target =
/// Returns the [`String`] representing the target features used for compiling to [isa]. Target::from_triple(&triple).expect("couldn't create target from target triple");
fn get_llvm_target_features(isa: Isa) -> String { target
match isa { .create_target_machine(
Isa::Host => TargetMachine::get_host_cpu_features().to_string(), &triple,
Isa::RiscV32G => "+a,+m,+f,+d".to_string(), "",
Isa::RiscV32IMA => "+a,+m".to_string(), &features,
Isa::CortexA9 => "+dsp,+fp16,+neon,+vfp3,+long-calls".to_string(), OptimizationLevel::Default,
} RelocMode::PIC,
} CodeModel::Default,
)
/// Returns an instance of [`CodeGenTargetMachineOptions`] representing the target machine
/// options used for compiling to [isa].
fn get_llvm_target_options(isa: Isa) -> CodeGenTargetMachineOptions {
CodeGenTargetMachineOptions {
triple: Nac3::get_llvm_target_triple(isa).as_str().to_string_lossy().into_owned(),
cpu: Nac3::get_llvm_target_cpu(isa),
features: Nac3::get_llvm_target_features(isa),
reloc_mode: RelocMode::PIC,
..CodeGenTargetMachineOptions::from_host()
}
}
/// Returns an instance of [`TargetMachine`] used in compiling and linking of a program to the
/// target [isa].
fn get_llvm_target_machine(&self) -> TargetMachine {
Nac3::get_llvm_target_options(self.isa)
.create_target_machine(self.llvm_options.opt_level)
.expect("couldn't create target machine") .expect("couldn't create target machine")
} }
} }
fn link_with_lld(elf_filename: String, obj_filename: String) -> PyResult<()> { fn link_with_lld(
elf_filename: String,
obj_filename: String,
) -> PyResult<()>{
let linker_args = vec![ let linker_args = vec![
"-shared".to_string(), "-shared".to_string(),
"--eh-frame-hdr".to_string(), "--eh-frame-hdr".to_string(),
@ -797,7 +711,9 @@ fn link_with_lld(elf_filename: String, obj_filename: String) -> PyResult<()> {
return Err(CompileError::new_err("failed to start linker")); return Err(CompileError::new_err("failed to start linker"));
} }
} else { } else {
return Err(CompileError::new_err("linker returned non-zero status code")); return Err(CompileError::new_err(
"linker returned non-zero status code",
));
} }
Ok(()) Ok(())
@ -807,7 +723,7 @@ fn add_exceptions(
composer: &mut TopLevelComposer, composer: &mut TopLevelComposer,
builtin_def: &mut HashMap<StrRef, DefinitionId>, builtin_def: &mut HashMap<StrRef, DefinitionId>,
builtin_ty: &mut HashMap<StrRef, Type>, builtin_ty: &mut HashMap<StrRef, Type>,
error_names: &[&str], error_names: &[&str]
) -> Vec<Type> { ) -> Vec<Type> {
let mut types = Vec::new(); let mut types = Vec::new();
// note: this is only for builtin exceptions, i.e. the exception name is "0:{exn}" // note: this is only for builtin exceptions, i.e. the exception name is "0:{exn}"
@ -820,7 +736,7 @@ fn add_exceptions(
// constructor id // constructor id
def_id + 1, def_id + 1,
&mut composer.unifier, &mut composer.unifier,
&composer.primitives_ty, &composer.primitives_ty
); );
composer.definition_ast_list.push((Arc::new(RwLock::new(exception_class)), None)); composer.definition_ast_list.push((Arc::new(RwLock::new(exception_class)), None));
composer.definition_ast_list.push((Arc::new(RwLock::new(exception_fn)), None)); composer.definition_ast_list.push((Arc::new(RwLock::new(exception_fn)), None));
@ -834,7 +750,7 @@ fn add_exceptions(
#[pymethods] #[pymethods]
impl Nac3 { impl Nac3 {
#[new] #[new]
fn new(isa: &str, artiq_builtins: &PyDict, py: Python) -> PyResult<Self> { fn new(isa: &str, py: Python) -> PyResult<Self> {
let isa = match isa { let isa = match isa {
"host" => Isa::Host, "host" => Isa::Host,
"rv32g" => Isa::RiscV32G, "rv32g" => Isa::RiscV32G,
@ -843,15 +759,16 @@ impl Nac3 {
_ => return Err(exceptions::PyValueError::new_err("invalid ISA")), _ => return Err(exceptions::PyValueError::new_err("invalid ISA")),
}; };
let time_fns: &(dyn TimeFns + Sync) = match isa { let time_fns: &(dyn TimeFns + Sync) = match isa {
Isa::Host => &timeline::EXTERN_TIME_FNS,
Isa::RiscV32G => &timeline::NOW_PINNING_TIME_FNS_64, Isa::RiscV32G => &timeline::NOW_PINNING_TIME_FNS_64,
Isa::RiscV32IMA => &timeline::NOW_PINNING_TIME_FNS, Isa::RiscV32IMA => &timeline::NOW_PINNING_TIME_FNS,
Isa::CortexA9 | Isa::Host => &timeline::EXTERN_TIME_FNS, Isa::CortexA9 => &timeline::EXTERN_TIME_FNS,
}; };
let primitive: PrimitiveStore = TopLevelComposer::make_primitives(isa.get_size_type()).0; let primitive: PrimitiveStore = TopLevelComposer::make_primitives().0;
let builtins = vec![ let builtins = vec![
( (
"now_mu".into(), "now_mu".into(),
FunSignature { args: vec![], ret: primitive.int64, vars: VarMap::new() }, FunSignature { args: vec![], ret: primitive.int64, vars: HashMap::new() },
Arc::new(GenCall::new(Box::new(move |ctx, _, _, _, _| { Arc::new(GenCall::new(Box::new(move |ctx, _, _, _, _| {
Ok(Some(time_fns.emit_now_mu(ctx))) Ok(Some(time_fns.emit_now_mu(ctx)))
}))), }))),
@ -865,12 +782,11 @@ impl Nac3 {
default_value: None, default_value: None,
}], }],
ret: primitive.none, ret: primitive.none,
vars: VarMap::new(), vars: HashMap::new(),
}, },
Arc::new(GenCall::new(Box::new(move |ctx, _, fun, args, generator| { Arc::new(GenCall::new(Box::new(move |ctx, _, fun, args, generator| {
let arg_ty = fun.0.args[0].ty; let arg_ty = fun.0.args[0].ty;
let arg = let arg = args[0].1.clone().to_basic_value_enum(ctx, generator, arg_ty).unwrap();
args[0].1.clone().to_basic_value_enum(ctx, generator, arg_ty).unwrap();
time_fns.emit_at_mu(ctx, arg); time_fns.emit_at_mu(ctx, arg);
Ok(None) Ok(None)
}))), }))),
@ -884,12 +800,11 @@ impl Nac3 {
default_value: None, default_value: None,
}], }],
ret: primitive.none, ret: primitive.none,
vars: VarMap::new(), vars: HashMap::new(),
}, },
Arc::new(GenCall::new(Box::new(move |ctx, _, fun, args, generator| { Arc::new(GenCall::new(Box::new(move |ctx, _, fun, args, generator| {
let arg_ty = fun.0.args[0].ty; let arg_ty = fun.0.args[0].ty;
let arg = let arg = args[0].1.clone().to_basic_value_enum(ctx, generator, arg_ty).unwrap();
args[0].1.clone().to_basic_value_enum(ctx, generator, arg_ty).unwrap();
time_fns.emit_delay_mu(ctx, arg); time_fns.emit_delay_mu(ctx, arg);
Ok(None) Ok(None)
}))), }))),
@ -902,37 +817,57 @@ impl Nac3 {
let typing_mod = PyModule::import(py, "typing").unwrap(); let typing_mod = PyModule::import(py, "typing").unwrap();
let types_mod = PyModule::import(py, "types").unwrap(); let types_mod = PyModule::import(py, "types").unwrap();
let get_id = |x: &PyAny| id_fn.call1((x,)).and_then(PyAny::extract).unwrap(); let get_id = |x| id_fn.call1((x,)).unwrap().extract().unwrap();
let get_attr_id = |obj: &PyModule, attr| { let get_attr_id = |obj: &PyModule, attr| id_fn.call1((obj.getattr(attr).unwrap(),))
id_fn.call1((obj.getattr(attr).unwrap(),)).unwrap().extract().unwrap() .unwrap().extract().unwrap();
};
let primitive_ids = PrimitivePythonId { let primitive_ids = PrimitivePythonId {
virtual_id: get_id(artiq_builtins.get_item("virtual").ok().flatten().unwrap()), virtual_id: get_id(
builtins_mod
.getattr("globals")
.unwrap()
.call0()
.unwrap()
.get_item("virtual")
.unwrap(
)),
generic_alias: ( generic_alias: (
get_attr_id(typing_mod, "_GenericAlias"), get_attr_id(typing_mod, "_GenericAlias"),
get_attr_id(types_mod, "GenericAlias"), get_attr_id(types_mod, "GenericAlias"),
), ),
none: get_id(artiq_builtins.get_item("none").ok().flatten().unwrap()), none: id_fn
.call1((builtins_mod
.getattr("globals")
.unwrap()
.call0()
.unwrap()
.get_item("none")
.unwrap(),))
.unwrap()
.extract()
.unwrap(),
typevar: get_attr_id(typing_mod, "TypeVar"), typevar: get_attr_id(typing_mod, "TypeVar"),
const_generic_marker: get_id(
artiq_builtins.get_item("_ConstGenericMarker").ok().flatten().unwrap(),
),
int: get_attr_id(builtins_mod, "int"), int: get_attr_id(builtins_mod, "int"),
int32: get_attr_id(numpy_mod, "int32"), int32: get_attr_id(numpy_mod, "int32"),
int64: get_attr_id(numpy_mod, "int64"), int64: get_attr_id(numpy_mod, "int64"),
uint32: get_attr_id(numpy_mod, "uint32"), uint32: get_attr_id(numpy_mod, "uint32"),
uint64: get_attr_id(numpy_mod, "uint64"), uint64: get_attr_id(numpy_mod, "uint64"),
bool: get_attr_id(builtins_mod, "bool"), bool: get_attr_id(builtins_mod, "bool"),
np_bool_: get_attr_id(numpy_mod, "bool_"),
string: get_attr_id(builtins_mod, "str"),
np_str_: get_attr_id(numpy_mod, "str_"),
float: get_attr_id(builtins_mod, "float"), float: get_attr_id(builtins_mod, "float"),
float64: get_attr_id(numpy_mod, "float64"), float64: get_attr_id(numpy_mod, "float64"),
list: get_attr_id(builtins_mod, "list"), list: get_attr_id(builtins_mod, "list"),
ndarray: get_attr_id(numpy_mod, "ndarray"),
tuple: get_attr_id(builtins_mod, "tuple"), tuple: get_attr_id(builtins_mod, "tuple"),
exception: get_attr_id(builtins_mod, "Exception"), exception: get_attr_id(builtins_mod, "Exception"),
option: get_id(artiq_builtins.get_item("Option").ok().flatten().unwrap()), option: id_fn
.call1((builtins_mod
.getattr("globals")
.unwrap()
.call0()
.unwrap()
.get_item("Option")
.unwrap(),))
.unwrap()
.extract()
.unwrap(),
}; };
let working_directory = tempfile::Builder::new().prefix("nac3-").tempdir().unwrap(); let working_directory = tempfile::Builder::new().prefix("nac3-").tempdir().unwrap();
@ -944,16 +879,12 @@ impl Nac3 {
primitive, primitive,
builtins, builtins,
primitive_ids, primitive_ids,
top_levels: Vec::default(), top_levels: Default::default(),
pyid_to_def: Arc::default(), pyid_to_def: Default::default(),
working_directory, working_directory,
string_store: Arc::default(), string_store: Default::default(),
exception_ids: Arc::default(), exception_ids: Default::default(),
deferred_eval_store: DeferredEvaluationStore::new(), deferred_eval_store: DeferredEvaluationStore::new(),
llvm_options: CodeGenLLVMOptions {
opt_level: OptimizationLevel::Default,
target: Nac3::get_llvm_target_options(isa),
},
}) })
} }
@ -966,11 +897,11 @@ impl Nac3 {
let id_fn = PyModule::import(py, "builtins")?.getattr("id")?; let id_fn = PyModule::import(py, "builtins")?.getattr("id")?;
let getmodule_fn = PyModule::import(py, "inspect")?.getattr("getmodule")?; let getmodule_fn = PyModule::import(py, "inspect")?.getattr("getmodule")?;
for function in functions { for function in functions.iter() {
let module = getmodule_fn.call1((function,))?.extract()?; let module = getmodule_fn.call1((function,))?.extract()?;
modules.insert(id_fn.call1((&module,))?.extract()?, module); modules.insert(id_fn.call1((&module,))?.extract()?, module);
} }
for class in classes { for class in classes.iter() {
let module = getmodule_fn.call1((class,))?.extract()?; let module = getmodule_fn.call1((class,))?.extract()?;
modules.insert(id_fn.call1((&module,))?.extract()?, module); modules.insert(id_fn.call1((&module,))?.extract()?, module);
class_ids.insert(id_fn.call1((class,))?.extract()?); class_ids.insert(id_fn.call1((class,))?.extract()?);
@ -979,7 +910,7 @@ impl Nac3 {
})?; })?;
for module in modules.into_values() { for module in modules.into_values() {
self.register_module(&module, &class_ids)?; self.register_module(module, &class_ids)?;
} }
Ok(()) Ok(())
} }
@ -1003,7 +934,7 @@ impl Nac3 {
.expect("couldn't write module to file"); .expect("couldn't write module to file");
link_with_lld( link_with_lld(
filename.to_string(), filename.to_string(),
working_directory.join("module.o").to_string_lossy().to_string(), working_directory.join("module.o").to_string_lossy().to_string()
)?; )?;
Ok(()) Ok(())
}; };
@ -1044,14 +975,14 @@ impl Nac3 {
let link_fn = |module: &Module| { let link_fn = |module: &Module| {
let working_directory = self.working_directory.path().to_owned(); let working_directory = self.working_directory.path().to_owned();
target_machine target_machine
.write_to_file(module, FileType::Object, &working_directory.join("module.o")) .write_to_file(&module, FileType::Object, &working_directory.join("module.o"))
.expect("couldn't write module to file"); .expect("couldn't write module to file");
let filename_path = self.working_directory.path().join("module.elf"); let filename_path = self.working_directory.path().join("module.elf");
let filename = filename_path.to_str().unwrap(); let filename = filename_path.to_str().unwrap();
link_with_lld( link_with_lld(
filename.to_string(), filename.to_string(),
working_directory.join("module.o").to_string_lossy().to_string(), working_directory.join("module.o").to_string_lossy().to_string()
)?; )?;
Ok(PyBytes::new(py, &fs::read(filename).unwrap()).into()) Ok(PyBytes::new(py, &fs::read(filename).unwrap()).into())
@ -1061,7 +992,7 @@ impl Nac3 {
} else { } else {
let link_fn = |module: &Module| { let link_fn = |module: &Module| {
let object_mem = target_machine let object_mem = target_machine
.write_to_memory_buffer(module, FileType::Object) .write_to_memory_buffer(&module, FileType::Object)
.expect("couldn't write module to object file buffer"); .expect("couldn't write module to object file buffer");
if let Ok(dyn_lib) = Linker::ld(object_mem.as_slice()) { if let Ok(dyn_lib) = Linker::ld(object_mem.as_slice()) {
Ok(PyBytes::new(py, &dyn_lib).into()) Ok(PyBytes::new(py, &dyn_lib).into())

File diff suppressed because it is too large Load Diff

View File

@ -1,20 +1,10 @@
use inkwell::{ use inkwell::{values::BasicValueEnum, AddressSpace, AtomicOrdering};
values::{BasicValueEnum, CallSiteValue},
AddressSpace, AtomicOrdering,
};
use itertools::Either;
use nac3core::codegen::CodeGenContext; use nac3core::codegen::CodeGenContext;
/// Functions for manipulating the timeline.
pub trait TimeFns { pub trait TimeFns {
/// Emits LLVM IR for `now_mu`. fn emit_now_mu<'ctx, 'a>(&self, ctx: &mut CodeGenContext<'ctx, 'a>) -> BasicValueEnum<'ctx>;
fn emit_now_mu<'ctx>(&self, ctx: &mut CodeGenContext<'ctx, '_>) -> BasicValueEnum<'ctx>; fn emit_at_mu<'ctx, 'a>(&self, ctx: &mut CodeGenContext<'ctx, 'a>, t: BasicValueEnum<'ctx>);
fn emit_delay_mu<'ctx, 'a>(&self, ctx: &mut CodeGenContext<'ctx, 'a>, dt: BasicValueEnum<'ctx>);
/// Emits LLVM IR for `at_mu`.
fn emit_at_mu<'ctx>(&self, ctx: &mut CodeGenContext<'ctx, '_>, t: BasicValueEnum<'ctx>);
/// Emits LLVM IR for `delay_mu`.
fn emit_delay_mu<'ctx>(&self, ctx: &mut CodeGenContext<'ctx, '_>, dt: BasicValueEnum<'ctx>);
} }
pub struct NowPinningTimeFns64 {} pub struct NowPinningTimeFns64 {}
@ -22,143 +12,141 @@ pub struct NowPinningTimeFns64 {}
// For FPGA design reasons, on VexRiscv with 64-bit data bus, the "now" CSR is split into two 32-bit // For FPGA design reasons, on VexRiscv with 64-bit data bus, the "now" CSR is split into two 32-bit
// values that are each padded to 64-bits. // values that are each padded to 64-bits.
impl TimeFns for NowPinningTimeFns64 { impl TimeFns for NowPinningTimeFns64 {
fn emit_now_mu<'ctx>(&self, ctx: &mut CodeGenContext<'ctx, '_>) -> BasicValueEnum<'ctx> { fn emit_now_mu<'ctx, 'a>(&self, ctx: &mut CodeGenContext<'ctx, 'a>) -> BasicValueEnum<'ctx> {
let i64_type = ctx.ctx.i64_type(); let i64_type = ctx.ctx.i64_type();
let i32_type = ctx.ctx.i32_type(); let i32_type = ctx.ctx.i32_type();
let now = ctx let now = ctx
.module .module
.get_global("now") .get_global("now")
.unwrap_or_else(|| ctx.module.add_global(i64_type, None, "now")); .unwrap_or_else(|| ctx.module.add_global(i64_type, None, "now"));
let now_hiptr = ctx let now_hiptr =
.builder ctx.builder.build_bitcast(now, i32_type.ptr_type(AddressSpace::Generic), "now_hiptr");
.build_bitcast(now, i32_type.ptr_type(AddressSpace::default()), "now.hi.addr") if let BasicValueEnum::PointerValue(now_hiptr) = now_hiptr {
.map(BasicValueEnum::into_pointer_value) let now_loptr = unsafe {
.unwrap(); ctx.builder.build_gep(now_hiptr, &[i32_type.const_int(2, false)], "now_gep")
};
let now_loptr = unsafe { if let (BasicValueEnum::IntValue(now_hi), BasicValueEnum::IntValue(now_lo)) = (
ctx.builder.build_gep(now_hiptr, &[i32_type.const_int(2, false)], "now.lo.addr") ctx.builder.build_load(now_hiptr, "now_hi"),
ctx.builder.build_load(now_loptr, "now_lo"),
) {
let zext_hi = ctx.builder.build_int_z_extend(now_hi, i64_type, "now_zext_hi");
let shifted_hi = ctx.builder.build_left_shift(
zext_hi,
i64_type.const_int(32, false),
"now_shifted_zext_hi",
);
let zext_lo = ctx.builder.build_int_z_extend(now_lo, i64_type, "now_zext_lo");
ctx.builder.build_or(shifted_hi, zext_lo, "now_or").into()
} else {
unreachable!();
}
} else {
unreachable!();
} }
.unwrap();
let now_hi = ctx
.builder
.build_load(now_hiptr, "now.hi")
.map(BasicValueEnum::into_int_value)
.unwrap();
let now_lo = ctx
.builder
.build_load(now_loptr, "now.lo")
.map(BasicValueEnum::into_int_value)
.unwrap();
let zext_hi = ctx.builder.build_int_z_extend(now_hi, i64_type, "").unwrap();
let shifted_hi =
ctx.builder.build_left_shift(zext_hi, i64_type.const_int(32, false), "").unwrap();
let zext_lo = ctx.builder.build_int_z_extend(now_lo, i64_type, "").unwrap();
ctx.builder.build_or(shifted_hi, zext_lo, "now_mu").map(Into::into).unwrap()
} }
fn emit_at_mu<'ctx>(&self, ctx: &mut CodeGenContext<'ctx, '_>, t: BasicValueEnum<'ctx>) { fn emit_at_mu<'ctx, 'a>(&self, ctx: &mut CodeGenContext<'ctx, 'a>, t: BasicValueEnum<'ctx>) {
let i32_type = ctx.ctx.i32_type(); let i32_type = ctx.ctx.i32_type();
let i64_type = ctx.ctx.i64_type(); let i64_type = ctx.ctx.i64_type();
let i64_32 = i64_type.const_int(32, false); let i64_32 = i64_type.const_int(32, false);
let time = t.into_int_value(); if let BasicValueEnum::IntValue(time) = t {
let time_hi = ctx.builder.build_int_truncate(
let time_hi = ctx ctx.builder.build_right_shift(time, i64_32, false, "now_lshr"),
.builder
.build_int_truncate(
ctx.builder.build_right_shift(time, i64_32, false, "time.hi").unwrap(),
i32_type, i32_type,
"", "now_trunc",
) );
.unwrap(); let time_lo = ctx.builder.build_int_truncate(time, i32_type, "now_trunc");
let time_lo = ctx.builder.build_int_truncate(time, i32_type, "time.lo").unwrap(); let now = ctx
let now = ctx .module
.module .get_global("now")
.get_global("now") .unwrap_or_else(|| ctx.module.add_global(i64_type, None, "now"));
.unwrap_or_else(|| ctx.module.add_global(i64_type, None, "now")); let now_hiptr = ctx.builder.build_bitcast(
let now_hiptr = ctx now,
.builder i32_type.ptr_type(AddressSpace::Generic),
.build_bitcast(now, i32_type.ptr_type(AddressSpace::default()), "now.hi.addr") "now_bitcast",
.map(BasicValueEnum::into_pointer_value) );
.unwrap(); if let BasicValueEnum::PointerValue(now_hiptr) = now_hiptr {
let now_loptr = unsafe {
let now_loptr = unsafe { ctx.builder.build_gep(now_hiptr, &[i32_type.const_int(2, false)], "now_gep")
ctx.builder.build_gep(now_hiptr, &[i32_type.const_int(2, false)], "now.lo.addr") };
ctx.builder
.build_store(now_hiptr, time_hi)
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
.unwrap();
ctx.builder
.build_store(now_loptr, time_lo)
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
.unwrap();
} else {
unreachable!();
}
} else {
unreachable!();
} }
.unwrap();
ctx.builder
.build_store(now_hiptr, time_hi)
.unwrap()
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
.unwrap();
ctx.builder
.build_store(now_loptr, time_lo)
.unwrap()
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
.unwrap();
} }
fn emit_delay_mu<'ctx>(&self, ctx: &mut CodeGenContext<'ctx, '_>, dt: BasicValueEnum<'ctx>) { fn emit_delay_mu<'ctx, 'a>(
&self,
ctx: &mut CodeGenContext<'ctx, 'a>,
dt: BasicValueEnum<'ctx>,
) {
let i64_type = ctx.ctx.i64_type(); let i64_type = ctx.ctx.i64_type();
let i32_type = ctx.ctx.i32_type(); let i32_type = ctx.ctx.i32_type();
let now = ctx let now = ctx
.module .module
.get_global("now") .get_global("now")
.unwrap_or_else(|| ctx.module.add_global(i64_type, None, "now")); .unwrap_or_else(|| ctx.module.add_global(i64_type, None, "now"));
let now_hiptr = ctx let now_hiptr =
.builder ctx.builder.build_bitcast(now, i32_type.ptr_type(AddressSpace::Generic), "now_hiptr");
.build_bitcast(now, i32_type.ptr_type(AddressSpace::default()), "now.hi.addr") if let BasicValueEnum::PointerValue(now_hiptr) = now_hiptr {
.map(BasicValueEnum::into_pointer_value) let now_loptr = unsafe {
.unwrap(); ctx.builder.build_gep(now_hiptr, &[i32_type.const_int(2, false)], "now_loptr")
};
if let (
BasicValueEnum::IntValue(now_hi),
BasicValueEnum::IntValue(now_lo),
BasicValueEnum::IntValue(dt),
) = (
ctx.builder.build_load(now_hiptr, "now_hi"),
ctx.builder.build_load(now_loptr, "now_lo"),
dt,
) {
let zext_hi = ctx.builder.build_int_z_extend(now_hi, i64_type, "now_zext_hi");
let shifted_hi = ctx.builder.build_left_shift(
zext_hi,
i64_type.const_int(32, false),
"now_shifted_zext_hi",
);
let zext_lo = ctx.builder.build_int_z_extend(now_lo, i64_type, "now_zext_lo");
let now_val = ctx.builder.build_or(shifted_hi, zext_lo, "now_or");
let now_loptr = unsafe { let time = ctx.builder.build_int_add(now_val, dt, "now_add");
ctx.builder.build_gep(now_hiptr, &[i32_type.const_int(2, false)], "now.lo.addr") let time_hi = ctx.builder.build_int_truncate(
} ctx.builder.build_right_shift(
.unwrap(); time,
i64_type.const_int(32, false),
false,
"now_lshr",
),
i32_type,
"now_trunc",
);
let time_lo = ctx.builder.build_int_truncate(time, i32_type, "now_trunc");
let now_hi = ctx
.builder
.build_load(now_hiptr, "now.hi")
.map(BasicValueEnum::into_int_value)
.unwrap();
let now_lo = ctx
.builder
.build_load(now_loptr, "now.lo")
.map(BasicValueEnum::into_int_value)
.unwrap();
let dt = dt.into_int_value();
let zext_hi = ctx.builder.build_int_z_extend(now_hi, i64_type, "").unwrap();
let shifted_hi =
ctx.builder.build_left_shift(zext_hi, i64_type.const_int(32, false), "").unwrap();
let zext_lo = ctx.builder.build_int_z_extend(now_lo, i64_type, "").unwrap();
let now_val = ctx.builder.build_or(shifted_hi, zext_lo, "now").unwrap();
let time = ctx.builder.build_int_add(now_val, dt, "time").unwrap();
let time_hi = ctx
.builder
.build_int_truncate(
ctx.builder ctx.builder
.build_right_shift(time, i64_type.const_int(32, false), false, "") .build_store(now_hiptr, time_hi)
.unwrap(), .set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
i32_type, .unwrap();
"time.hi", ctx.builder
) .build_store(now_loptr, time_lo)
.unwrap(); .set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
let time_lo = ctx.builder.build_int_truncate(time, i32_type, "time.lo").unwrap(); .unwrap();
} else {
ctx.builder unreachable!();
.build_store(now_hiptr, time_hi) }
.unwrap() } else {
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent) unreachable!();
.unwrap(); };
ctx.builder
.build_store(now_loptr, time_lo)
.unwrap()
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
.unwrap();
} }
} }
@ -167,115 +155,110 @@ pub static NOW_PINNING_TIME_FNS_64: NowPinningTimeFns64 = NowPinningTimeFns64 {}
pub struct NowPinningTimeFns {} pub struct NowPinningTimeFns {}
impl TimeFns for NowPinningTimeFns { impl TimeFns for NowPinningTimeFns {
fn emit_now_mu<'ctx>(&self, ctx: &mut CodeGenContext<'ctx, '_>) -> BasicValueEnum<'ctx> { fn emit_now_mu<'ctx, 'a>(&self, ctx: &mut CodeGenContext<'ctx, 'a>) -> BasicValueEnum<'ctx> {
let i64_type = ctx.ctx.i64_type(); let i64_type = ctx.ctx.i64_type();
let now = ctx let now = ctx
.module .module
.get_global("now") .get_global("now")
.unwrap_or_else(|| ctx.module.add_global(i64_type, None, "now")); .unwrap_or_else(|| ctx.module.add_global(i64_type, None, "now"));
let now_raw = ctx let now_raw = ctx.builder.build_load(now.as_pointer_value(), "now");
.builder if let BasicValueEnum::IntValue(now_raw) = now_raw {
.build_load(now.as_pointer_value(), "now") let i64_32 = i64_type.const_int(32, false);
.map(BasicValueEnum::into_int_value) let now_lo = ctx.builder.build_left_shift(now_raw, i64_32, "now_shl");
.unwrap(); let now_hi = ctx.builder.build_right_shift(now_raw, i64_32, false, "now_lshr");
ctx.builder.build_or(now_lo, now_hi, "now_or").into()
let i64_32 = i64_type.const_int(32, false); } else {
let now_lo = ctx.builder.build_left_shift(now_raw, i64_32, "now.lo").unwrap(); unreachable!();
let now_hi = ctx.builder.build_right_shift(now_raw, i64_32, false, "now.hi").unwrap();
ctx.builder.build_or(now_lo, now_hi, "now_mu").map(Into::into).unwrap()
}
fn emit_at_mu<'ctx>(&self, ctx: &mut CodeGenContext<'ctx, '_>, t: BasicValueEnum<'ctx>) {
let i32_type = ctx.ctx.i32_type();
let i64_type = ctx.ctx.i64_type();
let i64_32 = i64_type.const_int(32, false);
let time = t.into_int_value();
let time_hi = ctx
.builder
.build_int_truncate(
ctx.builder.build_right_shift(time, i64_32, false, "").unwrap(),
i32_type,
"time.hi",
)
.unwrap();
let time_lo = ctx.builder.build_int_truncate(time, i32_type, "now_trunc").unwrap();
let now = ctx
.module
.get_global("now")
.unwrap_or_else(|| ctx.module.add_global(i64_type, None, "now"));
let now_hiptr = ctx
.builder
.build_bitcast(now, i32_type.ptr_type(AddressSpace::default()), "now.hi.addr")
.map(BasicValueEnum::into_pointer_value)
.unwrap();
let now_loptr = unsafe {
ctx.builder.build_gep(now_hiptr, &[i32_type.const_int(1, false)], "now.lo.addr")
} }
.unwrap();
ctx.builder
.build_store(now_hiptr, time_hi)
.unwrap()
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
.unwrap();
ctx.builder
.build_store(now_loptr, time_lo)
.unwrap()
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
.unwrap();
} }
fn emit_delay_mu<'ctx>(&self, ctx: &mut CodeGenContext<'ctx, '_>, dt: BasicValueEnum<'ctx>) { fn emit_at_mu<'ctx, 'a>(&self, ctx: &mut CodeGenContext<'ctx, 'a>, t: BasicValueEnum<'ctx>) {
let i32_type = ctx.ctx.i32_type(); let i32_type = ctx.ctx.i32_type();
let i64_type = ctx.ctx.i64_type(); let i64_type = ctx.ctx.i64_type();
let i64_32 = i64_type.const_int(32, false); let i64_32 = i64_type.const_int(32, false);
let now = ctx if let BasicValueEnum::IntValue(time) = t {
.module let time_hi = ctx.builder.build_int_truncate(
.get_global("now") ctx.builder.build_right_shift(time, i64_32, false, "now_lshr"),
.unwrap_or_else(|| ctx.module.add_global(i64_type, None, "now"));
let now_raw = ctx
.builder
.build_load(now.as_pointer_value(), "")
.map(BasicValueEnum::into_int_value)
.unwrap();
let dt = dt.into_int_value();
let now_lo = ctx.builder.build_left_shift(now_raw, i64_32, "now.lo").unwrap();
let now_hi = ctx.builder.build_right_shift(now_raw, i64_32, false, "now.hi").unwrap();
let now_val = ctx.builder.build_or(now_lo, now_hi, "now_val").unwrap();
let time = ctx.builder.build_int_add(now_val, dt, "time").unwrap();
let time_hi = ctx
.builder
.build_int_truncate(
ctx.builder.build_right_shift(time, i64_32, false, "time.hi").unwrap(),
i32_type, i32_type,
"now_trunc", "now_trunc",
) );
.unwrap(); let time_lo = ctx.builder.build_int_truncate(time, i32_type, "now_trunc");
let time_lo = ctx.builder.build_int_truncate(time, i32_type, "time.lo").unwrap(); let now = ctx
let now_hiptr = ctx .module
.builder .get_global("now")
.build_bitcast(now, i32_type.ptr_type(AddressSpace::default()), "now.hi.addr") .unwrap_or_else(|| ctx.module.add_global(i64_type, None, "now"));
.map(BasicValueEnum::into_pointer_value) let now_hiptr = ctx.builder.build_bitcast(
.unwrap(); now,
i32_type.ptr_type(AddressSpace::Generic),
let now_loptr = unsafe { "now_bitcast",
ctx.builder.build_gep(now_hiptr, &[i32_type.const_int(1, false)], "now.lo.addr") );
if let BasicValueEnum::PointerValue(now_hiptr) = now_hiptr {
let now_loptr = unsafe {
ctx.builder.build_gep(now_hiptr, &[i32_type.const_int(1, false)], "now_gep")
};
ctx.builder
.build_store(now_hiptr, time_hi)
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
.unwrap();
ctx.builder
.build_store(now_loptr, time_lo)
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
.unwrap();
} else {
unreachable!();
}
} else {
unreachable!();
}
}
fn emit_delay_mu<'ctx, 'a>(
&self,
ctx: &mut CodeGenContext<'ctx, 'a>,
dt: BasicValueEnum<'ctx>,
) {
let i32_type = ctx.ctx.i32_type();
let i64_type = ctx.ctx.i64_type();
let i64_32 = i64_type.const_int(32, false);
let now = ctx
.module
.get_global("now")
.unwrap_or_else(|| ctx.module.add_global(i64_type, None, "now"));
let now_raw = ctx.builder.build_load(now.as_pointer_value(), "now");
if let (BasicValueEnum::IntValue(now_raw), BasicValueEnum::IntValue(dt)) = (now_raw, dt) {
let now_lo = ctx.builder.build_left_shift(now_raw, i64_32, "now_shl");
let now_hi = ctx.builder.build_right_shift(now_raw, i64_32, false, "now_lshr");
let now_val = ctx.builder.build_or(now_lo, now_hi, "now_or");
let time = ctx.builder.build_int_add(now_val, dt, "now_add");
let time_hi = ctx.builder.build_int_truncate(
ctx.builder.build_right_shift(time, i64_32, false, "now_lshr"),
i32_type,
"now_trunc",
);
let time_lo = ctx.builder.build_int_truncate(time, i32_type, "now_trunc");
let now_hiptr = ctx.builder.build_bitcast(
now,
i32_type.ptr_type(AddressSpace::Generic),
"now_bitcast",
);
if let BasicValueEnum::PointerValue(now_hiptr) = now_hiptr {
let now_loptr = unsafe {
ctx.builder.build_gep(now_hiptr, &[i32_type.const_int(1, false)], "now_gep")
};
ctx.builder
.build_store(now_hiptr, time_hi)
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
.unwrap();
ctx.builder
.build_store(now_loptr, time_lo)
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
.unwrap();
} else {
unreachable!();
}
} else {
unreachable!();
} }
.unwrap();
ctx.builder
.build_store(now_hiptr, time_hi)
.unwrap()
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
.unwrap();
ctx.builder
.build_store(now_loptr, time_lo)
.unwrap()
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
.unwrap();
} }
} }
@ -284,18 +267,14 @@ pub static NOW_PINNING_TIME_FNS: NowPinningTimeFns = NowPinningTimeFns {};
pub struct ExternTimeFns {} pub struct ExternTimeFns {}
impl TimeFns for ExternTimeFns { impl TimeFns for ExternTimeFns {
fn emit_now_mu<'ctx>(&self, ctx: &mut CodeGenContext<'ctx, '_>) -> BasicValueEnum<'ctx> { fn emit_now_mu<'ctx, 'a>(&self, ctx: &mut CodeGenContext<'ctx, 'a>) -> BasicValueEnum<'ctx> {
let now_mu = ctx.module.get_function("now_mu").unwrap_or_else(|| { let now_mu = ctx.module.get_function("now_mu").unwrap_or_else(|| {
ctx.module.add_function("now_mu", ctx.ctx.i64_type().fn_type(&[], false), None) ctx.module.add_function("now_mu", ctx.ctx.i64_type().fn_type(&[], false), None)
}); });
ctx.builder ctx.builder.build_call(now_mu, &[], "now_mu").try_as_basic_value().left().unwrap()
.build_call(now_mu, &[], "now_mu")
.map(CallSiteValue::try_as_basic_value)
.map(Either::unwrap_left)
.unwrap()
} }
fn emit_at_mu<'ctx>(&self, ctx: &mut CodeGenContext<'ctx, '_>, t: BasicValueEnum<'ctx>) { fn emit_at_mu<'ctx, 'a>(&self, ctx: &mut CodeGenContext<'ctx, 'a>, t: BasicValueEnum<'ctx>) {
let at_mu = ctx.module.get_function("at_mu").unwrap_or_else(|| { let at_mu = ctx.module.get_function("at_mu").unwrap_or_else(|| {
ctx.module.add_function( ctx.module.add_function(
"at_mu", "at_mu",
@ -303,10 +282,14 @@ impl TimeFns for ExternTimeFns {
None, None,
) )
}); });
ctx.builder.build_call(at_mu, &[t.into()], "at_mu").unwrap(); ctx.builder.build_call(at_mu, &[t.into()], "at_mu");
} }
fn emit_delay_mu<'ctx>(&self, ctx: &mut CodeGenContext<'ctx, '_>, dt: BasicValueEnum<'ctx>) { fn emit_delay_mu<'ctx, 'a>(
&self,
ctx: &mut CodeGenContext<'ctx, 'a>,
dt: BasicValueEnum<'ctx>,
) {
let delay_mu = ctx.module.get_function("delay_mu").unwrap_or_else(|| { let delay_mu = ctx.module.get_function("delay_mu").unwrap_or_else(|| {
ctx.module.add_function( ctx.module.add_function(
"delay_mu", "delay_mu",
@ -314,7 +297,7 @@ impl TimeFns for ExternTimeFns {
None, None,
) )
}); });
ctx.builder.build_call(delay_mu, &[dt.into()], "delay_mu").unwrap(); ctx.builder.build_call(delay_mu, &[dt.into()], "delay_mu");
} }
} }

View File

@ -2,7 +2,7 @@
name = "nac3ast" name = "nac3ast"
version = "0.1.0" version = "0.1.0"
authors = ["RustPython Team", "M-Labs"] authors = ["RustPython Team", "M-Labs"]
edition = "2021" edition = "2018"
[features] [features]
default = ["constant-optimization", "fold"] default = ["constant-optimization", "fold"]
@ -10,7 +10,7 @@ constant-optimization = ["fold"]
fold = [] fold = []
[dependencies] [dependencies]
lazy_static = "1.5" lazy_static = "1.4"
parking_lot = "0.12" parking_lot = "0.12"
string-interner = "0.17" string-interner = "0.14"
fxhash = "0.2" fxhash = "0.2"

File diff suppressed because it is too large Load Diff

View File

@ -28,12 +28,12 @@ impl From<bool> for Constant {
} }
impl From<i32> for Constant { impl From<i32> for Constant {
fn from(i: i32) -> Constant { fn from(i: i32) -> Constant {
Self::Int(i128::from(i)) Self::Int(i as i128)
} }
} }
impl From<i64> for Constant { impl From<i64> for Constant {
fn from(i: i64) -> Constant { fn from(i: i64) -> Constant {
Self::Int(i128::from(i)) Self::Int(i as i128)
} }
} }
@ -50,7 +50,6 @@ pub enum ConversionFlag {
} }
impl ConversionFlag { impl ConversionFlag {
#[must_use]
pub fn try_from_byte(b: u8) -> Option<Self> { pub fn try_from_byte(b: u8) -> Option<Self> {
match b { match b {
b's' => Some(Self::Str), b's' => Some(Self::Str),
@ -70,7 +69,6 @@ pub struct ConstantOptimizer {
#[cfg(feature = "constant-optimization")] #[cfg(feature = "constant-optimization")]
impl ConstantOptimizer { impl ConstantOptimizer {
#[inline] #[inline]
#[must_use]
pub fn new() -> Self { pub fn new() -> Self {
Self { _priv: () } Self { _priv: () }
} }
@ -87,22 +85,33 @@ impl<U> crate::fold::Fold<U> for ConstantOptimizer {
fn fold_expr(&mut self, node: crate::Expr<U>) -> Result<crate::Expr<U>, Self::Error> { fn fold_expr(&mut self, node: crate::Expr<U>) -> Result<crate::Expr<U>, Self::Error> {
match node.node { match node.node {
crate::ExprKind::Tuple { elts, ctx } => { crate::ExprKind::Tuple { elts, ctx } => {
let elts = let elts = elts
elts.into_iter().map(|x| self.fold_expr(x)).collect::<Result<Vec<_>, _>>()?; .into_iter()
let expr = .map(|x| self.fold_expr(x))
if elts.iter().all(|e| matches!(e.node, crate::ExprKind::Constant { .. })) { .collect::<Result<Vec<_>, _>>()?;
let tuple = elts let expr = if elts
.into_iter() .iter()
.map(|e| match e.node { .all(|e| matches!(e.node, crate::ExprKind::Constant { .. }))
crate::ExprKind::Constant { value, .. } => value, {
_ => unreachable!(), let tuple = elts
}) .into_iter()
.collect(); .map(|e| match e.node {
crate::ExprKind::Constant { value: Constant::Tuple(tuple), kind: None } crate::ExprKind::Constant { value, .. } => value,
} else { _ => unreachable!(),
crate::ExprKind::Tuple { elts, ctx } })
}; .collect();
Ok(crate::Expr { node: expr, custom: node.custom, location: node.location }) crate::ExprKind::Constant {
value: Constant::Tuple(tuple),
kind: None,
}
} else {
crate::ExprKind::Tuple { elts, ctx }
};
Ok(crate::Expr {
node: expr,
custom: node.custom,
location: node.location,
})
} }
_ => crate::fold::fold_expr(self, node), _ => crate::fold::fold_expr(self, node),
} }
@ -118,7 +127,7 @@ mod tests {
use crate::fold::Fold; use crate::fold::Fold;
use crate::*; use crate::*;
let location = Location::new(0, 0, FileName::default()); let location = Location::new(0, 0, Default::default());
let custom = (); let custom = ();
let ast = Located { let ast = Located {
location, location,
@ -129,12 +138,18 @@ mod tests {
Located { Located {
location, location,
custom, custom,
node: ExprKind::Constant { value: 1.into(), kind: None }, node: ExprKind::Constant {
value: 1.into(),
kind: None,
},
}, },
Located { Located {
location, location,
custom, custom,
node: ExprKind::Constant { value: 2.into(), kind: None }, node: ExprKind::Constant {
value: 2.into(),
kind: None,
},
}, },
Located { Located {
location, location,
@ -145,17 +160,26 @@ mod tests {
Located { Located {
location, location,
custom, custom,
node: ExprKind::Constant { value: 3.into(), kind: None }, node: ExprKind::Constant {
value: 3.into(),
kind: None,
},
}, },
Located { Located {
location, location,
custom, custom,
node: ExprKind::Constant { value: 4.into(), kind: None }, node: ExprKind::Constant {
value: 4.into(),
kind: None,
},
}, },
Located { Located {
location, location,
custom, custom,
node: ExprKind::Constant { value: 5.into(), kind: None }, node: ExprKind::Constant {
value: 5.into(),
kind: None,
},
}, },
], ],
}, },
@ -163,7 +187,9 @@ mod tests {
], ],
}, },
}; };
let new_ast = ConstantOptimizer::new().fold_expr(ast).unwrap_or_else(|e| match e {}); let new_ast = ConstantOptimizer::new()
.fold_expr(ast)
.unwrap_or_else(|e| match e {});
assert_eq!( assert_eq!(
new_ast, new_ast,
Located { Located {
@ -173,7 +199,11 @@ mod tests {
value: Constant::Tuple(vec![ value: Constant::Tuple(vec![
1.into(), 1.into(),
2.into(), 2.into(),
Constant::Tuple(vec![3.into(), 4.into(), 5.into(),]) Constant::Tuple(vec![
3.into(),
4.into(),
5.into(),
])
]), ]),
kind: None kind: None
}, },

View File

@ -64,4 +64,11 @@ macro_rules! simple_fold {
}; };
} }
simple_fold!(usize, String, bool, StrRef, constant::Constant, constant::ConversionFlag); simple_fold!(
usize,
String,
bool,
StrRef,
constant::Constant,
constant::ConversionFlag
);

View File

@ -2,7 +2,6 @@ use crate::{Constant, ExprKind};
impl<U> ExprKind<U> { impl<U> ExprKind<U> {
/// Returns a short name for the node suitable for use in error messages. /// Returns a short name for the node suitable for use in error messages.
#[must_use]
pub fn name(&self) -> &'static str { pub fn name(&self) -> &'static str {
match self { match self {
ExprKind::BoolOp { .. } | ExprKind::BinOp { .. } | ExprKind::UnaryOp { .. } => { ExprKind::BoolOp { .. } | ExprKind::BinOp { .. } | ExprKind::UnaryOp { .. } => {
@ -35,7 +34,10 @@ impl<U> ExprKind<U> {
ExprKind::Starred { .. } => "starred", ExprKind::Starred { .. } => "starred",
ExprKind::Slice { .. } => "slice", ExprKind::Slice { .. } => "slice",
ExprKind::JoinedStr { values } => { ExprKind::JoinedStr { values } => {
if values.iter().any(|e| matches!(e.node, ExprKind::JoinedStr { .. })) { if values
.iter()
.any(|e| matches!(e.node, ExprKind::JoinedStr { .. }))
{
"f-string expression" "f-string expression"
} else { } else {
"literal" "literal"

View File

@ -1,19 +1,3 @@
#![deny(
future_incompatible,
let_underscore,
nonstandard_style,
rust_2024_compatibility,
clippy::all
)]
#![warn(clippy::pedantic)]
#![allow(
clippy::missing_errors_doc,
clippy::missing_panics_doc,
clippy::module_name_repetitions,
clippy::too_many_lines,
clippy::wildcard_imports
)]
#[macro_use] #[macro_use]
extern crate lazy_static; extern crate lazy_static;
@ -25,6 +9,6 @@ mod impls;
mod location; mod location;
pub use ast_gen::*; pub use ast_gen::*;
pub use location::{FileName, Location}; pub use location::{Location, FileName};
pub type Suite<U = ()> = Vec<Stmt<U>>; pub type Suite<U = ()> = Vec<Stmt<U>>;

View File

@ -1,9 +1,8 @@
//! Datatypes to support source location information. //! Datatypes to support source location information.
use crate::ast_gen::StrRef; use crate::ast_gen::StrRef;
use std::cmp::Ordering;
use std::fmt; use std::fmt;
#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub struct FileName(pub StrRef); pub struct FileName(pub StrRef);
impl Default for FileName { impl Default for FileName {
fn default() -> Self { fn default() -> Self {
@ -18,38 +17,16 @@ impl From<String> for FileName {
} }
/// A location somewhere in the sourcecode. /// A location somewhere in the sourcecode.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Location { pub struct Location {
pub row: usize, pub row: usize,
pub column: usize, pub column: usize,
pub file: FileName, pub file: FileName
} }
impl fmt::Display for Location { impl fmt::Display for Location {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}:{}:{}", self.file.0, self.row, self.column) write!(f, "{}: line {} column {}", self.file.0, self.row, self.column)
}
}
impl Ord for Location {
fn cmp(&self, other: &Self) -> Ordering {
let file_cmp = self.file.0.to_string().cmp(&other.file.0.to_string());
if file_cmp != Ordering::Equal {
return file_cmp;
}
let row_cmp = self.row.cmp(&other.row);
if row_cmp != Ordering::Equal {
return row_cmp;
}
self.column.cmp(&other.column)
}
}
impl PartialOrd for Location {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
} }
} }
@ -76,22 +53,23 @@ impl Location {
) )
} }
} }
Visualize { loc: *self, line, desc } Visualize {
loc: *self,
line,
desc,
}
} }
} }
impl Location { impl Location {
#[must_use]
pub fn new(row: usize, column: usize, file: FileName) -> Self { pub fn new(row: usize, column: usize, file: FileName) -> Self {
Location { row, column, file } Location { row, column, file }
} }
#[must_use]
pub fn row(&self) -> usize { pub fn row(&self) -> usize {
self.row self.row
} }
#[must_use]
pub fn column(&self) -> usize { pub fn column(&self) -> usize {
self.column self.column
} }

View File

@ -1,31 +1,26 @@
[features]
test = []
[package] [package]
name = "nac3core" name = "nac3core"
version = "0.1.0" version = "0.1.0"
authors = ["M-Labs"] authors = ["M-Labs"]
edition = "2021" edition = "2018"
[dependencies] [dependencies]
itertools = "0.13" itertools = "0.10"
crossbeam = "0.8" crossbeam = "0.8"
indexmap = "2.2"
parking_lot = "0.12" parking_lot = "0.12"
rayon = "1.8" rayon = "1.5"
nac3parser = { path = "../nac3parser" } nac3parser = { path = "../nac3parser" }
strum = "0.26.2" lazy_static = "1.4"
strum_macros = "0.26.4"
[dependencies.inkwell] [dependencies.inkwell]
version = "0.4" git = "https://github.com/TheDan64/inkwell.git"
default-features = false default-features = false
features = ["llvm14-0", "target-x86", "target-arm", "target-riscv", "no-libffi-linking"] features = ["llvm14-0", "target-x86", "target-arm", "target-riscv", "no-libffi-linking"]
[dev-dependencies] [dev-dependencies]
test-case = "1.2.0" test-case = "1.2.0"
indoc = "2.0" indoc = "1.0"
insta = "=1.11.0" insta = "=1.11.0"
[build-dependencies] [build-dependencies]
regex = "1.10" regex = "1"

View File

@ -7,41 +7,30 @@ use std::{
process::{Command, Stdio}, process::{Command, Stdio},
}; };
fn compile_irrt(irrt_dir: &Path, out_dir: &Path) { fn main() {
let irrt_cpp_path = irrt_dir.join("irrt.cpp"); const FILE: &str = "src/codegen/irrt/irrt.c";
println!("cargo:rerun-if-changed={}", FILE);
let out_dir = env::var("OUT_DIR").unwrap();
let out_path = Path::new(&out_dir);
/* /*
* HACK: Sadly, clang doesn't let us emit generic LLVM bitcode. * HACK: Sadly, clang doesn't let us emit generic LLVM bitcode.
* Compiling for WASM32 and filtering the output with regex is the closest we can get. * Compiling for WASM32 and filtering the output with regex is the closest we can get.
*/ */
let flags: &[&str] = &[
const FLAG: &[&str] = &[
"--target=wasm32", "--target=wasm32",
irrt_cpp_path.to_str().unwrap(), FILE,
"-x", "-O3",
"c++",
"-fno-discard-value-names",
"-fno-exceptions",
"-fno-rtti",
match env::var("PROFILE").as_deref() {
Ok("debug") => "-O0",
Ok("release") => "-O3",
flavor => panic!("Unknown or missing build flavor {flavor:?}"),
},
"-emit-llvm", "-emit-llvm",
"-S", "-S",
"-Wall", "-Wall",
"-Wextra", "-Wextra",
"-Werror=return-type",
"-I",
irrt_dir.to_str().unwrap(),
"-o", "-o",
"-", "-",
]; ];
let output = Command::new("clang")
println!("cargo:rerun-if-changed={}", out_dir.to_str().unwrap()); .args(FLAG)
let output = Command::new("clang-irrt")
.args(flags)
.output() .output()
.map(|o| { .map(|o| {
assert!(o.status.success(), "{}", std::str::from_utf8(&o.stderr).unwrap()); assert!(o.status.success(), "{}", std::str::from_utf8(&o.stderr).unwrap());
@ -53,13 +42,9 @@ fn compile_irrt(irrt_dir: &Path, out_dir: &Path) {
let output = std::str::from_utf8(&output.stdout).unwrap().replace("\r\n", "\n"); let output = std::str::from_utf8(&output.stdout).unwrap().replace("\r\n", "\n");
let mut filtered_output = String::with_capacity(output.len()); let mut filtered_output = String::with_capacity(output.len());
// (?ms:^define.*?\}$) to capture `define` blocks let regex_filter = regex::Regex::new(r"(?ms:^define.*?\}$)|(?m:^declare.*?$)").unwrap();
// (?m:^declare.*?$) to capture `declare` blocks
// (?m:^%.+?=\s*type\s*\{.+?\}$) to capture `type` declarations
let regex_filter =
Regex::new(r"(?ms:^define.*?\}$)|(?m:^declare.*?$)|(?m:^%.+?=\s*type\s*\{.+?\}$)").unwrap();
for f in regex_filter.captures_iter(&output) { for f in regex_filter.captures_iter(&output) {
assert_eq!(f.len(), 1); assert!(f.len() == 1);
filtered_output.push_str(&f[0]); filtered_output.push_str(&f[0]);
filtered_output.push('\n'); filtered_output.push('\n');
} }
@ -70,65 +55,18 @@ fn compile_irrt(irrt_dir: &Path, out_dir: &Path) {
println!("cargo:rerun-if-env-changed=DEBUG_DUMP_IRRT"); println!("cargo:rerun-if-env-changed=DEBUG_DUMP_IRRT");
if env::var("DEBUG_DUMP_IRRT").is_ok() { if env::var("DEBUG_DUMP_IRRT").is_ok() {
let mut file = File::create(out_dir.join("irrt.ll")).unwrap(); let mut file = File::create(out_path.join("irrt.ll")).unwrap();
file.write_all(output.as_bytes()).unwrap(); file.write_all(output.as_bytes()).unwrap();
let mut file = File::create(out_dir.join("irrt-filtered.ll")).unwrap(); let mut file = File::create(out_path.join("irrt-filtered.ll")).unwrap();
file.write_all(filtered_output.as_bytes()).unwrap(); file.write_all(filtered_output.as_bytes()).unwrap();
} }
let mut llvm_as = Command::new("llvm-as-irrt") let mut llvm_as = Command::new("llvm-as")
.stdin(Stdio::piped()) .stdin(Stdio::piped())
.arg("-o") .arg("-o")
.arg(out_dir.join("irrt.bc")) .arg(out_path.join("irrt.bc"))
.spawn() .spawn()
.unwrap(); .unwrap();
llvm_as.stdin.as_mut().unwrap().write_all(filtered_output.as_bytes()).unwrap(); llvm_as.stdin.as_mut().unwrap().write_all(filtered_output.as_bytes()).unwrap();
assert!(llvm_as.wait().unwrap().success()); assert!(llvm_as.wait().unwrap().success())
}
fn compile_irrt_test(irrt_dir: &Path, out_dir: &Path) {
let irrt_test_cpp_path = irrt_dir.join("irrt_test.cpp");
let exe_path = out_dir.join("irrt_test.out");
let flags: &[&str] = &[
irrt_test_cpp_path.to_str().unwrap(),
"-x",
"c++",
"-I",
irrt_dir.to_str().unwrap(),
"-g",
"-fno-discard-value-names",
"-O0",
"-Wall",
"-Wextra",
"-Werror=return-type",
"-lm", // for `tgamma()`, `lgamma()`
"-o",
exe_path.to_str().unwrap(),
];
Command::new("clang-irrt-test")
.args(flags)
.output()
.map(|o| {
assert!(o.status.success(), "{}", std::str::from_utf8(&o.stderr).unwrap());
o
})
.unwrap();
println!("cargo:rerun-if-changed={}", out_dir.to_str().unwrap());
}
fn main() {
let out_dir = env::var("OUT_DIR").unwrap();
let out_dir = Path::new(&out_dir);
let irrt_dir = Path::new("./irrt");
compile_irrt(irrt_dir, out_dir);
// https://github.com/rust-lang/cargo/issues/2549
// `cargo test -F test` to also build `irrt_test.cpp
if cfg!(feature = "test") {
compile_irrt_test(irrt_dir, out_dir);
}
} }

View File

@ -1,5 +0,0 @@
#include "irrt_everything.hpp"
/*
This file will be read by `clang-irrt` to conveniently produce LLVM IR for `nac3core/codegen`.
*/

View File

@ -1,437 +0,0 @@
#ifndef IRRT_DONT_TYPEDEF_INTS
typedef _BitInt(8) int8_t;
typedef unsigned _BitInt(8) uint8_t;
typedef _BitInt(32) int32_t;
typedef unsigned _BitInt(32) uint32_t;
typedef _BitInt(64) int64_t;
typedef unsigned _BitInt(64) uint64_t;
#endif
// NDArray indices are always `uint32_t`.
typedef uint32_t NDIndex;
// The type of an index or a value describing the length of a range/slice is
// always `int32_t`.
typedef int32_t SliceIndex;
template <typename T>
static T max(T a, T b) {
return a > b ? a : b;
}
template <typename T>
static T min(T a, T b) {
return a > b ? b : a;
}
// adapted from GNU Scientific Library: https://git.savannah.gnu.org/cgit/gsl.git/tree/sys/pow_int.c
// need to make sure `exp >= 0` before calling this function
template <typename T>
static T __nac3_int_exp_impl(T base, T exp) {
T res = 1;
/* repeated squaring method */
do {
if (exp & 1) {
res *= base; /* for n odd */
}
exp >>= 1;
base *= base;
} while (exp);
return res;
}
template <typename SizeT>
static SizeT __nac3_ndarray_calc_size_impl(
const SizeT *list_data,
SizeT list_len,
SizeT begin_idx,
SizeT end_idx
) {
__builtin_assume(end_idx <= list_len);
SizeT num_elems = 1;
for (SizeT i = begin_idx; i < end_idx; ++i) {
SizeT val = list_data[i];
__builtin_assume(val > 0);
num_elems *= val;
}
return num_elems;
}
template <typename SizeT>
static void __nac3_ndarray_calc_nd_indices_impl(
SizeT index,
const SizeT *dims,
SizeT num_dims,
NDIndex *idxs
) {
SizeT stride = 1;
for (SizeT dim = 0; dim < num_dims; dim++) {
SizeT i = num_dims - dim - 1;
__builtin_assume(dims[i] > 0);
idxs[i] = (index / stride) % dims[i];
stride *= dims[i];
}
}
template <typename SizeT>
static SizeT __nac3_ndarray_flatten_index_impl(
const SizeT *dims,
SizeT num_dims,
const NDIndex *indices,
SizeT num_indices
) {
SizeT idx = 0;
SizeT stride = 1;
for (SizeT i = 0; i < num_dims; ++i) {
SizeT ri = num_dims - i - 1;
if (ri < num_indices) {
idx += stride * indices[ri];
}
__builtin_assume(dims[i] > 0);
stride *= dims[ri];
}
return idx;
}
template <typename SizeT>
static void __nac3_ndarray_calc_broadcast_impl(
const SizeT *lhs_dims,
SizeT lhs_ndims,
const SizeT *rhs_dims,
SizeT rhs_ndims,
SizeT *out_dims
) {
SizeT max_ndims = lhs_ndims > rhs_ndims ? lhs_ndims : rhs_ndims;
for (SizeT i = 0; i < max_ndims; ++i) {
const SizeT *lhs_dim_sz = i < lhs_ndims ? &lhs_dims[lhs_ndims - i - 1] : nullptr;
const SizeT *rhs_dim_sz = i < rhs_ndims ? &rhs_dims[rhs_ndims - i - 1] : nullptr;
SizeT *out_dim = &out_dims[max_ndims - i - 1];
if (lhs_dim_sz == nullptr) {
*out_dim = *rhs_dim_sz;
} else if (rhs_dim_sz == nullptr) {
*out_dim = *lhs_dim_sz;
} else if (*lhs_dim_sz == 1) {
*out_dim = *rhs_dim_sz;
} else if (*rhs_dim_sz == 1) {
*out_dim = *lhs_dim_sz;
} else if (*lhs_dim_sz == *rhs_dim_sz) {
*out_dim = *lhs_dim_sz;
} else {
__builtin_unreachable();
}
}
}
template <typename SizeT>
static void __nac3_ndarray_calc_broadcast_idx_impl(
const SizeT *src_dims,
SizeT src_ndims,
const NDIndex *in_idx,
NDIndex *out_idx
) {
for (SizeT i = 0; i < src_ndims; ++i) {
SizeT src_i = src_ndims - i - 1;
out_idx[src_i] = src_dims[src_i] == 1 ? 0 : in_idx[src_i];
}
}
template<typename SizeT>
static void __nac3_ndarray_strides_from_shape_impl(
SizeT ndims,
SizeT *shape,
SizeT *dst_strides
) {
SizeT stride_product = 1;
for (SizeT i = 0; i < ndims; i++) {
int dim_i = ndims - i - 1;
dst_strides[dim_i] = stride_product;
stride_product *= shape[dim_i];
}
}
extern "C" {
#define DEF_nac3_int_exp_(T) \
T __nac3_int_exp_##T(T base, T exp) {\
return __nac3_int_exp_impl(base, exp);\
}
DEF_nac3_int_exp_(int32_t)
DEF_nac3_int_exp_(int64_t)
DEF_nac3_int_exp_(uint32_t)
DEF_nac3_int_exp_(uint64_t)
SliceIndex __nac3_slice_index_bound(SliceIndex i, const SliceIndex len) {
if (i < 0) {
i = len + i;
}
if (i < 0) {
return 0;
} else if (i > len) {
return len;
}
return i;
}
SliceIndex __nac3_range_slice_len(
const SliceIndex start,
const SliceIndex end,
const SliceIndex step
) {
SliceIndex diff = end - start;
if (diff > 0 && step > 0) {
return ((diff - 1) / step) + 1;
} else if (diff < 0 && step < 0) {
return ((diff + 1) / step) + 1;
} else {
return 0;
}
}
// Handle list assignment and dropping part of the list when
// both dest_step and src_step are +1.
// - All the index must *not* be out-of-bound or negative,
// - The end index is *inclusive*,
// - The length of src and dest slice size should already
// be checked: if dest.step == 1 then len(src) <= len(dest) else len(src) == len(dest)
SliceIndex __nac3_list_slice_assign_var_size(
SliceIndex dest_start,
SliceIndex dest_end,
SliceIndex dest_step,
uint8_t *dest_arr,
SliceIndex dest_arr_len,
SliceIndex src_start,
SliceIndex src_end,
SliceIndex src_step,
uint8_t *src_arr,
SliceIndex src_arr_len,
const SliceIndex size
) {
/* if dest_arr_len == 0, do nothing since we do not support extending list */
if (dest_arr_len == 0) return dest_arr_len;
/* if both step is 1, memmove directly, handle the dropping of the list, and shrink size */
if (src_step == dest_step && dest_step == 1) {
const SliceIndex src_len = (src_end >= src_start) ? (src_end - src_start + 1) : 0;
const SliceIndex dest_len = (dest_end >= dest_start) ? (dest_end - dest_start + 1) : 0;
if (src_len > 0) {
__builtin_memmove(
dest_arr + dest_start * size,
src_arr + src_start * size,
src_len * size
);
}
if (dest_len > 0) {
/* dropping */
__builtin_memmove(
dest_arr + (dest_start + src_len) * size,
dest_arr + (dest_end + 1) * size,
(dest_arr_len - dest_end - 1) * size
);
}
/* shrink size */
return dest_arr_len - (dest_len - src_len);
}
/* if two range overlaps, need alloca */
uint8_t need_alloca =
(dest_arr == src_arr)
&& !(
max(dest_start, dest_end) < min(src_start, src_end)
|| max(src_start, src_end) < min(dest_start, dest_end)
);
if (need_alloca) {
uint8_t *tmp = reinterpret_cast<uint8_t *>(__builtin_alloca(src_arr_len * size));
__builtin_memcpy(tmp, src_arr, src_arr_len * size);
src_arr = tmp;
}
SliceIndex src_ind = src_start;
SliceIndex dest_ind = dest_start;
for (;
(src_step > 0) ? (src_ind <= src_end) : (src_ind >= src_end);
src_ind += src_step, dest_ind += dest_step
) {
/* for constant optimization */
if (size == 1) {
__builtin_memcpy(dest_arr + dest_ind, src_arr + src_ind, 1);
} else if (size == 4) {
__builtin_memcpy(dest_arr + dest_ind * 4, src_arr + src_ind * 4, 4);
} else if (size == 8) {
__builtin_memcpy(dest_arr + dest_ind * 8, src_arr + src_ind * 8, 8);
} else {
/* memcpy for var size, cannot overlap after previous alloca */
__builtin_memcpy(dest_arr + dest_ind * size, src_arr + src_ind * size, size);
}
}
/* only dest_step == 1 can we shrink the dest list. */
/* size should be ensured prior to calling this function */
if (dest_step == 1 && dest_end >= dest_start) {
__builtin_memmove(
dest_arr + dest_ind * size,
dest_arr + (dest_end + 1) * size,
(dest_arr_len - dest_end - 1) * size
);
return dest_arr_len - (dest_end - dest_ind) - 1;
}
return dest_arr_len;
}
int32_t __nac3_isinf(double x) {
return __builtin_isinf(x);
}
int32_t __nac3_isnan(double x) {
return __builtin_isnan(x);
}
double tgamma(double arg);
double __nac3_gamma(double z) {
// Handling for denormals
// | x | Python gamma(x) | C tgamma(x) |
// --- | ----------------- | --------------- | ----------- |
// (1) | nan | nan | nan |
// (2) | -inf | -inf | inf |
// (3) | inf | inf | inf |
// (4) | 0.0 | inf | inf |
// (5) | {-1.0, -2.0, ...} | inf | nan |
// (1)-(3)
if (__builtin_isinf(z) || __builtin_isnan(z)) {
return z;
}
double v = tgamma(z);
// (4)-(5)
return __builtin_isinf(v) || __builtin_isnan(v) ? __builtin_inf() : v;
}
double lgamma(double arg);
double __nac3_gammaln(double x) {
// libm's handling of value overflows differs from scipy:
// - scipy: gammaln(-inf) -> -inf
// - libm : lgamma(-inf) -> inf
if (__builtin_isinf(x)) {
return x;
}
return lgamma(x);
}
double j0(double x);
double __nac3_j0(double x) {
// libm's handling of value overflows differs from scipy:
// - scipy: j0(inf) -> nan
// - libm : j0(inf) -> 0.0
if (__builtin_isinf(x)) {
return __builtin_nan("");
}
return j0(x);
}
uint32_t __nac3_ndarray_calc_size(
const uint32_t *list_data,
uint32_t list_len,
uint32_t begin_idx,
uint32_t end_idx
) {
return __nac3_ndarray_calc_size_impl(list_data, list_len, begin_idx, end_idx);
}
uint64_t __nac3_ndarray_calc_size64(
const uint64_t *list_data,
uint64_t list_len,
uint64_t begin_idx,
uint64_t end_idx
) {
return __nac3_ndarray_calc_size_impl(list_data, list_len, begin_idx, end_idx);
}
void __nac3_ndarray_calc_nd_indices(
uint32_t index,
const uint32_t* dims,
uint32_t num_dims,
NDIndex* idxs
) {
__nac3_ndarray_calc_nd_indices_impl(index, dims, num_dims, idxs);
}
void __nac3_ndarray_calc_nd_indices64(
uint64_t index,
const uint64_t* dims,
uint64_t num_dims,
NDIndex* idxs
) {
__nac3_ndarray_calc_nd_indices_impl(index, dims, num_dims, idxs);
}
uint32_t __nac3_ndarray_flatten_index(
const uint32_t* dims,
uint32_t num_dims,
const NDIndex* indices,
uint32_t num_indices
) {
return __nac3_ndarray_flatten_index_impl(dims, num_dims, indices, num_indices);
}
uint64_t __nac3_ndarray_flatten_index64(
const uint64_t* dims,
uint64_t num_dims,
const NDIndex* indices,
uint64_t num_indices
) {
return __nac3_ndarray_flatten_index_impl(dims, num_dims, indices, num_indices);
}
void __nac3_ndarray_calc_broadcast(
const uint32_t *lhs_dims,
uint32_t lhs_ndims,
const uint32_t *rhs_dims,
uint32_t rhs_ndims,
uint32_t *out_dims
) {
return __nac3_ndarray_calc_broadcast_impl(lhs_dims, lhs_ndims, rhs_dims, rhs_ndims, out_dims);
}
void __nac3_ndarray_calc_broadcast64(
const uint64_t *lhs_dims,
uint64_t lhs_ndims,
const uint64_t *rhs_dims,
uint64_t rhs_ndims,
uint64_t *out_dims
) {
return __nac3_ndarray_calc_broadcast_impl(lhs_dims, lhs_ndims, rhs_dims, rhs_ndims, out_dims);
}
void __nac3_ndarray_calc_broadcast_idx(
const uint32_t *src_dims,
uint32_t src_ndims,
const NDIndex *in_idx,
NDIndex *out_idx
) {
__nac3_ndarray_calc_broadcast_idx_impl(src_dims, src_ndims, in_idx, out_idx);
}
void __nac3_ndarray_calc_broadcast_idx64(
const uint64_t *src_dims,
uint64_t src_ndims,
const NDIndex *in_idx,
NDIndex *out_idx
) {
__nac3_ndarray_calc_broadcast_idx_impl(src_dims, src_ndims, in_idx, out_idx);
}
void __nac3_ndarray_strides_from_shape(uint32_t ndims, uint32_t* shape, uint32_t* dst_strides) {
__nac3_ndarray_strides_from_shape_impl(ndims, shape, dst_strides);
}
void __nac3_ndarray_strides_from_shape64(uint64_t ndims, uint64_t* shape, uint64_t* dst_strides) {
__nac3_ndarray_strides_from_shape_impl(ndims, shape, dst_strides);
}
}

View File

@ -1,216 +0,0 @@
#pragma once
#include "irrt_utils.hpp"
#include "irrt_typedefs.hpp"
/*
This header contains IRRT implementations
that do not deserved to be categorized (e.g., into numpy, etc.)
Check out other *.hpp files before including them here!!
*/
// The type of an index or a value describing the length of a range/slice is
// always `int32_t`.
namespace {
// adapted from GNU Scientific Library: https://git.savannah.gnu.org/cgit/gsl.git/tree/sys/pow_int.c
// need to make sure `exp >= 0` before calling this function
template <typename T>
T __nac3_int_exp_impl(T base, T exp) {
T res = 1;
/* repeated squaring method */
do {
if (exp & 1) {
res *= base; /* for n odd */
}
exp >>= 1;
base *= base;
} while (exp);
return res;
}
}
extern "C" {
#define DEF_nac3_int_exp_(T) \
T __nac3_int_exp_##T(T base, T exp) {\
return __nac3_int_exp_impl(base, exp);\
}
DEF_nac3_int_exp_(int32_t)
DEF_nac3_int_exp_(int64_t)
DEF_nac3_int_exp_(uint32_t)
DEF_nac3_int_exp_(uint64_t)
SliceIndex __nac3_slice_index_bound(SliceIndex i, const SliceIndex len) {
if (i < 0) {
i = len + i;
}
if (i < 0) {
return 0;
} else if (i > len) {
return len;
}
return i;
}
SliceIndex __nac3_range_slice_len(
const SliceIndex start,
const SliceIndex end,
const SliceIndex step
) {
SliceIndex diff = end - start;
if (diff > 0 && step > 0) {
return ((diff - 1) / step) + 1;
} else if (diff < 0 && step < 0) {
return ((diff + 1) / step) + 1;
} else {
return 0;
}
}
// Handle list assignment and dropping part of the list when
// both dest_step and src_step are +1.
// - All the index must *not* be out-of-bound or negative,
// - The end index is *inclusive*,
// - The length of src and dest slice size should already
// be checked: if dest.step == 1 then len(src) <= len(dest) else len(src) == len(dest)
SliceIndex __nac3_list_slice_assign_var_size(
SliceIndex dest_start,
SliceIndex dest_end,
SliceIndex dest_step,
uint8_t *dest_arr,
SliceIndex dest_arr_len,
SliceIndex src_start,
SliceIndex src_end,
SliceIndex src_step,
uint8_t *src_arr,
SliceIndex src_arr_len,
const SliceIndex size
) {
/* if dest_arr_len == 0, do nothing since we do not support extending list */
if (dest_arr_len == 0) return dest_arr_len;
/* if both step is 1, memmove directly, handle the dropping of the list, and shrink size */
if (src_step == dest_step && dest_step == 1) {
const SliceIndex src_len = (src_end >= src_start) ? (src_end - src_start + 1) : 0;
const SliceIndex dest_len = (dest_end >= dest_start) ? (dest_end - dest_start + 1) : 0;
if (src_len > 0) {
__builtin_memmove(
dest_arr + dest_start * size,
src_arr + src_start * size,
src_len * size
);
}
if (dest_len > 0) {
/* dropping */
__builtin_memmove(
dest_arr + (dest_start + src_len) * size,
dest_arr + (dest_end + 1) * size,
(dest_arr_len - dest_end - 1) * size
);
}
/* shrink size */
return dest_arr_len - (dest_len - src_len);
}
/* if two range overlaps, need alloca */
uint8_t need_alloca =
(dest_arr == src_arr)
&& !(
max(dest_start, dest_end) < min(src_start, src_end)
|| max(src_start, src_end) < min(dest_start, dest_end)
);
if (need_alloca) {
uint8_t *tmp = reinterpret_cast<uint8_t *>(__builtin_alloca(src_arr_len * size));
__builtin_memcpy(tmp, src_arr, src_arr_len * size);
src_arr = tmp;
}
SliceIndex src_ind = src_start;
SliceIndex dest_ind = dest_start;
for (;
(src_step > 0) ? (src_ind <= src_end) : (src_ind >= src_end);
src_ind += src_step, dest_ind += dest_step
) {
/* for constant optimization */
if (size == 1) {
__builtin_memcpy(dest_arr + dest_ind, src_arr + src_ind, 1);
} else if (size == 4) {
__builtin_memcpy(dest_arr + dest_ind * 4, src_arr + src_ind * 4, 4);
} else if (size == 8) {
__builtin_memcpy(dest_arr + dest_ind * 8, src_arr + src_ind * 8, 8);
} else {
/* memcpy for var size, cannot overlap after previous alloca */
__builtin_memcpy(dest_arr + dest_ind * size, src_arr + src_ind * size, size);
}
}
/* only dest_step == 1 can we shrink the dest list. */
/* size should be ensured prior to calling this function */
if (dest_step == 1 && dest_end >= dest_start) {
__builtin_memmove(
dest_arr + dest_ind * size,
dest_arr + (dest_end + 1) * size,
(dest_arr_len - dest_end - 1) * size
);
return dest_arr_len - (dest_end - dest_ind) - 1;
}
return dest_arr_len;
}
int32_t __nac3_isinf(double x) {
return __builtin_isinf(x);
}
int32_t __nac3_isnan(double x) {
return __builtin_isnan(x);
}
double tgamma(double arg);
double __nac3_gamma(double z) {
// Handling for denormals
// | x | Python gamma(x) | C tgamma(x) |
// --- | ----------------- | --------------- | ----------- |
// (1) | nan | nan | nan |
// (2) | -inf | -inf | inf |
// (3) | inf | inf | inf |
// (4) | 0.0 | inf | inf |
// (5) | {-1.0, -2.0, ...} | inf | nan |
// (1)-(3)
if (__builtin_isinf(z) || __builtin_isnan(z)) {
return z;
}
double v = tgamma(z);
// (4)-(5)
return __builtin_isinf(v) || __builtin_isnan(v) ? __builtin_inf() : v;
}
double lgamma(double arg);
double __nac3_gammaln(double x) {
// libm's handling of value overflows differs from scipy:
// - scipy: gammaln(-inf) -> -inf
// - libm : lgamma(-inf) -> inf
if (__builtin_isinf(x)) {
return x;
}
return lgamma(x);
}
double j0(double x);
double __nac3_j0(double x) {
// libm's handling of value overflows differs from scipy:
// - scipy: j0(inf) -> nan
// - libm : j0(inf) -> 0.0
if (__builtin_isinf(x)) {
return __builtin_nan("");
}
return j0(x);
}
}

View File

@ -1,14 +0,0 @@
#pragma once
#include "irrt_utils.hpp"
#include "irrt_typedefs.hpp"
#include "irrt_basic.hpp"
#include "irrt_slice.hpp"
#include "irrt_numpy_ndarray.hpp"
/*
All IRRT implementations.
We don't have any pre-compiled objects, so we are writing all implementations in headers and
concatenate them with `#include` into one massive source file that contains all the IRRT stuff.
*/

View File

@ -1,466 +0,0 @@
#pragma once
#include "irrt_utils.hpp"
#include "irrt_typedefs.hpp"
#include "irrt_slice.hpp"
/*
NDArray-related implementations.
`*/
// NDArray indices are always `uint32_t`.
using NDIndex = uint32_t;
namespace {
namespace ndarray_util {
template <typename SizeT>
static void set_indices_by_nth(SizeT ndims, const SizeT* shape, SizeT* indices, SizeT nth) {
for (int32_t i = 0; i < ndims; i++) {
int32_t dim_i = ndims - i - 1;
int32_t dim = shape[dim_i];
indices[dim_i] = nth % dim;
nth /= dim;
}
}
// Compute the strides of an ndarray given an ndarray `shape`
// and assuming that the ndarray is *fully C-contagious*.
//
// You might want to read up on https://ajcr.net/stride-guide-part-1/.
template <typename SizeT>
static void set_strides_by_shape(SizeT itemsize, SizeT ndims, SizeT* dst_strides, const SizeT* shape) {
SizeT stride_product = 1;
for (SizeT i = 0; i < ndims; i++) {
int dim_i = ndims - i - 1;
dst_strides[dim_i] = stride_product * itemsize;
stride_product *= shape[dim_i];
}
}
// Compute the size/# of elements of an ndarray given its shape
template <typename SizeT>
static SizeT calc_size_from_shape(SizeT ndims, const SizeT* shape) {
SizeT size = 1;
for (SizeT dim_i = 0; dim_i < ndims; dim_i++) size *= shape[dim_i];
return size;
}
template <typename SizeT>
static bool can_broadcast_shape_to(
const SizeT target_ndims,
const SizeT *target_shape,
const SizeT src_ndims,
const SizeT *src_shape
) {
/*
// See https://numpy.org/doc/stable/user/basics.broadcasting.html
This function handles this example:
```
Image (3d array): 256 x 256 x 3
Scale (1d array): 3
Result (3d array): 256 x 256 x 3
```
Other interesting examples to consider:
- `can_broadcast_shape_to([3], [1, 1, 1, 1, 3]) == true`
- `can_broadcast_shape_to([3], [3, 1]) == false`
- `can_broadcast_shape_to([256, 256, 3], [256, 1, 3]) == true`
In cases when the shapes contain zero(es):
- `can_broadcast_shape_to([0], [1]) == true`
- `can_broadcast_shape_to([0], [2]) == false`
- `can_broadcast_shape_to([0, 4, 0, 0], [1]) == true`
- `can_broadcast_shape_to([0, 4, 0, 0], [1, 1, 1, 1]) == true`
- `can_broadcast_shape_to([0, 4, 0, 0], [1, 4, 1, 1]) == true`
- `can_broadcast_shape_to([4, 3], [0, 3]) == false`
- `can_broadcast_shape_to([4, 3], [0, 0]) == false`
*/
// This is essentially doing the following in Python:
// `for target_dim, src_dim in itertools.zip_longest(target_shape[::-1], src_shape[::-1], fillvalue=1)`
for (SizeT i = 0; i < max(target_ndims, src_ndims); i++) {
SizeT target_dim_i = target_ndims - i - 1;
SizeT src_dim_i = src_ndims - i - 1;
bool target_dim_exists = target_dim_i >= 0;
bool src_dim_exists = src_dim_i >= 0;
SizeT target_dim = target_dim_exists ? target_shape[target_dim_i] : 1;
SizeT src_dim = src_dim_exists ? src_shape[src_dim_i] : 1;
bool ok = src_dim == 1 || target_dim == src_dim;
if (!ok) return false;
}
return true;
}
}
typedef uint8_t NDSliceType;
extern "C" {
const NDSliceType INPUT_SLICE_TYPE_INDEX = 0;
const NDSliceType INPUT_SLICE_TYPE_SLICE = 1;
}
struct NDSlice {
// A poor-man's `std::variant<int, UserRange>`
NDSliceType type;
/*
if type == INPUT_SLICE_TYPE_INDEX => `slice` points to a single `SizeT`
if type == INPUT_SLICE_TYPE_SLICE => `slice` points to a single `UserRange`
*/
uint8_t *slice;
};
namespace ndarray_util {
template<typename SizeT>
SizeT deduce_ndims_after_slicing(SizeT ndims, SizeT num_slices, const NDSlice *slices) {
irrt_assert(num_slices <= ndims);
SizeT final_ndims = ndims;
for (SizeT i = 0; i < num_slices; i++) {
if (slices[i].type == INPUT_SLICE_TYPE_INDEX) {
final_ndims--; // An integer slice demotes the rank by 1
}
}
return final_ndims;
}
}
template <typename SizeT>
struct NDArrayIndicesIter {
SizeT ndims;
const SizeT *shape;
SizeT *indices;
void set_indices_zero() {
__builtin_memset(indices, 0, sizeof(SizeT) * ndims);
}
void next() {
for (SizeT i = 0; i < ndims; i++) {
SizeT dim_i = ndims - i - 1;
indices[dim_i]++;
if (indices[dim_i] < shape[dim_i]) {
break;
} else {
indices[dim_i] = 0;
}
}
}
};
// The NDArray object. `SizeT` is the *signed* size type of this ndarray.
//
// NOTE: The order of fields is IMPORTANT. DON'T TOUCH IT
//
// Some resources you might find helpful:
// - The official numpy implementations:
// - https://github.com/numpy/numpy/blob/735a477f0bc2b5b84d0e72d92f224bde78d4e069/doc/source/reference/c-api/types-and-structures.rst
// - On strides (about reshaping, slicing, C-contagiousness, etc)
// - https://ajcr.net/stride-guide-part-1/.
// - https://ajcr.net/stride-guide-part-2/.
// - https://ajcr.net/stride-guide-part-3/.
template <typename SizeT>
struct NDArray {
// The underlying data this `ndarray` is pointing to.
//
// NOTE: Formally this should be of type `void *`, but clang
// translates `void *` to `i8 *` when run with `-S -emit-llvm`,
// so we will put `uint8_t *` here for clarity.
uint8_t *data;
// The number of bytes of a single element in `data`.
//
// The `SizeT` is treated as `unsigned`.
SizeT itemsize;
// The number of dimensions of this shape.
//
// The `SizeT` is treated as `unsigned`.
SizeT ndims;
// Array shape, with length equal to `ndims`.
//
// The `SizeT` is treated as `unsigned`.
//
// NOTE: `shape` can contain 0.
// (those appear when the user makes an out of bounds slice into an ndarray, e.g., `np.zeros((3, 3))[400:].shape == (0, 3)`)
SizeT *shape;
// Array strides (stride value is in number of bytes, NOT number of elements), with length equal to `ndims`.
//
// The `SizeT` is treated as `signed`.
//
// NOTE: `strides` can have negative numbers.
// (those appear when there is a slice with a negative step, e.g., `my_array[::-1]`)
SizeT *strides;
// Calculate the size/# of elements of an `ndarray`.
// This function corresponds to `np.size(<ndarray>)` or `ndarray.size`
SizeT size() {
return ndarray_util::calc_size_from_shape(ndims, shape);
}
// Calculate the number of bytes of its content of an `ndarray` *in its view*.
// This function corresponds to `ndarray.nbytes`
SizeT nbytes() {
return this->size() * itemsize;
}
void set_value_at_pelement(uint8_t* pelement, const uint8_t* pvalue) {
__builtin_memcpy(pelement, pvalue, itemsize);
}
uint8_t* get_pelement(const SizeT *indices) {
uint8_t* element = data;
for (SizeT dim_i = 0; dim_i < ndims; dim_i++)
element += indices[dim_i] * strides[dim_i];
return element;
}
uint8_t* get_nth_pelement(SizeT nth) {
irrt_assert(0 <= nth);
irrt_assert(nth < this->size());
SizeT* indices = (SizeT*) __builtin_alloca(sizeof(SizeT) * this->ndims);
ndarray_util::set_indices_by_nth(this->ndims, this->shape, indices, nth);
return get_pelement(indices);
}
// Get pointer to the first element of this ndarray, assuming
// `this->size() > 0`, i.e., not "degenerate" due to zeroes in `this->shape`)
//
// This is particularly useful for when the ndarray is just containing a single scalar.
uint8_t* get_first_pelement() {
irrt_assert(this->size() > 0);
return this->data; // ...It is simply `this->data`
}
// Is the given `indices` valid/in-bounds?
bool in_bounds(const SizeT *indices) {
for (SizeT dim_i = 0; dim_i < ndims; dim_i++) {
bool dim_ok = indices[dim_i] < shape[dim_i];
if (!dim_ok) return false;
}
return true;
}
// Fill the ndarray with a value
void fill_generic(const uint8_t* pvalue) {
NDArrayIndicesIter<SizeT> iter;
iter.ndims = this->ndims;
iter.shape = this->shape;
iter.indices = (SizeT*) __builtin_alloca(sizeof(SizeT) * ndims);
iter.set_indices_zero();
for (SizeT i = 0; i < this->size(); i++, iter.next()) {
uint8_t* pelement = get_pelement(iter.indices);
set_value_at_pelement(pelement, pvalue);
}
}
// Set the strides of the ndarray with `ndarray_util::set_strides_by_shape`
void set_strides_by_shape() {
ndarray_util::set_strides_by_shape(itemsize, ndims, strides, shape);
}
// https://numpy.org/doc/stable/reference/generated/numpy.eye.html
void set_to_eye(SizeT k, const uint8_t* zero_pvalue, const uint8_t* one_pvalue) {
__builtin_assume(ndims == 2);
// TODO: Better implementation
fill_generic(zero_pvalue);
for (SizeT i = 0; i < min(shape[0], shape[1]); i++) {
SizeT row = i;
SizeT col = i + k;
SizeT indices[2] = { row, col };
if (!in_bounds(indices)) continue;
uint8_t* pelement = get_pelement(indices);
set_value_at_pelement(pelement, one_pvalue);
}
}
// To support numpy complex slices (e.g., `my_array[:50:2,4,:2:-1]`)
//
// Things assumed by this function:
// - `dst_ndarray` is allocated by the caller
// - `dst_ndarray.ndims` has the correct value (according to `ndarray_util::deduce_ndims_after_slicing`).
// - ... and `dst_ndarray.shape` and `dst_ndarray.strides` have been allocated by the caller as well
//
// Other notes:
// - `dst_ndarray->data` does not have to be set, it will be derived.
// - `dst_ndarray->itemsize` does not have to be set, it will be set to `this->itemsize`
// - `dst_ndarray->shape` and `dst_ndarray.strides` can contain empty values
void slice(SizeT num_ndslices, NDSlice* ndslices, NDArray<SizeT>* dst_ndarray) {
// REFERENCE CODE (check out `_index_helper` in `__getitem__`):
// https://github.com/wadetb/tinynumpy/blob/0d23d22e07062ffab2afa287374c7b366eebdda1/tinynumpy/tinynumpy.py#L652
irrt_assert(dst_ndarray->ndims == ndarray_util::deduce_ndims_after_slicing(this->ndims, num_ndslices, ndslices));
dst_ndarray->data = this->data;
SizeT this_axis = 0;
SizeT dst_axis = 0;
for (SizeT i = 0; i < num_ndslices; i++) {
NDSlice *ndslice = &ndslices[i];
if (ndslice->type == INPUT_SLICE_TYPE_INDEX) {
// Handle when the ndslice is just a single (possibly negative) integer
// e.g., `my_array[::2, -5, ::-1]`
// ^^------ like this
SizeT index_user = *((SizeT*) ndslice->slice);
SizeT index = resolve_index_in_length(this->shape[this_axis], index_user);
dst_ndarray->data += index * this->strides[this_axis]; // Add offset
// Next
this_axis++;
} else if (ndslice->type == INPUT_SLICE_TYPE_SLICE) {
// Handle when the ndslice is a slice (represented by UserSlice in IRRT)
// e.g., `my_array[::2, -5, ::-1]`
// ^^^------^^^^----- like these
UserSlice<SizeT>* user_slice = (UserSlice<SizeT>*) ndslice->slice;
Slice<SizeT> slice = user_slice->indices(this->shape[this_axis]); // To resolve negative indices and other funny stuff written by the user
// NOTE: There is no need to write special code to handle negative steps/strides.
// This simple implementation meticulously handles both positive and negative steps/strides.
// Check out the tinynumpy and IRRT's test cases if you are not convinced.
dst_ndarray->data += slice.start * this->strides[this_axis]; // Add offset (NOTE: no need to `* itemsize`, strides count in # of bytes)
dst_ndarray->strides[dst_axis] = slice.step * this->strides[this_axis]; // Determine stride
dst_ndarray->shape[dst_axis] = slice.len(); // Determine shape dimension
// Next
dst_axis++;
this_axis++;
} else {
__builtin_unreachable();
}
}
irrt_assert(dst_axis == dst_ndarray->ndims); // Sanity check on the implementation
}
// Similar to `np.broadcast_to(<ndarray>, <target_shape>)`
// Assumptions:
// - `this` has to be fully initialized.
// - `dst_ndarray->ndims` has to be set.
// - `dst_ndarray->shape` has to be set, this determines the shape `this` broadcasts to.
//
// Other notes:
// - `dst_ndarray->data` does not have to be set, it will be set to `this->data`.
// - `dst_ndarray->itemsize` does not have to be set, it will be set to `this->data`.
// - `dst_ndarray->strides` does not have to be set, it will be overwritten.
//
// Cautions:
// ```
// xs = np.zeros((4,))
// ys = np.zero((4, 1))
// ys[:] = xs # ok
//
// xs = np.zeros((1, 4))
// ys = np.zero((4,))
// ys[:] = xs # allowed
// # However `np.broadcast_to(xs, (4,))` would fails, as per numpy's broadcasting rule.
// # and apparently numpy will "deprecate" this? SEE https://github.com/numpy/numpy/issues/21744
// # This implementation will NOT support this assignment.
// ```
void broadcast_to(NDArray<SizeT>* dst_ndarray) {
dst_ndarray->data = this->data;
dst_ndarray->itemsize = this->itemsize;
irrt_assert(
ndarray_util::can_broadcast_shape_to(
dst_ndarray->ndims,
dst_ndarray->shape,
this->ndims,
this->shape
)
);
SizeT stride_product = 1;
for (SizeT i = 0; i < max(this->ndims, dst_ndarray->ndims); i++) {
SizeT this_dim_i = this->ndims - i - 1;
SizeT dst_dim_i = dst_ndarray->ndims - i - 1;
bool this_dim_exists = this_dim_i >= 0;
bool dst_dim_exists = dst_dim_i >= 0;
// TODO: Explain how this works
bool c1 = this_dim_exists && this->shape[this_dim_i] == 1;
bool c2 = dst_dim_exists && dst_ndarray->shape[dst_dim_i] != 1;
if (!this_dim_exists || (c1 && c2)) {
dst_ndarray->strides[dst_dim_i] = 0; // Freeze it in-place
} else {
dst_ndarray->strides[dst_dim_i] = stride_product * this->itemsize;
stride_product *= this->shape[this_dim_i]; // NOTE: this_dim_exist must be true here.
}
}
}
// Simulates `this_ndarray[:] = src_ndarray`, with automatic broadcasting.
// Caution on https://github.com/numpy/numpy/issues/21744
// Also see `NDArray::broadcast_to`
void assign_with(NDArray<SizeT>* src_ndarray) {
irrt_assert(
ndarray_util::can_broadcast_shape_to(
this->ndims,
this->shape,
src_ndarray->ndims,
src_ndarray->shape
)
);
// Broadcast the `src_ndarray` to make the reading process *much* easier
SizeT* broadcasted_src_ndarray_strides = __builtin_alloca(sizeof(SizeT) * this->ndims); // Remember to allocate strides beforehand
NDArray<SizeT> broadcasted_src_ndarray = {
.ndims = this->ndims,
.shape = this->shape,
.strides = broadcasted_src_ndarray_strides
};
src_ndarray->broadcast_to(&broadcasted_src_ndarray);
// Using iter instead of `get_nth_pelement` because it is slightly faster
SizeT* indices = __builtin_alloca(sizeof(SizeT) * this->ndims);
auto iter = NDArrayIndicesIter<SizeT> {
.ndims = this->ndims,
.shape = this->shape,
.indices = indices
};
const SizeT this_size = this->size();
for (SizeT i = 0; i < this_size; i++, iter.next()) {
uint8_t* src_pelement = broadcasted_src_ndarray_strides->get_pelement(indices);
uint8_t* this_pelement = this->get_pelement(indices);
this->set_value_at_pelement(src_pelement, src_pelement);
}
}
};
}
extern "C" {
uint32_t __nac3_ndarray_size(NDArray<int32_t>* ndarray) {
return ndarray->size();
}
uint64_t __nac3_ndarray_size64(NDArray<int64_t>* ndarray) {
return ndarray->size();
}
void __nac3_ndarray_fill_generic(NDArray<int32_t>* ndarray, uint8_t* pvalue) {
ndarray->fill_generic(pvalue);
}
void __nac3_ndarray_fill_generic64(NDArray<int64_t>* ndarray, uint8_t* pvalue) {
ndarray->fill_generic(pvalue);
}
// void __nac3_ndarray_slice(NDArray<int32_t>* ndarray, int32_t num_slices, NDSlice<int32_t> *slices, NDArray<int32_t> *dst_ndarray) {
// // ndarray->slice(num_slices, slices, dst_ndarray);
// }
}

View File

@ -1,80 +0,0 @@
#pragma once
#include "irrt_utils.hpp"
#include "irrt_typedefs.hpp"
namespace {
// A proper slice in IRRT, all negative indices have be resolved to absolute values.
// Even though nac3core's slices are always `int32_t`, we will template slice anyway
// since this struct is used as a general utility.
template <typename T>
struct Slice {
T start;
T stop;
T step;
// The length/The number of elements of the slice if it were a range,
// i.e., the value of `len(range(this->start, this->stop, this->end))`
T len() {
T diff = stop - start;
if (diff > 0 && step > 0) {
return ((diff - 1) / step) + 1;
} else if (diff < 0 && step < 0) {
return ((diff + 1) / step) + 1;
} else {
return 0;
}
}
};
template<typename T>
T resolve_index_in_length(T length, T index) {
irrt_assert(length >= 0);
if (index < 0) {
// Remember that index is negative, so do a plus here
return max(length + index, 0);
} else {
return min(length, index);
}
}
// NOTE: using a bitfield for the `*_defined` is better, at the
// cost of a more annoying implementation in nac3core inkwell
template <typename T>
struct UserSlice {
uint8_t start_defined;
T start;
uint8_t stop_defined;
T stop;
uint8_t step_defined;
T step;
// Like Python's `slice(start, stop, step).indices(length)`
Slice<T> indices(T length) {
// NOTE: This function implements Python's `slice.indices` *FAITHFULLY*.
// SEE: https://github.com/python/cpython/blob/f62161837e68c1c77961435f1b954412dd5c2b65/Objects/sliceobject.c#L546
irrt_assert(length >= 0);
irrt_assert(!step_defined || step != 0); // step_defined -> step != 0; step cannot be zero if specified by user
Slice<T> result;
result.step = step_defined ? step : 1;
bool step_is_negative = result.step < 0;
if (start_defined) {
result.start = resolve_index_in_length(length, start);
} else {
result.start = step_is_negative ? length - 1 : 0;
}
if (stop_defined) {
result.stop = resolve_index_in_length(length, stop);
} else {
result.stop = step_is_negative ? -1 : length;
}
return result;
}
};
}

View File

@ -1,658 +0,0 @@
// This file will be compiled like a real C++ program,
// and we do have the luxury to use the standard libraries.
// That is if the nix flakes do not have issues... especially on msys2...
#include <cstdint>
#include <cstdio>
#include <cstdlib>
// Set `IRRT_DONT_TYPEDEF_INTS` because `cstdint` defines them
#define IRRT_DONT_TYPEDEF_INTS
#include "irrt_everything.hpp"
void test_fail() {
printf("[!] Test failed\n");
exit(1);
}
void __begin_test(const char* function_name, const char* file, int line) {
printf("######### Running %s @ %s:%d\n", function_name, file, line);
}
#define BEGIN_TEST() __begin_test(__FUNCTION__, __FILE__, __LINE__)
template <typename T>
void debug_print_array(const char* format, int len, T* as) {
printf("[");
for (int i = 0; i < len; i++) {
if (i != 0) printf(", ");
printf(format, as[i]);
}
printf("]");
}
template <typename T>
void assert_arrays_match(const char* label, const char* format, int len, T* expected, T* got) {
if (!arrays_match(len, expected, got)) {
printf(">>>>>>> %s\n", label);
printf(" Expecting = ");
debug_print_array(format, len, expected);
printf("\n");
printf(" Got = ");
debug_print_array(format, len, got);
printf("\n");
test_fail();
}
}
template <typename T>
void assert_values_match(const char* label, const char* format, T expected, T got) {
if (expected != got) {
printf(">>>>>>> %s\n", label);
printf(" Expecting = ");
printf(format, expected);
printf("\n");
printf(" Got = ");
printf(format, got);
printf("\n");
test_fail();
}
}
void print_repeated(const char *str, int count) {
for (int i = 0; i < count; i++) {
printf("%s", str);
}
}
template<typename SizeT, typename ElementT>
void __print_ndarray_aux(const char *format, bool first, bool last, SizeT* cursor, SizeT depth, NDArray<SizeT>* ndarray) {
// A really lazy recursive implementation
// Add left padding unless its the first entry (since there would be "[[[" before it)
if (!first) {
print_repeated(" ", depth);
}
const SizeT dim = ndarray->shape[depth];
if (depth + 1 == ndarray->ndims) {
// Recursed down to last dimension, print the values in a nice list
printf("[");
SizeT* indices = (SizeT*) __builtin_alloca(sizeof(SizeT) * ndarray->ndims);
for (SizeT i = 0; i < dim; i++) {
ndarray_util::set_indices_by_nth(ndarray->ndims, ndarray->shape, indices, *cursor);
ElementT* pelement = (ElementT*) ndarray->get_pelement(indices);
ElementT element = *pelement;
if (i != 0) printf(", "); // List delimiter
printf(format, element);
printf("(@");
debug_print_array("%d", ndarray->ndims, indices);
printf(")");
(*cursor)++;
}
printf("]");
} else {
printf("[");
for (SizeT i = 0; i < ndarray->shape[depth]; i++) {
__print_ndarray_aux<SizeT, ElementT>(
format,
i == 0, // first?
i + 1 == dim, // last?
cursor,
depth + 1,
ndarray
);
}
printf("]");
}
// Add newline unless its the last entry (since there will be "]]]" after it)
if (!last) {
print_repeated("\n", depth);
}
}
template<typename SizeT, typename ElementT>
void print_ndarray(const char *format, NDArray<SizeT>* ndarray) {
if (ndarray->ndims == 0) {
printf("<empty ndarray>");
} else {
SizeT cursor = 0;
__print_ndarray_aux<SizeT, ElementT>(format, true, true, &cursor, 0, ndarray);
}
printf("\n");
}
void test_calc_size_from_shape_normal() {
// Test shapes with normal values
BEGIN_TEST();
int32_t shape[4] = { 2, 3, 5, 7 };
assert_values_match("size", "%d", 210, ndarray_util::calc_size_from_shape<int32_t>(4, shape));
}
void test_calc_size_from_shape_has_zero() {
// Test shapes with 0 in them
BEGIN_TEST();
int32_t shape[4] = { 2, 0, 5, 7 };
assert_values_match("size", "%d", 0, ndarray_util::calc_size_from_shape<int32_t>(4, shape));
}
void test_set_strides_by_shape() {
// Test `set_strides_by_shape()`
BEGIN_TEST();
int32_t shape[4] = { 99, 3, 5, 7 };
int32_t strides[4] = { 0 };
ndarray_util::set_strides_by_shape((int32_t) sizeof(int32_t), 4, strides, shape);
int32_t expected_strides[4] = {
105 * sizeof(int32_t),
35 * sizeof(int32_t),
7 * sizeof(int32_t),
1 * sizeof(int32_t)
};
assert_arrays_match("strides", "%u", 4u, expected_strides, strides);
}
void test_ndarray_indices_iter_normal() {
// Test NDArrayIndicesIter normal behavior
BEGIN_TEST();
int32_t shape[3] = { 1, 2, 3 };
int32_t indices[3] = { 0, 0, 0 };
auto iter = NDArrayIndicesIter<int32_t> {
.ndims = 3,
.shape = shape,
.indices = indices
};
assert_arrays_match("indices #0", "%u", 3u, iter.indices, (int32_t[3]) { 0, 0, 0 });
iter.next();
assert_arrays_match("indices #1", "%u", 3u, iter.indices, (int32_t[3]) { 0, 0, 1 });
iter.next();
assert_arrays_match("indices #2", "%u", 3u, iter.indices, (int32_t[3]) { 0, 0, 2 });
iter.next();
assert_arrays_match("indices #3", "%u", 3u, iter.indices, (int32_t[3]) { 0, 1, 0 });
iter.next();
assert_arrays_match("indices #4", "%u", 3u, iter.indices, (int32_t[3]) { 0, 1, 1 });
iter.next();
assert_arrays_match("indices #5", "%u", 3u, iter.indices, (int32_t[3]) { 0, 1, 2 });
iter.next();
assert_arrays_match("indices #6", "%u", 3u, iter.indices, (int32_t[3]) { 0, 0, 0 }); // Loops back
iter.next();
assert_arrays_match("indices #7", "%u", 3u, iter.indices, (int32_t[3]) { 0, 0, 1 });
}
void test_ndarray_fill_generic() {
// Test ndarray fill_generic
BEGIN_TEST();
// Choose a type that's neither int32_t nor uint64_t (candidates of SizeT) to spice it up
// Also make all the octets non-zero, to see if `memcpy` in `fill_generic` is working perfectly.
uint16_t fill_value = 0xFACE;
uint16_t in_data[6] = { 100, 101, 102, 103, 104, 105 }; // Fill `data` with values that != `999`
int32_t in_itemsize = sizeof(uint16_t);
const int32_t in_ndims = 2;
int32_t in_shape[in_ndims] = { 2, 3 };
int32_t in_strides[in_ndims] = {};
NDArray<int32_t> ndarray = {
.data = (uint8_t*) in_data,
.itemsize = in_itemsize,
.ndims = in_ndims,
.shape = in_shape,
.strides = in_strides,
};
ndarray.set_strides_by_shape();
ndarray.fill_generic((uint8_t*) &fill_value); // `fill_generic` here
uint16_t expected_data[6] = { fill_value, fill_value, fill_value, fill_value, fill_value, fill_value };
assert_arrays_match("data", "0x%hX", 6, expected_data, in_data);
}
void test_ndarray_set_to_eye() {
// Test `set_to_eye` behavior (helper function to implement `np.eye()`)
BEGIN_TEST();
double in_data[9] = { 99.0, 99.0, 99.0, 99.0, 99.0, 99.0, 99.0, 99.0, 99.0 };
int32_t in_itemsize = sizeof(double);
const int32_t in_ndims = 2;
int32_t in_shape[in_ndims] = { 3, 3 };
int32_t in_strides[in_ndims] = {};
NDArray<int32_t> ndarray = {
.data = (uint8_t*) in_data,
.itemsize = in_itemsize,
.ndims = in_ndims,
.shape = in_shape,
.strides = in_strides,
};
ndarray.set_strides_by_shape();
double zero = 0.0;
double one = 1.0;
ndarray.set_to_eye(1, (uint8_t*) &zero, (uint8_t*) &one);
assert_values_match("in_data[0]", "%f", 0.0, in_data[0]);
assert_values_match("in_data[1]", "%f", 1.0, in_data[1]);
assert_values_match("in_data[2]", "%f", 0.0, in_data[2]);
assert_values_match("in_data[3]", "%f", 0.0, in_data[3]);
assert_values_match("in_data[4]", "%f", 0.0, in_data[4]);
assert_values_match("in_data[5]", "%f", 1.0, in_data[5]);
assert_values_match("in_data[6]", "%f", 0.0, in_data[6]);
assert_values_match("in_data[7]", "%f", 0.0, in_data[7]);
assert_values_match("in_data[8]", "%f", 0.0, in_data[8]);
}
void test_slice_1() {
// Test `slice(5, None, None).indices(100) == slice(5, 100, 1)`
BEGIN_TEST();
UserSlice<int> user_slice = {
.start_defined = 1,
.start = 5,
.stop_defined = 0,
.step_defined = 0,
};
auto slice = user_slice.indices(100);
assert_values_match("start", "%d", 5, slice.start);
assert_values_match("stop", "%d", 100, slice.stop);
assert_values_match("step", "%d", 1, slice.step);
}
void test_slice_2() {
// Test `slice(400, 999, None).indices(100) == slice(100, 100, 1)`
BEGIN_TEST();
UserSlice<int> user_slice = {
.start_defined = 1,
.start = 400,
.stop_defined = 0,
.step_defined = 0,
};
auto slice = user_slice.indices(100);
assert_values_match("start", "%d", 100, slice.start);
assert_values_match("stop", "%d", 100, slice.stop);
assert_values_match("step", "%d", 1, slice.step);
}
void test_slice_3() {
// Test `slice(-10, -5, None).indices(100) == slice(90, 95, 1)`
BEGIN_TEST();
UserSlice<int> user_slice = {
.start_defined = 1,
.start = -10,
.stop_defined = 1,
.stop = -5,
.step_defined = 0,
};
auto slice = user_slice.indices(100);
assert_values_match("start", "%d", 90, slice.start);
assert_values_match("stop", "%d", 95, slice.stop);
assert_values_match("step", "%d", 1, slice.step);
}
void test_slice_4() {
// Test `slice(None, None, -5).indices(100) == (99, -1, -5)`
BEGIN_TEST();
UserSlice<int> user_slice = {
.start_defined = 0,
.stop_defined = 0,
.step_defined = 1,
.step = -5
};
auto slice = user_slice.indices(100);
assert_values_match("start", "%d", 99, slice.start);
assert_values_match("stop", "%d", -1, slice.stop);
assert_values_match("step", "%d", -5, slice.step);
}
void test_ndslice_1() {
/*
Reference Python code:
```python
ndarray = np.arange(12, dtype=np.float64).reshape((3, 4));
# array([[ 0., 1., 2., 3.],
# [ 4., 5., 6., 7.],
# [ 8., 9., 10., 11.]])
dst_ndarray = ndarray[-2:, 1::2]
# array([[ 5., 7.],
# [ 9., 11.]])
assert dst_ndarray.shape == (2, 2)
assert dst_ndarray.strides == (32, 16)
assert dst_ndarray[0, 0] == 5.0
assert dst_ndarray[0, 1] == 7.0
assert dst_ndarray[1, 0] == 9.0
assert dst_ndarray[1, 1] == 11.0
```
*/
BEGIN_TEST();
double in_data[12] = { 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0 };
int32_t in_itemsize = sizeof(double);
const int32_t in_ndims = 2;
int32_t in_shape[in_ndims] = { 3, 4 };
int32_t in_strides[in_ndims] = {};
NDArray<int32_t> ndarray = {
.data = (uint8_t*) in_data,
.itemsize = in_itemsize,
.ndims = in_ndims,
.shape = in_shape,
.strides = in_strides
};
ndarray.set_strides_by_shape();
// Destination ndarray
// As documented, ndims and shape & strides must be allocated and determined by the caller.
const int32_t dst_ndims = 2;
int32_t dst_shape[dst_ndims] = {999, 999}; // Empty values
int32_t dst_strides[dst_ndims] = {999, 999}; // Empty values
NDArray<int32_t> dst_ndarray = {
.data = nullptr,
.ndims = dst_ndims,
.shape = dst_shape,
.strides = dst_strides
};
// Create the slice in `ndarray[-2::, 1::2]`
UserSlice<int32_t> user_slice_1 = {
.start_defined = 1,
.start = -2,
.stop_defined = 0,
.step_defined = 0
};
UserSlice<int32_t> user_slice_2 = {
.start_defined = 1,
.start = 1,
.stop_defined = 0,
.step_defined = 1,
.step = 2
};
const int32_t num_ndslices = 2;
NDSlice ndslices[num_ndslices] = {
{ .type = INPUT_SLICE_TYPE_SLICE, .slice = (uint8_t*) &user_slice_1 },
{ .type = INPUT_SLICE_TYPE_SLICE, .slice = (uint8_t*) &user_slice_2 }
};
ndarray.slice(num_ndslices, ndslices, &dst_ndarray);
int32_t expected_shape[dst_ndims] = { 2, 2 };
int32_t expected_strides[dst_ndims] = { 32, 16 };
assert_arrays_match("shape", "%d", dst_ndims, expected_shape, dst_ndarray.shape);
assert_arrays_match("strides", "%d", dst_ndims, expected_strides, dst_ndarray.strides);
assert_values_match("dst_ndarray[0, 0]", "%f", 5.0, *((double *) dst_ndarray.get_pelement((int32_t[dst_ndims]) { 0, 0 })));
assert_values_match("dst_ndarray[0, 1]", "%f", 7.0, *((double *) dst_ndarray.get_pelement((int32_t[dst_ndims]) { 0, 1 })));
assert_values_match("dst_ndarray[1, 0]", "%f", 9.0, *((double *) dst_ndarray.get_pelement((int32_t[dst_ndims]) { 1, 0 })));
assert_values_match("dst_ndarray[1, 1]", "%f", 11.0, *((double *) dst_ndarray.get_pelement((int32_t[dst_ndims]) { 1, 1 })));
}
void test_ndslice_2() {
/*
```python
ndarray = np.arange(12, dtype=np.float64).reshape((3, 4))
# array([[ 0., 1., 2., 3.],
# [ 4., 5., 6., 7.],
# [ 8., 9., 10., 11.]])
dst_ndarray = ndarray[2, ::-2]
# array([11., 9.])
assert dst_ndarray.shape == (2,)
assert dst_ndarray.strides == (-16,)
assert dst_ndarray[0] == 11.0
assert dst_ndarray[1] == 9.0
dst_ndarray[1, 0] == 99 # If you write to `dst_ndarray`
assert ndarray[1, 3] == 99 # `ndarray` also updates!!
```
*/
BEGIN_TEST();
double in_data[12] = { 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0 };
int32_t in_itemsize = sizeof(double);
const int32_t in_ndims = 2;
int32_t in_shape[in_ndims] = { 3, 4 };
int32_t in_strides[in_ndims] = {};
NDArray<int32_t> ndarray = {
.data = (uint8_t*) in_data,
.itemsize = in_itemsize,
.ndims = in_ndims,
.shape = in_shape,
.strides = in_strides
};
ndarray.set_strides_by_shape();
// Destination ndarray
// As documented, ndims and shape & strides must be allocated and determined by the caller.
const int32_t dst_ndims = 1;
int32_t dst_shape[dst_ndims] = {999}; // Empty values
int32_t dst_strides[dst_ndims] = {999}; // Empty values
NDArray<int32_t> dst_ndarray = {
.data = nullptr,
.ndims = dst_ndims,
.shape = dst_shape,
.strides = dst_strides
};
// Create the slice in `ndarray[2, ::-2]`
int32_t user_slice_1 = 2;
UserSlice<int32_t> user_slice_2 = {
.start_defined = 0,
.stop_defined = 0,
.step_defined = 1,
.step = -2
};
const int32_t num_ndslices = 2;
NDSlice ndslices[num_ndslices] = {
{ .type = INPUT_SLICE_TYPE_INDEX, .slice = (uint8_t*) &user_slice_1 },
{ .type = INPUT_SLICE_TYPE_SLICE, .slice = (uint8_t*) &user_slice_2 }
};
ndarray.slice(num_ndslices, ndslices, &dst_ndarray);
int32_t expected_shape[dst_ndims] = { 2 };
int32_t expected_strides[dst_ndims] = { -16 };
assert_arrays_match("shape", "%d", dst_ndims, expected_shape, dst_ndarray.shape);
assert_arrays_match("strides", "%d", dst_ndims, expected_strides, dst_ndarray.strides);
// [5.0, 3.0]
assert_values_match("dst_ndarray[0]", "%f", 11.0, *((double *) dst_ndarray.get_pelement((int32_t[dst_ndims]) { 0 })));
assert_values_match("dst_ndarray[1]", "%f", 9.0, *((double *) dst_ndarray.get_pelement((int32_t[dst_ndims]) { 1 })));
}
void test_can_broadcast_shape() {
BEGIN_TEST();
assert_values_match(
"can_broadcast_shape_to([3], [1, 1, 1, 1, 3]) == true",
"%d",
true,
ndarray_util::can_broadcast_shape_to(1, (int32_t[]) { 3 }, 5, (int32_t[]) { 1, 1, 1, 1, 3 })
);
assert_values_match(
"can_broadcast_shape_to([3], [3, 1]) == false",
"%d",
false,
ndarray_util::can_broadcast_shape_to(1, (int32_t[]) { 3 }, 2, (int32_t[]) { 3, 1 }));
assert_values_match(
"can_broadcast_shape_to([3], [3]) == true",
"%d",
true,
ndarray_util::can_broadcast_shape_to(1, (int32_t[]) { 3 }, 1, (int32_t[]) { 3 }));
assert_values_match(
"can_broadcast_shape_to([1], [3]) == false",
"%d",
false,
ndarray_util::can_broadcast_shape_to(1, (int32_t[]) { 1 }, 1, (int32_t[]) { 3 }));
assert_values_match(
"can_broadcast_shape_to([1], [1]) == true",
"%d",
true,
ndarray_util::can_broadcast_shape_to(1, (int32_t[]) { 1 }, 1, (int32_t[]) { 1 }));
assert_values_match(
"can_broadcast_shape_to([256, 256, 3], [256, 1, 3]) == true",
"%d",
true,
ndarray_util::can_broadcast_shape_to(3, (int32_t[]) { 256, 256, 3 }, 3, (int32_t[]) { 256, 1, 3 })
);
assert_values_match(
"can_broadcast_shape_to([256, 256, 3], [3]) == true",
"%d",
true,
ndarray_util::can_broadcast_shape_to(3, (int32_t[]) { 256, 256, 3 }, 1, (int32_t[]) { 3 })
);
assert_values_match(
"can_broadcast_shape_to([256, 256, 3], [2]) == false",
"%d",
false,
ndarray_util::can_broadcast_shape_to(3, (int32_t[]) { 256, 256, 3 }, 1, (int32_t[]) { 2 })
);
assert_values_match(
"can_broadcast_shape_to([256, 256, 3], [1]) == true",
"%d",
true,
ndarray_util::can_broadcast_shape_to(3, (int32_t[]) { 256, 256, 3 }, 1, (int32_t[]) { 1 })
);
// In cases when the shapes contain zero(es)
assert_values_match(
"can_broadcast_shape_to([0], [1]) == true",
"%d",
true,
ndarray_util::can_broadcast_shape_to(1, (int32_t[]) { 0 }, 1, (int32_t[]) { 1 })
);
assert_values_match(
"can_broadcast_shape_to([0], [2]) == false",
"%d",
false,
ndarray_util::can_broadcast_shape_to(1, (int32_t[]) { 0 }, 1, (int32_t[]) { 2 })
);
assert_values_match(
"can_broadcast_shape_to([0, 4, 0, 0], [1]) == true",
"%d",
true,
ndarray_util::can_broadcast_shape_to(4, (int32_t[]) { 0, 4, 0, 0 }, 1, (int32_t[]) { 1 })
);
assert_values_match(
"can_broadcast_shape_to([0, 4, 0, 0], [1, 1, 1, 1]) == true",
"%d",
true,
ndarray_util::can_broadcast_shape_to(4, (int32_t[]) { 0, 4, 0, 0 }, 4, (int32_t[]) { 1, 1, 1, 1 })
);
assert_values_match(
"can_broadcast_shape_to([0, 4, 0, 0], [1, 4, 1, 1]) == true",
"%d",
true,
ndarray_util::can_broadcast_shape_to(4, (int32_t[]) { 0, 4, 0, 0 }, 4, (int32_t[]) { 1, 4, 1, 1 })
);
assert_values_match(
"can_broadcast_shape_to([4, 3], [0, 3]) == false",
"%d",
false,
ndarray_util::can_broadcast_shape_to(2, (int32_t[]) { 4, 3 }, 2, (int32_t[]) { 0, 3 })
);
assert_values_match(
"can_broadcast_shape_to([4, 3], [0, 0]) == false",
"%d",
false,
ndarray_util::can_broadcast_shape_to(2, (int32_t[]) { 4, 3 }, 2, (int32_t[]) { 0, 0 })
);
}
void test_ndarray_broadcast_1() {
/*
# array = np.array([[19.9, 29.9, 39.9, 49.9]], dtype=np.float64)
# >>> [[19.9 29.9 39.9 49.9]]
#
# array = np.broadcast_to(array, (2, 3, 4))
# >>> [[[19.9 29.9 39.9 49.9]
# >>> [19.9 29.9 39.9 49.9]
# >>> [19.9 29.9 39.9 49.9]]
# >>> [[19.9 29.9 39.9 49.9]
# >>> [19.9 29.9 39.9 49.9]
# >>> [19.9 29.9 39.9 49.9]]]
#
# assery array.strides == (0, 0, 8)
*/
BEGIN_TEST();
double in_data[4] = { 19.9, 29.9, 39.9, 49.9 };
const int32_t in_ndims = 2;
int32_t in_shape[in_ndims] = {1, 4};
int32_t in_strides[in_ndims] = {};
NDArray<int32_t> ndarray = {
.data = (uint8_t*) in_data,
.itemsize = sizeof(double),
.ndims = in_ndims,
.shape = in_shape,
.strides = in_strides
};
ndarray.set_strides_by_shape();
const int32_t dst_ndims = 3;
int32_t dst_shape[dst_ndims] = {2, 3, 4};
int32_t dst_strides[dst_ndims] = {};
NDArray<int32_t> dst_ndarray = {
.ndims = dst_ndims,
.shape = dst_shape,
.strides = dst_strides
};
ndarray.broadcast_to(&dst_ndarray);
assert_arrays_match("dst_ndarray->strides", "%d", dst_ndims, (int32_t[]) { 0, 0, 8 }, dst_ndarray.strides);
assert_values_match("dst_ndarray[0, 0, 0]", "%f", 19.9, *((double*) dst_ndarray.get_pelement((int32_t[]) {0, 0, 0})));
assert_values_match("dst_ndarray[0, 0, 1]", "%f", 29.9, *((double*) dst_ndarray.get_pelement((int32_t[]) {0, 0, 1})));
assert_values_match("dst_ndarray[0, 0, 2]", "%f", 39.9, *((double*) dst_ndarray.get_pelement((int32_t[]) {0, 0, 2})));
assert_values_match("dst_ndarray[0, 0, 3]", "%f", 49.9, *((double*) dst_ndarray.get_pelement((int32_t[]) {0, 0, 3})));
assert_values_match("dst_ndarray[0, 1, 0]", "%f", 19.9, *((double*) dst_ndarray.get_pelement((int32_t[]) {0, 1, 0})));
assert_values_match("dst_ndarray[0, 1, 1]", "%f", 29.9, *((double*) dst_ndarray.get_pelement((int32_t[]) {0, 1, 1})));
assert_values_match("dst_ndarray[0, 1, 2]", "%f", 39.9, *((double*) dst_ndarray.get_pelement((int32_t[]) {0, 1, 2})));
assert_values_match("dst_ndarray[0, 1, 3]", "%f", 49.9, *((double*) dst_ndarray.get_pelement((int32_t[]) {0, 1, 3})));
assert_values_match("dst_ndarray[1, 2, 3]", "%f", 49.9, *((double*) dst_ndarray.get_pelement((int32_t[]) {1, 2, 3})));
}
void test_assign_with() {
/*
```
xs = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]], dtype=np.float64)
ys = xs.shape
```
*/
}
int main() {
test_calc_size_from_shape_normal();
test_calc_size_from_shape_has_zero();
test_set_strides_by_shape();
test_ndarray_indices_iter_normal();
test_ndarray_fill_generic();
test_ndarray_set_to_eye();
test_slice_1();
test_slice_2();
test_slice_3();
test_slice_4();
test_ndslice_1();
test_ndslice_2();
test_can_broadcast_shape();
test_ndarray_broadcast_1();
test_assign_with();
return 0;
}

View File

@ -1,14 +0,0 @@
#pragma once
// This is made toggleable since `irrt_test.cpp` itself would include
// headers that define the `int_t` family.
#ifndef IRRT_DONT_TYPEDEF_INTS
typedef _BitInt(8) int8_t;
typedef unsigned _BitInt(8) uint8_t;
typedef _BitInt(32) int32_t;
typedef unsigned _BitInt(32) uint32_t;
typedef _BitInt(64) int64_t;
typedef unsigned _BitInt(64) uint64_t;
#endif
typedef int32_t SliceIndex;

View File

@ -1,37 +0,0 @@
#pragma once
#include "irrt_typedefs.hpp"
namespace {
template <typename T>
T max(T a, T b) {
return a > b ? a : b;
}
template <typename T>
T min(T a, T b) {
return a > b ? b : a;
}
template <typename T>
bool arrays_match(int len, T *as, T *bs) {
for (int i = 0; i < len; i++) {
if (as[i] != bs[i]) return false;
}
return true;
}
void irrt_panic() {
// Crash the program for now.
// TODO: Don't crash the program
// ... or at least produce a good message when doing testing IRRT
uint8_t* death = nullptr;
*death = 0; // TODO: address 0 on hardware might be writable?
}
// TODO: Make this a macro and allow it to be toggled on/off (e.g., debug vs release)
void irrt_assert(bool condition) {
if (!condition) irrt_panic();
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -3,13 +3,10 @@ use crate::{
toplevel::DefinitionId, toplevel::DefinitionId,
typecheck::{ typecheck::{
type_inferencer::PrimitiveStore, type_inferencer::PrimitiveStore,
typedef::{ typedef::{FunSignature, FuncArg, Type, TypeEnum, Unifier},
into_var_map, FunSignature, FuncArg, Type, TypeEnum, TypeVar, TypeVarId, Unifier,
},
}, },
}; };
use indexmap::IndexMap;
use nac3parser::ast::StrRef; use nac3parser::ast::StrRef;
use std::collections::HashMap; use std::collections::HashMap;
@ -47,10 +44,13 @@ pub enum ConcreteTypeEnum {
TTuple { TTuple {
ty: Vec<ConcreteType>, ty: Vec<ConcreteType>,
}, },
TList {
ty: ConcreteType,
},
TObj { TObj {
obj_id: DefinitionId, obj_id: DefinitionId,
fields: HashMap<StrRef, (ConcreteType, bool)>, fields: HashMap<StrRef, (ConcreteType, bool)>,
params: IndexMap<TypeVarId, ConcreteType>, params: HashMap<u32, ConcreteType>,
}, },
TVirtual { TVirtual {
ty: ConcreteType, ty: ConcreteType,
@ -58,15 +58,11 @@ pub enum ConcreteTypeEnum {
TFunc { TFunc {
args: Vec<ConcreteFuncArg>, args: Vec<ConcreteFuncArg>,
ret: ConcreteType, ret: ConcreteType,
vars: HashMap<TypeVarId, ConcreteType>, vars: HashMap<u32, ConcreteType>,
},
TLiteral {
values: Vec<SymbolValue>,
}, },
} }
impl ConcreteTypeStore { impl ConcreteTypeStore {
#[must_use]
pub fn new() -> ConcreteTypeStore { pub fn new() -> ConcreteTypeStore {
ConcreteTypeStore { ConcreteTypeStore {
store: vec![ store: vec![
@ -84,7 +80,6 @@ impl ConcreteTypeStore {
} }
} }
#[must_use]
pub fn get(&self, cty: ConcreteType) -> &ConcreteTypeEnum { pub fn get(&self, cty: ConcreteType) -> &ConcreteTypeEnum {
&self.store[cty.0] &self.store[cty.0]
} }
@ -164,6 +159,9 @@ impl ConcreteTypeStore {
.map(|t| self.from_unifier_type(unifier, primitives, *t, cache)) .map(|t| self.from_unifier_type(unifier, primitives, *t, cache))
.collect(), .collect(),
}, },
TypeEnum::TList { ty } => ConcreteTypeEnum::TList {
ty: self.from_unifier_type(unifier, primitives, *ty, cache),
},
TypeEnum::TObj { obj_id, fields, params } => ConcreteTypeEnum::TObj { TypeEnum::TObj { obj_id, fields, params } => ConcreteTypeEnum::TObj {
obj_id: *obj_id, obj_id: *obj_id,
fields: fields fields: fields
@ -196,12 +194,9 @@ impl ConcreteTypeStore {
ty: self.from_unifier_type(unifier, primitives, *ty, cache), ty: self.from_unifier_type(unifier, primitives, *ty, cache),
}, },
TypeEnum::TFunc(signature) => { TypeEnum::TFunc(signature) => {
self.from_signature(unifier, primitives, signature, cache) self.from_signature(unifier, primitives, &*signature, cache)
} }
TypeEnum::TLiteral { values, .. } => { _ => unreachable!(),
ConcreteTypeEnum::TLiteral { values: values.clone() }
}
_ => unreachable!("{:?}", ty_enum.get_type_name()),
}; };
let index = if let Some(ConcreteType(index)) = cache.get(&ty).unwrap() { let index = if let Some(ConcreteType(index)) = cache.get(&ty).unwrap() {
self.store[*index] = result; self.store[*index] = result;
@ -226,7 +221,7 @@ impl ConcreteTypeStore {
return if let Some(ty) = ty { return if let Some(ty) = ty {
*ty *ty
} else { } else {
*ty = Some(unifier.get_dummy_var().ty); *ty = Some(unifier.get_dummy_var().0);
ty.unwrap() ty.unwrap()
}; };
} }
@ -254,6 +249,9 @@ impl ConcreteTypeStore {
.map(|cty| self.to_unifier_type(unifier, primitives, *cty, cache)) .map(|cty| self.to_unifier_type(unifier, primitives, *cty, cache))
.collect(), .collect(),
}, },
ConcreteTypeEnum::TList { ty } => {
TypeEnum::TList { ty: self.to_unifier_type(unifier, primitives, *ty, cache) }
}
ConcreteTypeEnum::TVirtual { ty } => { ConcreteTypeEnum::TVirtual { ty } => {
TypeEnum::TVirtual { ty: self.to_unifier_type(unifier, primitives, *ty, cache) } TypeEnum::TVirtual { ty: self.to_unifier_type(unifier, primitives, *ty, cache) }
} }
@ -265,10 +263,10 @@ impl ConcreteTypeStore {
(*name, (self.to_unifier_type(unifier, primitives, cty.0, cache), cty.1)) (*name, (self.to_unifier_type(unifier, primitives, cty.0, cache), cty.1))
}) })
.collect::<HashMap<_, _>>(), .collect::<HashMap<_, _>>(),
params: into_var_map(params.iter().map(|(&id, cty)| { params: params
let ty = self.to_unifier_type(unifier, primitives, *cty, cache); .iter()
TypeVar { id, ty } .map(|(id, cty)| (*id, self.to_unifier_type(unifier, primitives, *cty, cache)))
})), .collect::<HashMap<_, _>>(),
}, },
ConcreteTypeEnum::TFunc { args, ret, vars } => TypeEnum::TFunc(FunSignature { ConcreteTypeEnum::TFunc { args, ret, vars } => TypeEnum::TFunc(FunSignature {
args: args args: args
@ -280,14 +278,11 @@ impl ConcreteTypeStore {
}) })
.collect(), .collect(),
ret: self.to_unifier_type(unifier, primitives, *ret, cache), ret: self.to_unifier_type(unifier, primitives, *ret, cache),
vars: into_var_map(vars.iter().map(|(&id, cty)| { vars: vars
let ty = self.to_unifier_type(unifier, primitives, *cty, cache); .iter()
TypeVar { id, ty } .map(|(id, cty)| (*id, self.to_unifier_type(unifier, primitives, *cty, cache)))
})), .collect::<HashMap<_, _>>(),
}), }),
ConcreteTypeEnum::TLiteral { values, .. } => {
TypeEnum::TLiteral { values: values.clone(), loc: None }
}
}; };
let result = unifier.add_ty(result); let result = unifier.add_ty(result);
if let Some(ty) = cache.get(&cty).unwrap() { if let Some(ty) = cache.get(&cty).unwrap() {

File diff suppressed because it is too large Load Diff

View File

@ -1,132 +0,0 @@
use inkwell::attributes::{Attribute, AttributeLoc};
use inkwell::values::{BasicValueEnum, CallSiteValue, FloatValue, IntValue};
use itertools::Either;
use crate::codegen::CodeGenContext;
/// Macro to generate extern function
/// Both function return type and function parameter type are `FloatValue`
///
/// Arguments:
/// * `unary/binary`: Whether the extern function requires one (unary) or two (binary) operands
/// * `$fn_name:ident`: The identifier of the rust function to be generated
/// * `$extern_fn:literal`: Name of underlying extern function
///
/// Optional Arguments:
/// * `$(,$attributes:literal)*)`: Attributes linked with the extern function
/// The default attributes are "mustprogress", "nofree", "nounwind", "willreturn", and "writeonly"
/// These will be used unless other attributes are specified
/// * `$(,$args:ident)*`: Operands of the extern function
/// The data type of these operands will be set to `FloatValue`
///
macro_rules! generate_extern_fn {
("unary", $fn_name:ident, $extern_fn:literal) => {
generate_extern_fn!($fn_name, $extern_fn, arg, "mustprogress", "nofree", "nounwind", "willreturn", "writeonly");
};
("unary", $fn_name:ident, $extern_fn:literal $(,$attributes:literal)*) => {
generate_extern_fn!($fn_name, $extern_fn, arg $(,$attributes)*);
};
("binary", $fn_name:ident, $extern_fn:literal) => {
generate_extern_fn!($fn_name, $extern_fn, arg1, arg2, "mustprogress", "nofree", "nounwind", "willreturn", "writeonly");
};
("binary", $fn_name:ident, $extern_fn:literal $(,$attributes:literal)*) => {
generate_extern_fn!($fn_name, $extern_fn, arg1, arg2 $(,$attributes)*);
};
($fn_name:ident, $extern_fn:literal $(,$args:ident)* $(,$attributes:literal)*) => {
#[doc = concat!("Invokes the [`", stringify!($extern_fn), "`](https://en.cppreference.com/w/c/numeric/math/", stringify!($llvm_name), ") function." )]
pub fn $fn_name<'ctx>(
ctx: &CodeGenContext<'ctx, '_>
$(,$args: FloatValue<'ctx>)*,
name: Option<&str>,
) -> FloatValue<'ctx> {
const FN_NAME: &str = $extern_fn;
let llvm_f64 = ctx.ctx.f64_type();
$(debug_assert_eq!($args.get_type(), llvm_f64);)*
let extern_fn = ctx.module.get_function(FN_NAME).unwrap_or_else(|| {
let fn_type = llvm_f64.fn_type(&[$($args.get_type().into()),*], false);
let func = ctx.module.add_function(FN_NAME, fn_type, None);
for attr in [$($attributes),*] {
func.add_attribute(
AttributeLoc::Function,
ctx.ctx.create_enum_attribute(Attribute::get_named_enum_kind_id(attr), 0),
);
}
func
});
ctx.builder
.build_call(extern_fn, &[$($args.into()),*], name.unwrap_or_default())
.map(CallSiteValue::try_as_basic_value)
.map(|v| v.map_left(BasicValueEnum::into_float_value))
.map(Either::unwrap_left)
.unwrap()
}
};
}
generate_extern_fn!("unary", call_tan, "tan");
generate_extern_fn!("unary", call_asin, "asin");
generate_extern_fn!("unary", call_acos, "acos");
generate_extern_fn!("unary", call_atan, "atan");
generate_extern_fn!("unary", call_sinh, "sinh");
generate_extern_fn!("unary", call_cosh, "cosh");
generate_extern_fn!("unary", call_tanh, "tanh");
generate_extern_fn!("unary", call_asinh, "asinh");
generate_extern_fn!("unary", call_acosh, "acosh");
generate_extern_fn!("unary", call_atanh, "atanh");
generate_extern_fn!("unary", call_expm1, "expm1");
generate_extern_fn!(
"unary",
call_cbrt,
"cbrt",
"mustprogress",
"nofree",
"nosync",
"nounwind",
"readonly",
"willreturn"
);
generate_extern_fn!("unary", call_erf, "erf", "nounwind");
generate_extern_fn!("unary", call_erfc, "erfc", "nounwind");
generate_extern_fn!("unary", call_j1, "j1", "nounwind");
generate_extern_fn!("binary", call_atan2, "atan2");
generate_extern_fn!("binary", call_hypot, "hypot", "nounwind");
generate_extern_fn!("binary", call_nextafter, "nextafter", "nounwind");
/// Invokes the [`ldexp`](https://en.cppreference.com/w/c/numeric/math/ldexp) function.
pub fn call_ldexp<'ctx>(
ctx: &CodeGenContext<'ctx, '_>,
arg: FloatValue<'ctx>,
exp: IntValue<'ctx>,
name: Option<&str>,
) -> FloatValue<'ctx> {
const FN_NAME: &str = "ldexp";
let llvm_f64 = ctx.ctx.f64_type();
let llvm_i32 = ctx.ctx.i32_type();
debug_assert_eq!(arg.get_type(), llvm_f64);
debug_assert_eq!(exp.get_type(), llvm_i32);
let extern_fn = ctx.module.get_function(FN_NAME).unwrap_or_else(|| {
let fn_type = llvm_f64.fn_type(&[llvm_f64.into(), llvm_i32.into()], false);
let func = ctx.module.add_function(FN_NAME, fn_type, None);
for attr in ["mustprogress", "nofree", "nounwind", "willreturn"] {
func.add_attribute(
AttributeLoc::Function,
ctx.ctx.create_enum_attribute(Attribute::get_named_enum_kind_id(attr), 0),
);
}
func
});
ctx.builder
.build_call(extern_fn, &[arg.into(), exp.into()], name.unwrap_or_default())
.map(CallSiteValue::try_as_basic_value)
.map(|v| v.map_left(BasicValueEnum::into_float_value))
.map(Either::unwrap_left)
.unwrap()
}

View File

@ -1,5 +1,5 @@
use crate::{ use crate::{
codegen::{bool_to_i1, bool_to_i8, classes::ArraySliceValue, expr::*, stmt::*, CodeGenContext}, codegen::{expr::*, stmt::*, CodeGenContext},
symbol_resolver::ValueEnum, symbol_resolver::ValueEnum,
toplevel::{DefinitionId, TopLevelDef}, toplevel::{DefinitionId, TopLevelDef},
typecheck::typedef::{FunSignature, Type}, typecheck::typedef::{FunSignature, Type},
@ -7,7 +7,7 @@ use crate::{
use inkwell::{ use inkwell::{
context::Context, context::Context,
types::{BasicTypeEnum, IntType}, types::{BasicTypeEnum, IntType},
values::{BasicValueEnum, IntValue, PointerValue}, values::{BasicValueEnum, PointerValue},
}; };
use nac3parser::ast::{Expr, Stmt, StrRef}; use nac3parser::ast::{Expr, Stmt, StrRef};
@ -22,9 +22,9 @@ pub trait CodeGenerator {
/// - fun: Function signature and definition ID. /// - fun: Function signature and definition ID.
/// - params: Function parameters. Note that this does not include the object even if the /// - params: Function parameters. Note that this does not include the object even if the
/// function is a class method. /// function is a class method.
fn gen_call<'ctx>( fn gen_call<'ctx, 'a>(
&mut self, &mut self,
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, 'a>,
obj: Option<(Type, ValueEnum<'ctx>)>, obj: Option<(Type, ValueEnum<'ctx>)>,
fun: (&FunSignature, DefinitionId), fun: (&FunSignature, DefinitionId),
params: Vec<(Option<StrRef>, ValueEnum<'ctx>)>, params: Vec<(Option<StrRef>, ValueEnum<'ctx>)>,
@ -39,9 +39,9 @@ pub trait CodeGenerator {
/// - signature: Function signature of the constructor. /// - signature: Function signature of the constructor.
/// - def: Class definition for the constructor class. /// - def: Class definition for the constructor class.
/// - params: Function parameters. /// - params: Function parameters.
fn gen_constructor<'ctx>( fn gen_constructor<'ctx, 'a>(
&mut self, &mut self,
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, 'a>,
signature: &FunSignature, signature: &FunSignature,
def: &TopLevelDef, def: &TopLevelDef,
params: Vec<(Option<StrRef>, ValueEnum<'ctx>)>, params: Vec<(Option<StrRef>, ValueEnum<'ctx>)>,
@ -59,20 +59,20 @@ pub trait CodeGenerator {
/// function is a class method. /// function is a class method.
/// Note that this function should check if the function is generated in another thread (due to /// Note that this function should check if the function is generated in another thread (due to
/// possible race condition), see the default implementation for an example. /// possible race condition), see the default implementation for an example.
fn gen_func_instance<'ctx>( fn gen_func_instance<'ctx, 'a>(
&mut self, &mut self,
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, 'a>,
obj: Option<(Type, ValueEnum<'ctx>)>, obj: Option<(Type, ValueEnum<'ctx>)>,
fun: (&FunSignature, &mut TopLevelDef, String), fun: (&FunSignature, &mut TopLevelDef, String),
id: usize, id: usize,
) -> Result<String, String> { ) -> Result<String, String> {
gen_func_instance(ctx, &obj, fun, id) gen_func_instance(ctx, obj, fun, id)
} }
/// Generate the code for an expression. /// Generate the code for an expression.
fn gen_expr<'ctx>( fn gen_expr<'ctx, 'a>(
&mut self, &mut self,
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, 'a>,
expr: &Expr<Option<Type>>, expr: &Expr<Option<Type>>,
) -> Result<Option<ValueEnum<'ctx>>, String> ) -> Result<Option<ValueEnum<'ctx>>, String>
where where
@ -83,44 +83,30 @@ pub trait CodeGenerator {
/// Allocate memory for a variable and return a pointer pointing to it. /// Allocate memory for a variable and return a pointer pointing to it.
/// The default implementation places the allocations at the start of the function. /// The default implementation places the allocations at the start of the function.
fn gen_var_alloc<'ctx>( fn gen_var_alloc<'ctx, 'a>(
&mut self, &mut self,
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, 'a>,
ty: BasicTypeEnum<'ctx>, ty: BasicTypeEnum<'ctx>,
name: Option<&str>,
) -> Result<PointerValue<'ctx>, String> { ) -> Result<PointerValue<'ctx>, String> {
gen_var(ctx, ty, name) gen_var(ctx, ty)
}
/// Allocate memory for a variable and return a pointer pointing to it.
/// The default implementation places the allocations at the start of the function.
fn gen_array_var_alloc<'ctx>(
&mut self,
ctx: &mut CodeGenContext<'ctx, '_>,
ty: BasicTypeEnum<'ctx>,
size: IntValue<'ctx>,
name: Option<&'ctx str>,
) -> Result<ArraySliceValue<'ctx>, String> {
gen_array_var(ctx, ty, size, name)
} }
/// Return a pointer pointing to the target of the expression. /// Return a pointer pointing to the target of the expression.
fn gen_store_target<'ctx>( fn gen_store_target<'ctx, 'a>(
&mut self, &mut self,
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, 'a>,
pattern: &Expr<Option<Type>>, pattern: &Expr<Option<Type>>,
name: Option<&str>, ) -> Result<PointerValue<'ctx>, String>
) -> Result<Option<PointerValue<'ctx>>, String>
where where
Self: Sized, Self: Sized,
{ {
gen_store_target(self, ctx, pattern, name) gen_store_target(self, ctx, pattern)
} }
/// Generate code for an assignment expression. /// Generate code for an assignment expression.
fn gen_assign<'ctx>( fn gen_assign<'ctx, 'a>(
&mut self, &mut self,
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, 'a>,
target: &Expr<Option<Type>>, target: &Expr<Option<Type>>,
value: ValueEnum<'ctx>, value: ValueEnum<'ctx>,
) -> Result<(), String> ) -> Result<(), String>
@ -132,9 +118,9 @@ pub trait CodeGenerator {
/// Generate code for a while expression. /// Generate code for a while expression.
/// Return true if the while loop must early return /// Return true if the while loop must early return
fn gen_while( fn gen_while<'ctx, 'a>(
&mut self, &mut self,
ctx: &mut CodeGenContext<'_, '_>, ctx: &mut CodeGenContext<'ctx, 'a>,
stmt: &Stmt<Option<Type>>, stmt: &Stmt<Option<Type>>,
) -> Result<(), String> ) -> Result<(), String>
where where
@ -143,11 +129,11 @@ pub trait CodeGenerator {
gen_while(self, ctx, stmt) gen_while(self, ctx, stmt)
} }
/// Generate code for a for expression. /// Generate code for a while expression.
/// Return true if the for loop must early return /// Return true if the while loop must early return
fn gen_for( fn gen_for<'ctx, 'a>(
&mut self, &mut self,
ctx: &mut CodeGenContext<'_, '_>, ctx: &mut CodeGenContext<'ctx, 'a>,
stmt: &Stmt<Option<Type>>, stmt: &Stmt<Option<Type>>,
) -> Result<(), String> ) -> Result<(), String>
where where
@ -158,9 +144,9 @@ pub trait CodeGenerator {
/// Generate code for an if expression. /// Generate code for an if expression.
/// Return true if the statement must early return /// Return true if the statement must early return
fn gen_if( fn gen_if<'ctx, 'a>(
&mut self, &mut self,
ctx: &mut CodeGenContext<'_, '_>, ctx: &mut CodeGenContext<'ctx, 'a>,
stmt: &Stmt<Option<Type>>, stmt: &Stmt<Option<Type>>,
) -> Result<(), String> ) -> Result<(), String>
where where
@ -169,9 +155,9 @@ pub trait CodeGenerator {
gen_if(self, ctx, stmt) gen_if(self, ctx, stmt)
} }
fn gen_with( fn gen_with<'ctx, 'a>(
&mut self, &mut self,
ctx: &mut CodeGenContext<'_, '_>, ctx: &mut CodeGenContext<'ctx, 'a>,
stmt: &Stmt<Option<Type>>, stmt: &Stmt<Option<Type>>,
) -> Result<(), String> ) -> Result<(), String>
where where
@ -181,11 +167,10 @@ pub trait CodeGenerator {
} }
/// Generate code for a statement /// Generate code for a statement
///
/// Return true if the statement must early return /// Return true if the statement must early return
fn gen_stmt( fn gen_stmt<'ctx, 'a>(
&mut self, &mut self,
ctx: &mut CodeGenContext<'_, '_>, ctx: &mut CodeGenContext<'ctx, 'a>,
stmt: &Stmt<Option<Type>>, stmt: &Stmt<Option<Type>>,
) -> Result<(), String> ) -> Result<(), String>
where where
@ -193,36 +178,6 @@ pub trait CodeGenerator {
{ {
gen_stmt(self, ctx, stmt) gen_stmt(self, ctx, stmt)
} }
/// Generates code for a block statement.
fn gen_block<'a, I: Iterator<Item = &'a Stmt<Option<Type>>>>(
&mut self,
ctx: &mut CodeGenContext<'_, '_>,
stmts: I,
) -> Result<(), String>
where
Self: Sized,
{
gen_block(self, ctx, stmts)
}
/// See [`bool_to_i1`].
fn bool_to_i1<'ctx>(
&self,
ctx: &CodeGenContext<'ctx, '_>,
bool_value: IntValue<'ctx>,
) -> IntValue<'ctx> {
bool_to_i1(&ctx.builder, bool_value)
}
/// See [`bool_to_i8`].
fn bool_to_i8<'ctx>(
&self,
ctx: &CodeGenContext<'ctx, '_>,
bool_value: IntValue<'ctx>,
) -> IntValue<'ctx> {
bool_to_i8(&ctx.builder, ctx.ctx, bool_value)
}
} }
pub struct DefaultCodeGenerator { pub struct DefaultCodeGenerator {
@ -231,20 +186,17 @@ pub struct DefaultCodeGenerator {
} }
impl DefaultCodeGenerator { impl DefaultCodeGenerator {
#[must_use]
pub fn new(name: String, size_t: u32) -> DefaultCodeGenerator { pub fn new(name: String, size_t: u32) -> DefaultCodeGenerator {
assert!(matches!(size_t, 32 | 64)); assert!(size_t == 32 || size_t == 64);
DefaultCodeGenerator { name, size_t } DefaultCodeGenerator { name, size_t }
} }
} }
impl CodeGenerator for DefaultCodeGenerator { impl CodeGenerator for DefaultCodeGenerator {
/// Returns the name for this [`CodeGenerator`].
fn get_name(&self) -> &str { fn get_name(&self) -> &str {
&self.name &self.name
} }
/// Returns an LLVM integer type representing `size_t`.
fn get_size_type<'ctx>(&self, ctx: &'ctx Context) -> IntType<'ctx> { fn get_size_type<'ctx>(&self, ctx: &'ctx Context) -> IntType<'ctx> {
// it should be unsigned, but we don't really need unsigned and this could save us from // it should be unsigned, but we don't really need unsigned and this could save us from
// having to do a bit cast... // having to do a bit cast...

View File

@ -0,0 +1,140 @@
typedef _ExtInt(8) int8_t;
typedef unsigned _ExtInt(8) uint8_t;
typedef _ExtInt(32) int32_t;
typedef unsigned _ExtInt(32) uint32_t;
typedef _ExtInt(64) int64_t;
typedef unsigned _ExtInt(64) uint64_t;
# define MAX(a, b) (a > b ? a : b)
# define MIN(a, b) (a > b ? b : a)
// adapted from GNU Scientific Library: https://git.savannah.gnu.org/cgit/gsl.git/tree/sys/pow_int.c
// need to make sure `exp >= 0` before calling this function
#define DEF_INT_EXP(T) T __nac3_int_exp_##T( \
T base, \
T exp \
) { \
T res = (T)1; \
/* repeated squaring method */ \
do { \
if (exp & 1) res *= base; /* for n odd */ \
exp >>= 1; \
base *= base; \
} while (exp); \
return res; \
} \
DEF_INT_EXP(int32_t)
DEF_INT_EXP(int64_t)
DEF_INT_EXP(uint32_t)
DEF_INT_EXP(uint64_t)
int32_t __nac3_slice_index_bound(int32_t i, const int32_t len) {
if (i < 0) {
i = len + i;
}
if (i < 0) {
return 0;
} else if (i > len) {
return len;
}
return i;
}
int32_t __nac3_range_slice_len(const int32_t start, const int32_t end, const int32_t step) {
int32_t diff = end - start;
if (diff > 0 && step > 0) {
return ((diff - 1) / step) + 1;
} else if (diff < 0 && step < 0) {
return ((diff + 1) / step) + 1;
} else {
return 0;
}
}
// Handle list assignment and dropping part of the list when
// both dest_step and src_step are +1.
// - All the index must *not* be out-of-bound or negative,
// - The end index is *inclusive*,
// - The length of src and dest slice size should already
// be checked: if dest.step == 1 then len(src) <= len(dest) else len(src) == len(dest)
int32_t __nac3_list_slice_assign_var_size(
int32_t dest_start,
int32_t dest_end,
int32_t dest_step,
uint8_t *dest_arr,
int32_t dest_arr_len,
int32_t src_start,
int32_t src_end,
int32_t src_step,
uint8_t *src_arr,
int32_t src_arr_len,
const int32_t size
) {
/* if dest_arr_len == 0, do nothing since we do not support extending list */
if (dest_arr_len == 0) return dest_arr_len;
/* if both step is 1, memmove directly, handle the dropping of the list, and shrink size */
if (src_step == dest_step && dest_step == 1) {
const int32_t src_len = (src_end >= src_start) ? (src_end - src_start + 1) : 0;
const int32_t dest_len = (dest_end >= dest_start) ? (dest_end - dest_start + 1) : 0;
if (src_len > 0) {
__builtin_memmove(
dest_arr + dest_start * size,
src_arr + src_start * size,
src_len * size
);
}
if (dest_len > 0) {
/* dropping */
__builtin_memmove(
dest_arr + (dest_start + src_len) * size,
dest_arr + (dest_end + 1) * size,
(dest_arr_len - dest_end - 1) * size
);
}
/* shrink size */
return dest_arr_len - (dest_len - src_len);
}
/* if two range overlaps, need alloca */
uint8_t need_alloca =
(dest_arr == src_arr)
&& !(
MAX(dest_start, dest_end) < MIN(src_start, src_end)
|| MAX(src_start, src_end) < MIN(dest_start, dest_end)
);
if (need_alloca) {
uint8_t *tmp = __builtin_alloca(src_arr_len * size);
__builtin_memcpy(tmp, src_arr, src_arr_len * size);
src_arr = tmp;
}
int32_t src_ind = src_start;
int32_t dest_ind = dest_start;
for (;
(src_step > 0) ? (src_ind <= src_end) : (src_ind >= src_end);
src_ind += src_step, dest_ind += dest_step
) {
/* for constant optimization */
if (size == 1) {
__builtin_memcpy(dest_arr + dest_ind, src_arr + src_ind, 1);
} else if (size == 4) {
__builtin_memcpy(dest_arr + dest_ind * 4, src_arr + src_ind * 4, 4);
} else if (size == 8) {
__builtin_memcpy(dest_arr + dest_ind * 8, src_arr + src_ind * 8, 8);
} else {
/* memcpy for var size, cannot overlap after previous alloca */
__builtin_memcpy(dest_arr + dest_ind * size, src_arr + src_ind * size, size);
}
}
/* only dest_step == 1 can we shrink the dest list. */
/* size should be ensured prior to calling this function */
if (dest_step == 1 && dest_end >= dest_start) {
__builtin_memmove(
dest_arr + dest_ind * size,
dest_arr + (dest_end + 1) * size,
(dest_arr_len - dest_end - 1) * size
);
return dest_arr_len - (dest_end - dest_ind) - 1;
}
return dest_arr_len;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,26 +0,0 @@
#[cfg(test)]
mod tests {
use std::{path::Path, process::Command};
#[test]
fn run_irrt_test() {
assert!(
cfg!(feature = "test"),
"Please do `cargo test -F test` to compile `irrt_test.out` and run test"
);
let irrt_test_out_path = Path::new(concat!(env!("OUT_DIR"), "/irrt_test.out"));
let output = Command::new(irrt_test_out_path.to_str().unwrap()).output().unwrap();
if !output.status.success() {
eprintln!("irrt_test failed with status {}:", output.status);
eprintln!("====== stdout ======");
eprintln!("{}", String::from_utf8(output.stdout).unwrap());
eprintln!("====== stderr ======");
eprintln!("{}", String::from_utf8(output.stderr).unwrap());
eprintln!("====================");
panic!("irrt_test failed");
}
}
}

View File

@ -1,308 +0,0 @@
use crate::codegen::CodeGenContext;
use inkwell::context::Context;
use inkwell::intrinsics::Intrinsic;
use inkwell::types::AnyTypeEnum::IntType;
use inkwell::types::FloatType;
use inkwell::values::{BasicValueEnum, CallSiteValue, FloatValue, IntValue, PointerValue};
use inkwell::AddressSpace;
use itertools::Either;
/// Returns the string representation for the floating-point type `ft` when used in intrinsic
/// functions.
fn get_float_intrinsic_repr(ctx: &Context, ft: FloatType) -> &'static str {
// Standard LLVM floating-point types
if ft == ctx.f16_type() {
return "f16";
}
if ft == ctx.f32_type() {
return "f32";
}
if ft == ctx.f64_type() {
return "f64";
}
if ft == ctx.f128_type() {
return "f128";
}
// Non-standard floating-point types
if ft == ctx.x86_f80_type() {
return "f80";
}
if ft == ctx.ppc_f128_type() {
return "ppcf128";
}
unreachable!()
}
/// Invokes the [`llvm.stacksave`](https://llvm.org/docs/LangRef.html#llvm-stacksave-intrinsic)
/// intrinsic.
pub fn call_stacksave<'ctx>(
ctx: &CodeGenContext<'ctx, '_>,
name: Option<&str>,
) -> PointerValue<'ctx> {
const FN_NAME: &str = "llvm.stacksave";
let intrinsic_fn = Intrinsic::find(FN_NAME)
.and_then(|intrinsic| intrinsic.get_declaration(&ctx.module, &[]))
.unwrap();
ctx.builder
.build_call(intrinsic_fn, &[], name.unwrap_or_default())
.map(CallSiteValue::try_as_basic_value)
.map(|v| v.map_left(BasicValueEnum::into_pointer_value))
.map(Either::unwrap_left)
.unwrap()
}
/// Invokes the
/// [`llvm.stackrestore`](https://llvm.org/docs/LangRef.html#llvm-stackrestore-intrinsic) intrinsic.
///
/// - `ptr`: The pointer storing the address to restore the stack to.
pub fn call_stackrestore<'ctx>(ctx: &CodeGenContext<'ctx, '_>, ptr: PointerValue<'ctx>) {
const FN_NAME: &str = "llvm.stackrestore";
/*
SEE https://github.com/TheDan64/inkwell/issues/496
We want `llvm.stackrestore`, but the following would generate `llvm.stackrestore.p0i8`.
```ignore
let intrinsic_fn = Intrinsic::find(FN_NAME)
.and_then(|intrinsic| intrinsic.get_declaration(&ctx.module, &[llvm_p0i8.into()]))
.unwrap();
```
Temp workaround by manually declaring the intrinsic with the correct function name instead.
*/
let intrinsic_fn = ctx.module.get_function(FN_NAME).unwrap_or_else(|| {
let llvm_void = ctx.ctx.void_type();
let llvm_i8 = ctx.ctx.i8_type();
let llvm_p0i8 = llvm_i8.ptr_type(AddressSpace::default());
let fn_type = llvm_void.fn_type(&[llvm_p0i8.into()], false);
ctx.module.add_function(FN_NAME, fn_type, None)
});
ctx.builder.build_call(intrinsic_fn, &[ptr.into()], "").unwrap();
}
/// Invokes the [`llvm.memcpy`](https://llvm.org/docs/LangRef.html#llvm-memcpy-intrinsic) intrinsic.
///
/// * `dest` - The pointer to the destination. Must be a pointer to an integer type.
/// * `src` - The pointer to the source. Must be a pointer to an integer type.
/// * `len` - The number of bytes to copy.
/// * `is_volatile` - Whether the `memcpy` operation should be `volatile`.
pub fn call_memcpy<'ctx>(
ctx: &CodeGenContext<'ctx, '_>,
dest: PointerValue<'ctx>,
src: PointerValue<'ctx>,
len: IntValue<'ctx>,
is_volatile: IntValue<'ctx>,
) {
const FN_NAME: &str = "llvm.memcpy";
debug_assert!(dest.get_type().get_element_type().is_int_type());
debug_assert!(src.get_type().get_element_type().is_int_type());
debug_assert_eq!(
dest.get_type().get_element_type().into_int_type().get_bit_width(),
src.get_type().get_element_type().into_int_type().get_bit_width(),
);
debug_assert!(matches!(len.get_type().get_bit_width(), 32 | 64));
debug_assert_eq!(is_volatile.get_type().get_bit_width(), 1);
let llvm_dest_t = dest.get_type();
let llvm_src_t = src.get_type();
let llvm_len_t = len.get_type();
let intrinsic_fn = Intrinsic::find(FN_NAME)
.and_then(|intrinsic| {
intrinsic.get_declaration(
&ctx.module,
&[llvm_dest_t.into(), llvm_src_t.into(), llvm_len_t.into()],
)
})
.unwrap();
ctx.builder
.build_call(intrinsic_fn, &[dest.into(), src.into(), len.into(), is_volatile.into()], "")
.unwrap();
}
/// Invokes the `llvm.memcpy` intrinsic.
///
/// Unlike [`call_memcpy`], this function accepts any type of pointer value. If `dest` or `src` is
/// not a pointer to an integer, the pointer(s) will be cast to `i8*` before invoking `memcpy`.
pub fn call_memcpy_generic<'ctx>(
ctx: &CodeGenContext<'ctx, '_>,
dest: PointerValue<'ctx>,
src: PointerValue<'ctx>,
len: IntValue<'ctx>,
is_volatile: IntValue<'ctx>,
) {
let llvm_i8 = ctx.ctx.i8_type();
let llvm_p0i8 = llvm_i8.ptr_type(AddressSpace::default());
let dest_elem_t = dest.get_type().get_element_type();
let src_elem_t = src.get_type().get_element_type();
let dest = if matches!(dest_elem_t, IntType(t) if t.get_bit_width() == 8) {
dest
} else {
ctx.builder
.build_bitcast(dest, llvm_p0i8, "")
.map(BasicValueEnum::into_pointer_value)
.unwrap()
};
let src = if matches!(src_elem_t, IntType(t) if t.get_bit_width() == 8) {
src
} else {
ctx.builder
.build_bitcast(src, llvm_p0i8, "")
.map(BasicValueEnum::into_pointer_value)
.unwrap()
};
call_memcpy(ctx, dest, src, len, is_volatile);
}
/// Macro to find and generate build call for llvm intrinsic (body of llvm intrinsic function)
///
/// Arguments:
/// * `$ctx:ident`: Reference to the current Code Generation Context
/// * `$name:ident`: Optional name to be assigned to the llvm build call (Option<&str>)
/// * `$llvm_name:literal`: Name of underlying llvm intrinsic function
/// * `$map_fn:ident`: Mapping function to be applied on `BasicValue` (`BasicValue` -> Function Return Type)
/// Use `BasicValueEnum::into_int_value` for Integer return type and `BasicValueEnum::into_float_value` for Float return type
/// * `$llvm_ty:ident`: Type of first operand
/// * `,($val:ident)*`: Comma separated list of operands
macro_rules! generate_llvm_intrinsic_fn_body {
($ctx:ident, $name:ident, $llvm_name:literal, $map_fn:expr, $llvm_ty:ident $(,$val:ident)*) => {{
const FN_NAME: &str = concat!("llvm.", $llvm_name);
let intrinsic_fn = Intrinsic::find(FN_NAME).and_then(|intrinsic| intrinsic.get_declaration(&$ctx.module, &[$llvm_ty.into()])).unwrap();
$ctx.builder.build_call(intrinsic_fn, &[$($val.into()),*], $name.unwrap_or_default()).map(CallSiteValue::try_as_basic_value).map(|v| v.map_left($map_fn)).map(Either::unwrap_left).unwrap()
}};
}
/// Macro to generate the llvm intrinsic function using [`generate_llvm_intrinsic_fn_body`].
///
/// Arguments:
/// * `float/int`: Indicates the return and argument type of the function
/// * `$fn_name:ident`: The identifier of the rust function to be generated
/// * `$llvm_name:literal`: Name of underlying llvm intrinsic function
/// Omit "llvm." prefix from the function name i.e. use "ceil" instead of "llvm.ceil"
/// * `$val:ident`: The operand for unary operations
/// * `$val1:ident`, `$val2:ident`: The operands for binary operations
macro_rules! generate_llvm_intrinsic_fn {
("float", $fn_name:ident, $llvm_name:literal, $val:ident) => {
#[doc = concat!("Invokes the [`", stringify!($llvm_name), "`](https://llvm.org/docs/LangRef.html#llvm-", stringify!($llvm_name), "-intrinsic) intrinsic." )]
pub fn $fn_name<'ctx> (
ctx: &CodeGenContext<'ctx, '_>,
$val: FloatValue<'ctx>,
name: Option<&str>,
) -> FloatValue<'ctx> {
let llvm_ty = $val.get_type();
generate_llvm_intrinsic_fn_body!(ctx, name, $llvm_name, BasicValueEnum::into_float_value, llvm_ty, $val)
}
};
("float", $fn_name:ident, $llvm_name:literal, $val1:ident, $val2:ident) => {
#[doc = concat!("Invokes the [`", stringify!($llvm_name), "`](https://llvm.org/docs/LangRef.html#llvm-", stringify!($llvm_name), "-intrinsic) intrinsic." )]
pub fn $fn_name<'ctx> (
ctx: &CodeGenContext<'ctx, '_>,
$val1: FloatValue<'ctx>,
$val2: FloatValue<'ctx>,
name: Option<&str>,
) -> FloatValue<'ctx> {
debug_assert_eq!($val1.get_type(), $val2.get_type());
let llvm_ty = $val1.get_type();
generate_llvm_intrinsic_fn_body!(ctx, name, $llvm_name, BasicValueEnum::into_float_value, llvm_ty, $val1, $val2)
}
};
("int", $fn_name:ident, $llvm_name:literal, $val1:ident, $val2:ident) => {
#[doc = concat!("Invokes the [`", stringify!($llvm_name), "`](https://llvm.org/docs/LangRef.html#llvm-", stringify!($llvm_name), "-intrinsic) intrinsic." )]
pub fn $fn_name<'ctx> (
ctx: &CodeGenContext<'ctx, '_>,
$val1: IntValue<'ctx>,
$val2: IntValue<'ctx>,
name: Option<&str>,
) -> IntValue<'ctx> {
debug_assert_eq!($val1.get_type().get_bit_width(), $val2.get_type().get_bit_width());
let llvm_ty = $val1.get_type();
generate_llvm_intrinsic_fn_body!(ctx, name, $llvm_name, BasicValueEnum::into_int_value, llvm_ty, $val1, $val2)
}
};
}
/// Invokes the [`llvm.abs`](https://llvm.org/docs/LangRef.html#llvm-abs-intrinsic) intrinsic.
///
/// * `src` - The value for which the absolute value is to be returned.
/// * `is_int_min_poison` - Whether `poison` is to be returned if `src` is `INT_MIN`.
pub fn call_int_abs<'ctx>(
ctx: &CodeGenContext<'ctx, '_>,
src: IntValue<'ctx>,
is_int_min_poison: IntValue<'ctx>,
name: Option<&str>,
) -> IntValue<'ctx> {
debug_assert_eq!(is_int_min_poison.get_type().get_bit_width(), 1);
debug_assert!(is_int_min_poison.is_const());
let src_type = src.get_type();
generate_llvm_intrinsic_fn_body!(
ctx,
name,
"abs",
BasicValueEnum::into_int_value,
src_type,
src,
is_int_min_poison
)
}
generate_llvm_intrinsic_fn!("int", call_int_smax, "smax", a, b);
generate_llvm_intrinsic_fn!("int", call_int_smin, "smin", a, b);
generate_llvm_intrinsic_fn!("int", call_int_umax, "umax", a, b);
generate_llvm_intrinsic_fn!("int", call_int_umin, "umin", a, b);
generate_llvm_intrinsic_fn!("int", call_expect, "expect", val, expected_val);
generate_llvm_intrinsic_fn!("float", call_float_sqrt, "sqrt", val);
generate_llvm_intrinsic_fn!("float", call_float_sin, "sin", val);
generate_llvm_intrinsic_fn!("float", call_float_cos, "cos", val);
generate_llvm_intrinsic_fn!("float", call_float_pow, "pow", val, power);
generate_llvm_intrinsic_fn!("float", call_float_exp, "exp", val);
generate_llvm_intrinsic_fn!("float", call_float_exp2, "exp2", val);
generate_llvm_intrinsic_fn!("float", call_float_log, "log", val);
generate_llvm_intrinsic_fn!("float", call_float_log10, "log10", val);
generate_llvm_intrinsic_fn!("float", call_float_log2, "log2", val);
generate_llvm_intrinsic_fn!("float", call_float_fabs, "fabs", src);
generate_llvm_intrinsic_fn!("float", call_float_minnum, "minnum", val, power);
generate_llvm_intrinsic_fn!("float", call_float_maxnum, "maxnum", val, power);
generate_llvm_intrinsic_fn!("float", call_float_copysign, "copysign", mag, sgn);
generate_llvm_intrinsic_fn!("float", call_float_floor, "floor", val);
generate_llvm_intrinsic_fn!("float", call_float_ceil, "ceil", val);
generate_llvm_intrinsic_fn!("float", call_float_round, "round", val);
generate_llvm_intrinsic_fn!("float", call_float_rint, "rint", val);
/// Invokes the [`llvm.powi`](https://llvm.org/docs/LangRef.html#llvm-powi-intrinsic) intrinsic.
pub fn call_float_powi<'ctx>(
ctx: &CodeGenContext<'ctx, '_>,
val: FloatValue<'ctx>,
power: IntValue<'ctx>,
name: Option<&str>,
) -> FloatValue<'ctx> {
const FN_NAME: &str = "llvm.powi";
let llvm_val_t = val.get_type();
let llvm_power_t = power.get_type();
let intrinsic_fn = Intrinsic::find(FN_NAME)
.and_then(|intrinsic| {
intrinsic.get_declaration(&ctx.module, &[llvm_val_t.into(), llvm_power_t.into()])
})
.unwrap();
ctx.builder
.build_call(intrinsic_fn, &[val.into(), power.into()], name.unwrap_or_default())
.map(CallSiteValue::try_as_basic_value)
.map(|v| v.map_left(BasicValueEnum::into_float_value))
.map(Either::unwrap_left)
.unwrap()
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,27 +1,18 @@
use crate::{ use crate::{
codegen::{ codegen::{
classes::{ListType, NDArrayType, ProxyType, RangeType}, concrete_type::ConcreteTypeStore, CodeGenContext, CodeGenTask, DefaultCodeGenerator,
concrete_type::ConcreteTypeStore, WithCall, WorkerRegistry,
CodeGenContext, CodeGenLLVMOptions, CodeGenTargetMachineOptions, CodeGenTask,
CodeGenerator, DefaultCodeGenerator, WithCall, WorkerRegistry,
}, },
symbol_resolver::{SymbolResolver, ValueEnum}, symbol_resolver::{SymbolResolver, ValueEnum},
toplevel::{ toplevel::{
composer::{ComposerConfig, TopLevelComposer}, composer::TopLevelComposer, DefinitionId, FunInstance, TopLevelContext, TopLevelDef,
DefinitionId, FunInstance, TopLevelContext, TopLevelDef,
}, },
typecheck::{ typecheck::{
type_inferencer::{FunctionData, Inferencer, PrimitiveStore}, type_inferencer::{FunctionData, Inferencer, PrimitiveStore},
typedef::{FunSignature, FuncArg, Type, TypeEnum, Unifier, VarMap}, typedef::{FunSignature, FuncArg, Type, TypeEnum, Unifier},
}, },
}; };
use indexmap::IndexMap;
use indoc::indoc; use indoc::indoc;
use inkwell::{
targets::{InitializationConfig, Target},
OptimizationLevel,
};
use nac3parser::ast::FileName;
use nac3parser::{ use nac3parser::{
ast::{fold::Fold, StrRef}, ast::{fold::Fold, StrRef},
parser::parse_program, parser::parse_program,
@ -57,23 +48,23 @@ impl SymbolResolver for Resolver {
_: &PrimitiveStore, _: &PrimitiveStore,
str: StrRef, str: StrRef,
) -> Result<Type, String> { ) -> Result<Type, String> {
self.id_to_type.get(&str).copied().ok_or_else(|| format!("cannot find symbol `{str}`")) self.id_to_type.get(&str).cloned().ok_or_else(|| format!("cannot find symbol `{}`", str))
} }
fn get_symbol_value<'ctx>( fn get_symbol_value<'ctx, 'a>(
&self, &self,
_: StrRef, _: StrRef,
_: &mut CodeGenContext<'ctx, '_>, _: &mut CodeGenContext<'ctx, 'a>,
) -> Option<ValueEnum<'ctx>> { ) -> Option<ValueEnum<'ctx>> {
unimplemented!() unimplemented!()
} }
fn get_identifier_def(&self, id: StrRef) -> Result<DefinitionId, HashSet<String>> { fn get_identifier_def(&self, id: StrRef) -> Result<DefinitionId, String> {
self.id_to_def self.id_to_def
.read() .read()
.get(&id) .get(&id)
.copied() .cloned()
.ok_or_else(|| HashSet::from([format!("cannot find symbol `{id}`")])) .ok_or_else(|| format!("cannot find symbol `{}`", id))
} }
fn get_string_id(&self, _: &str) -> i32 { fn get_string_id(&self, _: &str) -> i32 {
@ -92,9 +83,9 @@ fn test_primitives() {
d = a if c == 1 else 0 d = a if c == 1 else 0
return d return d
"}; "};
let statements = parse_program(source, FileName::default()).unwrap(); let statements = parse_program(source, Default::default()).unwrap();
let composer = TopLevelComposer::new(Vec::new(), ComposerConfig::default(), 32).0; let composer: TopLevelComposer = Default::default();
let mut unifier = composer.unifier.clone(); let mut unifier = composer.unifier.clone();
let primitives = composer.primitives_ty; let primitives = composer.primitives_ty;
let top_level = Arc::new(composer.make_top_level_context()); let top_level = Arc::new(composer.make_top_level_context());
@ -103,7 +94,7 @@ fn test_primitives() {
let resolver = Arc::new(Resolver { let resolver = Arc::new(Resolver {
id_to_type: HashMap::new(), id_to_type: HashMap::new(),
id_to_def: RwLock::new(HashMap::new()), id_to_def: RwLock::new(HashMap::new()),
class_names: HashMap::default(), class_names: Default::default(),
}) as Arc<dyn SymbolResolver + Send + Sync>; }) as Arc<dyn SymbolResolver + Send + Sync>;
let threads = vec![DefaultCodeGenerator::new("test".into(), 32).into()]; let threads = vec![DefaultCodeGenerator::new("test".into(), 32).into()];
@ -113,7 +104,7 @@ fn test_primitives() {
FuncArg { name: "b".into(), ty: primitives.int32, default_value: None }, FuncArg { name: "b".into(), ty: primitives.int32, default_value: None },
], ],
ret: primitives.int32, ret: primitives.int32,
vars: VarMap::new(), vars: HashMap::new(),
}; };
let mut store = ConcreteTypeStore::new(); let mut store = ConcreteTypeStore::new();
@ -128,12 +119,12 @@ fn test_primitives() {
}; };
let mut virtual_checks = Vec::new(); let mut virtual_checks = Vec::new();
let mut calls = HashMap::new(); let mut calls = HashMap::new();
let mut identifiers: HashSet<_> = ["a".into(), "b".into()].into(); let mut identifiers: HashSet<_> = ["a".into(), "b".into()].iter().cloned().collect();
let mut inferencer = Inferencer { let mut inferencer = Inferencer {
top_level: &top_level, top_level: &top_level,
function_data: &mut function_data, function_data: &mut function_data,
unifier: &mut unifier, unifier: &mut unifier,
variable_mapping: HashMap::default(), variable_mapping: Default::default(),
primitives: &primitives, primitives: &primitives,
virtual_checks: &mut virtual_checks, virtual_checks: &mut virtual_checks,
calls: &mut calls, calls: &mut calls,
@ -157,7 +148,7 @@ fn test_primitives() {
}); });
let task = CodeGenTask { let task = CodeGenTask {
subst: Vec::default(), subst: Default::default(),
symbol_name: "testing".into(), symbol_name: "testing".into(),
body: Arc::new(statements), body: Arc::new(statements),
unifier_index: 0, unifier_index: 0,
@ -190,16 +181,22 @@ fn test_primitives() {
; ModuleID = 'test' ; ModuleID = 'test'
source_filename = \"test\" source_filename = \"test\"
; Function Attrs: mustprogress nofree norecurse nosync nounwind readnone willreturn define i32 @testing(i32 %0, i32 %1) !dbg !4 {
define i32 @testing(i32 %0, i32 %1) local_unnamed_addr #0 !dbg !4 {
init: init:
%add = add i32 %1, %0, !dbg !9 %add = add i32 %0, %1, !dbg !9
%cmp = icmp eq i32 %add, 1, !dbg !10 %cmp = icmp eq i32 %add, 1, !dbg !10
%. = select i1 %cmp, i32 %0, i32 0, !dbg !11 br i1 %cmp, label %then, label %else, !dbg !10
ret i32 %., !dbg !12
}
attributes #0 = { mustprogress nofree norecurse nosync nounwind readnone willreturn } then: ; preds = %init
br label %cont, !dbg !11
else: ; preds = %init
br label %cont, !dbg !12
cont: ; preds = %else, %then
%if_exp_result.0 = phi i32 [ %0, %then ], [ 0, %else ], !dbg !13
ret i32 %if_exp_result.0, !dbg !14
}
!llvm.module.flags = !{!0, !1} !llvm.module.flags = !{!0, !1}
!llvm.dbg.cu = !{!2} !llvm.dbg.cu = !{!2}
@ -215,20 +212,15 @@ fn test_primitives() {
!8 = !{} !8 = !{}
!9 = !DILocation(line: 1, column: 9, scope: !4) !9 = !DILocation(line: 1, column: 9, scope: !4)
!10 = !DILocation(line: 2, column: 15, scope: !4) !10 = !DILocation(line: 2, column: 15, scope: !4)
!11 = !DILocation(line: 0, scope: !4) !11 = !DILocation(line: 2, column: 5, scope: !4)
!12 = !DILocation(line: 3, column: 8, scope: !4) !12 = !DILocation(line: 2, column: 22, scope: !4)
!13 = !DILocation(line: 0, scope: !4)
!14 = !DILocation(line: 3, column: 8, scope: !4)
"} "}
.trim(); .trim();
assert_eq!(expected, module.print_to_string().to_str().unwrap().trim()); assert_eq!(expected, module.print_to_string().to_str().unwrap().trim());
}))); })));
let (registry, handles) = WorkerRegistry::create_workers(threads, top_level, f);
Target::initialize_all(&InitializationConfig::default());
let llvm_options = CodeGenLLVMOptions {
opt_level: OptimizationLevel::Default,
target: CodeGenTargetMachineOptions::from_host_triple(),
};
let (registry, handles) = WorkerRegistry::create_workers(threads, top_level, &llvm_options, &f);
registry.add_task(task); registry.add_task(task);
registry.wait_tasks_complete(handles); registry.wait_tasks_complete(handles);
} }
@ -239,14 +231,14 @@ fn test_simple_call() {
a = foo(a) a = foo(a)
return a * 2 return a * 2
"}; "};
let statements_1 = parse_program(source_1, FileName::default()).unwrap(); let statements_1 = parse_program(source_1, Default::default()).unwrap();
let source_2 = indoc! { " let source_2 = indoc! { "
return a + 1 return a + 1
"}; "};
let statements_2 = parse_program(source_2, FileName::default()).unwrap(); let statements_2 = parse_program(source_2, Default::default()).unwrap();
let composer = TopLevelComposer::new(Vec::new(), ComposerConfig::default(), 32).0; let composer: TopLevelComposer = Default::default();
let mut unifier = composer.unifier.clone(); let mut unifier = composer.unifier.clone();
let primitives = composer.primitives_ty; let primitives = composer.primitives_ty;
let top_level = Arc::new(composer.make_top_level_context()); let top_level = Arc::new(composer.make_top_level_context());
@ -255,7 +247,7 @@ fn test_simple_call() {
let signature = FunSignature { let signature = FunSignature {
args: vec![FuncArg { name: "a".into(), ty: primitives.int32, default_value: None }], args: vec![FuncArg { name: "a".into(), ty: primitives.int32, default_value: None }],
ret: primitives.int32, ret: primitives.int32,
vars: VarMap::new(), vars: HashMap::new(),
}; };
let fun_ty = unifier.add_ty(TypeEnum::TFunc(signature.clone())); let fun_ty = unifier.add_ty(TypeEnum::TFunc(signature.clone()));
let mut store = ConcreteTypeStore::new(); let mut store = ConcreteTypeStore::new();
@ -279,7 +271,7 @@ fn test_simple_call() {
let resolver = Resolver { let resolver = Resolver {
id_to_type: HashMap::new(), id_to_type: HashMap::new(),
id_to_def: RwLock::new(HashMap::new()), id_to_def: RwLock::new(HashMap::new()),
class_names: HashMap::default(), class_names: Default::default(),
}; };
resolver.add_id_def("foo".into(), DefinitionId(foo_id)); resolver.add_id_def("foo".into(), DefinitionId(foo_id));
let resolver = Arc::new(resolver) as Arc<dyn SymbolResolver + Send + Sync>; let resolver = Arc::new(resolver) as Arc<dyn SymbolResolver + Send + Sync>;
@ -300,12 +292,12 @@ fn test_simple_call() {
}; };
let mut virtual_checks = Vec::new(); let mut virtual_checks = Vec::new();
let mut calls = HashMap::new(); let mut calls = HashMap::new();
let mut identifiers: HashSet<_> = ["a".into(), "foo".into()].into(); let mut identifiers: HashSet<_> = ["a".into(), "foo".into()].iter().cloned().collect();
let mut inferencer = Inferencer { let mut inferencer = Inferencer {
top_level: &top_level, top_level: &top_level,
function_data: &mut function_data, function_data: &mut function_data,
unifier: &mut unifier, unifier: &mut unifier,
variable_mapping: HashMap::default(), variable_mapping: Default::default(),
primitives: &primitives, primitives: &primitives,
virtual_checks: &mut virtual_checks, virtual_checks: &mut virtual_checks,
calls: &mut calls, calls: &mut calls,
@ -334,11 +326,11 @@ fn test_simple_call() {
&mut *top_level.definitions.read()[foo_id].write() &mut *top_level.definitions.read()[foo_id].write()
{ {
instance_to_stmt.insert( instance_to_stmt.insert(
String::new(), "".to_string(),
FunInstance { FunInstance {
body: Arc::new(statements_2), body: Arc::new(statements_2),
calls: Arc::new(inferencer.calls.clone()), calls: Arc::new(inferencer.calls.clone()),
subst: IndexMap::default(), subst: Default::default(),
unifier_id: 0, unifier_id: 0,
}, },
); );
@ -354,7 +346,7 @@ fn test_simple_call() {
}); });
let task = CodeGenTask { let task = CodeGenTask {
subst: Vec::default(), subst: Default::default(),
symbol_name: "testing".to_string(), symbol_name: "testing".to_string(),
body: Arc::new(statements_1), body: Arc::new(statements_1),
calls: Arc::new(calls1), calls: Arc::new(calls1),
@ -369,23 +361,19 @@ fn test_simple_call() {
; ModuleID = 'test' ; ModuleID = 'test'
source_filename = \"test\" source_filename = \"test\"
; Function Attrs: mustprogress nofree norecurse nosync nounwind readnone willreturn define i32 @testing(i32 %0) !dbg !5 {
define i32 @testing(i32 %0) local_unnamed_addr #0 !dbg !5 {
init: init:
%add.i = shl i32 %0, 1, !dbg !10 %call = call i32 @foo.0(i32 %0), !dbg !10
%mul = add i32 %add.i, 2, !dbg !10 %mul = mul i32 %call, 2, !dbg !11
ret i32 %mul, !dbg !10 ret i32 %mul, !dbg !11
} }
; Function Attrs: mustprogress nofree norecurse nosync nounwind readnone willreturn define i32 @foo.0(i32 %0) !dbg !12 {
define i32 @foo.0(i32 %0) local_unnamed_addr #0 !dbg !11 {
init: init:
%add = add i32 %0, 1, !dbg !12 %add = add i32 %0, 1, !dbg !13
ret i32 %add, !dbg !12 ret i32 %add, !dbg !13
} }
attributes #0 = { mustprogress nofree norecurse nosync nounwind readnone willreturn }
!llvm.module.flags = !{!0, !1} !llvm.module.flags = !{!0, !1}
!llvm.dbg.cu = !{!2, !4} !llvm.dbg.cu = !{!2, !4}
@ -399,53 +387,15 @@ fn test_simple_call() {
!7 = !{!8} !7 = !{!8}
!8 = !DIBasicType(name: \"_\", flags: DIFlagPublic) !8 = !DIBasicType(name: \"_\", flags: DIFlagPublic)
!9 = !{} !9 = !{}
!10 = !DILocation(line: 2, column: 12, scope: !5) !10 = !DILocation(line: 1, column: 9, scope: !5)
!11 = distinct !DISubprogram(name: \"foo.0\", linkageName: \"foo.0\", scope: null, file: !3, line: 1, type: !6, scopeLine: 1, flags: DIFlagPublic, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !4, retainedNodes: !9) !11 = !DILocation(line: 2, column: 12, scope: !5)
!12 = !DILocation(line: 1, column: 12, scope: !11) !12 = distinct !DISubprogram(name: \"foo.0\", linkageName: \"foo.0\", scope: null, file: !3, line: 1, type: !6, scopeLine: 1, flags: DIFlagPublic, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !4, retainedNodes: !9)
!13 = !DILocation(line: 1, column: 12, scope: !12)
"} "}
.trim(); .trim();
assert_eq!(expected, module.print_to_string().to_str().unwrap().trim()); assert_eq!(expected, module.print_to_string().to_str().unwrap().trim());
}))); })));
let (registry, handles) = WorkerRegistry::create_workers(threads, top_level, f);
Target::initialize_all(&InitializationConfig::default());
let llvm_options = CodeGenLLVMOptions {
opt_level: OptimizationLevel::Default,
target: CodeGenTargetMachineOptions::from_host_triple(),
};
let (registry, handles) = WorkerRegistry::create_workers(threads, top_level, &llvm_options, &f);
registry.add_task(task); registry.add_task(task);
registry.wait_tasks_complete(handles); registry.wait_tasks_complete(handles);
} }
#[test]
fn test_classes_list_type_new() {
let ctx = inkwell::context::Context::create();
let generator = DefaultCodeGenerator::new(String::new(), 64);
let llvm_i32 = ctx.i32_type();
let llvm_usize = generator.get_size_type(&ctx);
let llvm_list = ListType::new(&generator, &ctx, llvm_i32.into());
assert!(ListType::is_type(llvm_list.as_base_type(), llvm_usize).is_ok());
}
#[test]
fn test_classes_range_type_new() {
let ctx = inkwell::context::Context::create();
let llvm_range = RangeType::new(&ctx);
assert!(RangeType::is_type(llvm_range.as_base_type()).is_ok());
}
#[test]
fn test_classes_ndarray_type_new() {
let ctx = inkwell::context::Context::create();
let generator = DefaultCodeGenerator::new(String::new(), 64);
let llvm_i32 = ctx.i32_type();
let llvm_usize = generator.get_size_type(&ctx);
let llvm_ndarray = NDArrayType::new(&generator, &ctx, llvm_i32.into());
assert!(NDArrayType::is_type(llvm_ndarray.as_base_type(), llvm_usize).is_ok());
}

View File

@ -1,26 +1,7 @@
#![deny( #![warn(clippy::all)]
future_incompatible, #![allow(dead_code)]
let_underscore,
nonstandard_style,
rust_2024_compatibility,
clippy::all
)]
#![warn(clippy::pedantic)]
#![allow(
dead_code,
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::enum_glob_use,
clippy::missing_errors_doc,
clippy::missing_panics_doc,
clippy::module_name_repetitions,
clippy::similar_names,
clippy::too_many_lines,
clippy::wildcard_imports
)]
pub mod codegen; pub mod codegen;
pub mod symbol_resolver; pub mod symbol_resolver;
pub mod toplevel; pub mod toplevel;
pub mod typecheck; pub mod typecheck;
pub mod util;

View File

@ -1,19 +1,22 @@
use std::fmt::Debug; use std::fmt::Debug;
use std::rc::Rc;
use std::sync::Arc; use std::sync::Arc;
use std::{collections::HashMap, collections::HashSet, fmt::Display}; use std::{collections::HashMap, fmt::Display};
use crate::typecheck::typedef::TypeEnum;
use crate::{ use crate::{
codegen::{CodeGenContext, CodeGenerator}, codegen::CodeGenContext,
toplevel::{type_annotation::TypeAnnotation, DefinitionId, TopLevelDef}, toplevel::{DefinitionId, TopLevelDef},
};
use crate::{
codegen::CodeGenerator,
typecheck::{ typecheck::{
type_inferencer::PrimitiveStore, type_inferencer::PrimitiveStore,
typedef::{Type, TypeEnum, Unifier, VarMap}, typedef::{Type, Unifier},
}, },
}; };
use inkwell::values::{BasicValueEnum, FloatValue, IntValue, PointerValue, StructValue}; use inkwell::values::{BasicValueEnum, FloatValue, IntValue, PointerValue, StructValue};
use itertools::{chain, izip, Itertools}; use itertools::{chain, izip};
use nac3parser::ast::{Constant, Expr, Location, StrRef}; use nac3parser::ast::{Expr, Location, StrRef};
use parking_lot::RwLock; use parking_lot::RwLock;
#[derive(Clone, PartialEq, Debug)] #[derive(Clone, PartialEq, Debug)]
@ -30,192 +33,15 @@ pub enum SymbolValue {
OptionNone, OptionNone,
} }
impl SymbolValue {
/// Creates a [`SymbolValue`] from a [`Constant`].
///
/// * `constant` - The constant to create the value from.
/// * `expected_ty` - The expected type of the [`SymbolValue`].
pub fn from_constant(
constant: &Constant,
expected_ty: Type,
primitives: &PrimitiveStore,
unifier: &mut Unifier,
) -> Result<Self, String> {
match constant {
Constant::None => {
if unifier.unioned(expected_ty, primitives.option) {
Ok(SymbolValue::OptionNone)
} else {
Err(format!("Expected {expected_ty:?}, but got Option"))
}
}
Constant::Bool(b) => {
if unifier.unioned(expected_ty, primitives.bool) {
Ok(SymbolValue::Bool(*b))
} else {
Err(format!("Expected {expected_ty:?}, but got bool"))
}
}
Constant::Str(s) => {
if unifier.unioned(expected_ty, primitives.str) {
Ok(SymbolValue::Str(s.to_string()))
} else {
Err(format!("Expected {expected_ty:?}, but got str"))
}
}
Constant::Int(i) => {
if unifier.unioned(expected_ty, primitives.int32) {
i32::try_from(*i).map(SymbolValue::I32).map_err(|e| e.to_string())
} else if unifier.unioned(expected_ty, primitives.int64) {
i64::try_from(*i).map(SymbolValue::I64).map_err(|e| e.to_string())
} else if unifier.unioned(expected_ty, primitives.uint32) {
u32::try_from(*i).map(SymbolValue::U32).map_err(|e| e.to_string())
} else if unifier.unioned(expected_ty, primitives.uint64) {
u64::try_from(*i).map(SymbolValue::U64).map_err(|e| e.to_string())
} else {
Err(format!("Expected {}, but got int", unifier.stringify(expected_ty)))
}
}
Constant::Tuple(t) => {
let expected_ty = unifier.get_ty(expected_ty);
let TypeEnum::TTuple { ty } = expected_ty.as_ref() else {
return Err(format!(
"Expected {:?}, but got Tuple",
expected_ty.get_type_name()
));
};
assert_eq!(ty.len(), t.len());
let elems = t
.iter()
.zip(ty)
.map(|(constant, ty)| Self::from_constant(constant, *ty, primitives, unifier))
.collect::<Result<Vec<SymbolValue>, _>>()?;
Ok(SymbolValue::Tuple(elems))
}
Constant::Float(f) => {
if unifier.unioned(expected_ty, primitives.float) {
Ok(SymbolValue::Double(*f))
} else {
Err(format!("Expected {expected_ty:?}, but got float"))
}
}
_ => Err(format!("Unsupported value type {constant:?}")),
}
}
/// Creates a [`SymbolValue`] from a [`Constant`], with its type being inferred from the constant value.
///
/// * `constant` - The constant to create the value from.
pub fn from_constant_inferred(constant: &Constant) -> Result<Self, String> {
match constant {
Constant::None => Ok(SymbolValue::OptionNone),
Constant::Bool(b) => Ok(SymbolValue::Bool(*b)),
Constant::Str(s) => Ok(SymbolValue::Str(s.to_string())),
Constant::Int(i) => {
let i = *i;
if i >= 0 {
i32::try_from(i)
.map(SymbolValue::I32)
.or_else(|_| i64::try_from(i).map(SymbolValue::I64))
.map_err(|_| {
format!("Literal cannot be expressed as any integral type: {i}")
})
} else {
u32::try_from(i)
.map(SymbolValue::U32)
.or_else(|_| u64::try_from(i).map(SymbolValue::U64))
.map_err(|_| {
format!("Literal cannot be expressed as any integral type: {i}")
})
}
}
Constant::Tuple(t) => {
let elems = t
.iter()
.map(Self::from_constant_inferred)
.collect::<Result<Vec<SymbolValue>, _>>()?;
Ok(SymbolValue::Tuple(elems))
}
Constant::Float(f) => Ok(SymbolValue::Double(*f)),
_ => Err(format!("Unsupported value type {constant:?}")),
}
}
/// Returns the [`Type`] representing the data type of this value.
pub fn get_type(&self, primitives: &PrimitiveStore, unifier: &mut Unifier) -> Type {
match self {
SymbolValue::I32(_) => primitives.int32,
SymbolValue::I64(_) => primitives.int64,
SymbolValue::U32(_) => primitives.uint32,
SymbolValue::U64(_) => primitives.uint64,
SymbolValue::Str(_) => primitives.str,
SymbolValue::Double(_) => primitives.float,
SymbolValue::Bool(_) => primitives.bool,
SymbolValue::Tuple(vs) => {
let vs_tys = vs.iter().map(|v| v.get_type(primitives, unifier)).collect::<Vec<_>>();
unifier.add_ty(TypeEnum::TTuple { ty: vs_tys })
}
SymbolValue::OptionSome(_) | SymbolValue::OptionNone => primitives.option,
}
}
/// Returns the [`TypeAnnotation`] representing the data type of this value.
pub fn get_type_annotation(
&self,
primitives: &PrimitiveStore,
unifier: &mut Unifier,
) -> TypeAnnotation {
match self {
SymbolValue::Bool(..)
| SymbolValue::Double(..)
| SymbolValue::I32(..)
| SymbolValue::I64(..)
| SymbolValue::U32(..)
| SymbolValue::U64(..)
| SymbolValue::Str(..) => TypeAnnotation::Primitive(self.get_type(primitives, unifier)),
SymbolValue::Tuple(vs) => {
let vs_tys = vs
.iter()
.map(|v| v.get_type_annotation(primitives, unifier))
.collect::<Vec<_>>();
TypeAnnotation::Tuple(vs_tys)
}
SymbolValue::OptionNone => TypeAnnotation::CustomClass {
id: primitives.option.obj_id(unifier).unwrap(),
params: Vec::default(),
},
SymbolValue::OptionSome(v) => {
let ty = v.get_type_annotation(primitives, unifier);
TypeAnnotation::CustomClass {
id: primitives.option.obj_id(unifier).unwrap(),
params: vec![ty],
}
}
}
}
/// Returns the [`TypeEnum`] representing the data type of this value.
pub fn get_type_enum(
&self,
primitives: &PrimitiveStore,
unifier: &mut Unifier,
) -> Rc<TypeEnum> {
let ty = self.get_type(primitives, unifier);
unifier.get_ty(ty)
}
}
impl Display for SymbolValue { impl Display for SymbolValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
SymbolValue::I32(i) => write!(f, "{i}"), SymbolValue::I32(i) => write!(f, "{}", i),
SymbolValue::I64(i) => write!(f, "int64({i})"), SymbolValue::I64(i) => write!(f, "int64({})", i),
SymbolValue::U32(i) => write!(f, "uint32({i})"), SymbolValue::U32(i) => write!(f, "uint32({})", i),
SymbolValue::U64(i) => write!(f, "uint64({i})"), SymbolValue::U64(i) => write!(f, "uint64({})", i),
SymbolValue::Str(s) => write!(f, "\"{s}\""), SymbolValue::Str(s) => write!(f, "\"{}\"", s),
SymbolValue::Double(d) => write!(f, "{d}"), SymbolValue::Double(d) => write!(f, "{}", d),
SymbolValue::Bool(b) => { SymbolValue::Bool(b) => {
if *b { if *b {
write!(f, "True") write!(f, "True")
@ -224,82 +50,42 @@ impl Display for SymbolValue {
} }
} }
SymbolValue::Tuple(t) => { SymbolValue::Tuple(t) => {
write!(f, "({})", t.iter().map(|v| format!("{v}")).collect::<Vec<_>>().join(", ")) write!(f, "({})", t.iter().map(|v| format!("{}", v)).collect::<Vec<_>>().join(", "))
} }
SymbolValue::OptionSome(v) => write!(f, "Some({v})"), SymbolValue::OptionSome(v) => write!(f, "Some({})", v),
SymbolValue::OptionNone => write!(f, "none"), SymbolValue::OptionNone => write!(f, "none"),
} }
} }
} }
impl TryFrom<SymbolValue> for u64 {
type Error = ();
/// Tries to convert a [`SymbolValue`] into a [`u64`], returning [`Err`] if the value is not
/// numeric or if the value cannot be converted into a `u64` without overflow.
fn try_from(value: SymbolValue) -> Result<Self, Self::Error> {
match value {
SymbolValue::I32(v) => u64::try_from(v).map_err(|_| ()),
SymbolValue::I64(v) => u64::try_from(v).map_err(|_| ()),
SymbolValue::U32(v) => Ok(u64::from(v)),
SymbolValue::U64(v) => Ok(v),
_ => Err(()),
}
}
}
impl TryFrom<SymbolValue> for i128 {
type Error = ();
/// Tries to convert a [`SymbolValue`] into a [`i128`], returning [`Err`] if the value is not
/// numeric.
fn try_from(value: SymbolValue) -> Result<Self, Self::Error> {
match value {
SymbolValue::I32(v) => Ok(i128::from(v)),
SymbolValue::I64(v) => Ok(i128::from(v)),
SymbolValue::U32(v) => Ok(i128::from(v)),
SymbolValue::U64(v) => Ok(i128::from(v)),
_ => Err(()),
}
}
}
pub trait StaticValue { pub trait StaticValue {
/// Returns a unique identifier for this value.
fn get_unique_identifier(&self) -> u64; fn get_unique_identifier(&self) -> u64;
/// Returns the constant object represented by this unique identifier. fn get_const_obj<'ctx, 'a>(
fn get_const_obj<'ctx>(
&self, &self,
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, 'a>,
generator: &mut dyn CodeGenerator, generator: &mut dyn CodeGenerator,
) -> BasicValueEnum<'ctx>; ) -> BasicValueEnum<'ctx>;
/// Converts this value to a LLVM [`BasicValueEnum`]. fn to_basic_value_enum<'ctx, 'a>(
fn to_basic_value_enum<'ctx>(
&self, &self,
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, 'a>,
generator: &mut dyn CodeGenerator, generator: &mut dyn CodeGenerator,
expected_ty: Type, expected_ty: Type,
) -> Result<BasicValueEnum<'ctx>, String>; ) -> Result<BasicValueEnum<'ctx>, String>;
/// Returns a field within this value. fn get_field<'ctx, 'a>(
fn get_field<'ctx>(
&self, &self,
name: StrRef, name: StrRef,
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, 'a>,
) -> Option<ValueEnum<'ctx>>; ) -> Option<ValueEnum<'ctx>>;
/// Returns a single element of this tuple.
fn get_tuple_element<'ctx>(&self, index: u32) -> Option<ValueEnum<'ctx>>; fn get_tuple_element<'ctx>(&self, index: u32) -> Option<ValueEnum<'ctx>>;
} }
#[derive(Clone)] #[derive(Clone)]
pub enum ValueEnum<'ctx> { pub enum ValueEnum<'ctx> {
/// [`ValueEnum`] representing a static value.
Static(Arc<dyn StaticValue + Send + Sync>), Static(Arc<dyn StaticValue + Send + Sync>),
/// [`ValueEnum`] representing a dynamic value.
Dynamic(BasicValueEnum<'ctx>), Dynamic(BasicValueEnum<'ctx>),
} }
@ -334,7 +120,6 @@ impl<'ctx> From<StructValue<'ctx>> for ValueEnum<'ctx> {
} }
impl<'ctx> ValueEnum<'ctx> { impl<'ctx> ValueEnum<'ctx> {
/// Converts this [`ValueEnum`] to a [`BasicValueEnum`].
pub fn to_basic_value_enum<'a>( pub fn to_basic_value_enum<'a>(
self, self,
ctx: &mut CodeGenContext<'ctx, 'a>, ctx: &mut CodeGenContext<'ctx, 'a>,
@ -349,7 +134,7 @@ impl<'ctx> ValueEnum<'ctx> {
} }
pub trait SymbolResolver { pub trait SymbolResolver {
/// Get type of type variable identifier or top-level function type, // get type of type variable identifier or top-level function type
fn get_symbol_type( fn get_symbol_type(
&self, &self,
unifier: &mut Unifier, unifier: &mut Unifier,
@ -358,16 +143,16 @@ pub trait SymbolResolver {
str: StrRef, str: StrRef,
) -> Result<Type, String>; ) -> Result<Type, String>;
/// Get the top-level definition of identifiers. // get the top-level definition of identifiers
fn get_identifier_def(&self, str: StrRef) -> Result<DefinitionId, HashSet<String>>; fn get_identifier_def(&self, str: StrRef) -> Result<DefinitionId, String>;
fn get_symbol_value<'ctx>( fn get_symbol_value<'ctx, 'a>(
&self, &self,
str: StrRef, str: StrRef,
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, 'a>,
) -> Option<ValueEnum<'ctx>>; ) -> Option<ValueEnum<'ctx>>;
fn get_default_param_value(&self, expr: &Expr) -> Option<SymbolValue>; fn get_default_param_value(&self, expr: &nac3parser::ast::Expr) -> Option<SymbolValue>;
fn get_string_id(&self, s: &str) -> i32; fn get_string_id(&self, s: &str) -> i32;
fn get_exception_id(&self, tyid: usize) -> usize; fn get_exception_id(&self, tyid: usize) -> usize;
@ -375,7 +160,7 @@ pub trait SymbolResolver {
&self, &self,
_unifier: &mut Unifier, _unifier: &mut Unifier,
_top_level_defs: &[Arc<RwLock<TopLevelDef>>], _top_level_defs: &[Arc<RwLock<TopLevelDef>>],
_primitives: &PrimitiveStore, _primitives: &PrimitiveStore
) -> Result<(), String> { ) -> Result<(), String> {
Ok(()) Ok(())
} }
@ -388,23 +173,23 @@ thread_local! {
"float".into(), "float".into(),
"bool".into(), "bool".into(),
"virtual".into(), "virtual".into(),
"list".into(),
"tuple".into(), "tuple".into(),
"str".into(), "str".into(),
"Exception".into(), "Exception".into(),
"uint32".into(), "uint32".into(),
"uint64".into(), "uint64".into(),
"Literal".into(),
]; ];
} }
/// Converts a type annotation into a [Type]. // convert type annotation into type
pub fn parse_type_annotation<T>( pub fn parse_type_annotation<T>(
resolver: &dyn SymbolResolver, resolver: &dyn SymbolResolver,
top_level_defs: &[Arc<RwLock<TopLevelDef>>], top_level_defs: &[Arc<RwLock<TopLevelDef>>],
unifier: &mut Unifier, unifier: &mut Unifier,
primitives: &PrimitiveStore, primitives: &PrimitiveStore,
expr: &Expr<T>, expr: &Expr<T>,
) -> Result<Type, HashSet<String>> { ) -> Result<Type, String> {
use nac3parser::ast::ExprKind::*; use nac3parser::ast::ExprKind::*;
let ids = IDENTIFIER_ID.with(|ids| *ids); let ids = IDENTIFIER_ID.with(|ids| *ids);
let int32_id = ids[0]; let int32_id = ids[0];
@ -412,12 +197,12 @@ pub fn parse_type_annotation<T>(
let float_id = ids[2]; let float_id = ids[2];
let bool_id = ids[3]; let bool_id = ids[3];
let virtual_id = ids[4]; let virtual_id = ids[4];
let tuple_id = ids[5]; let list_id = ids[5];
let str_id = ids[6]; let tuple_id = ids[6];
let exn_id = ids[7]; let str_id = ids[7];
let uint32_id = ids[8]; let exn_id = ids[8];
let uint64_id = ids[9]; let uint32_id = ids[9];
let literal_id = ids[10]; let uint64_id = ids[10];
let name_handling = |id: &StrRef, loc: Location, unifier: &mut Unifier| { let name_handling = |id: &StrRef, loc: Location, unifier: &mut Unifier| {
if *id == int32_id { if *id == int32_id {
@ -438,33 +223,39 @@ pub fn parse_type_annotation<T>(
Ok(primitives.exception) Ok(primitives.exception)
} else { } else {
let obj_id = resolver.get_identifier_def(*id); let obj_id = resolver.get_identifier_def(*id);
if let Ok(obj_id) = obj_id { match obj_id {
let def = top_level_defs[obj_id.0].read(); Ok(obj_id) => {
if let TopLevelDef::Class { fields, methods, type_vars, .. } = &*def { let def = top_level_defs[obj_id.0].read();
if !type_vars.is_empty() { if let TopLevelDef::Class { fields, methods, type_vars, .. } = &*def {
return Err(HashSet::from([format!( if !type_vars.is_empty() {
"Unexpected number of type parameters: expected {} but got 0", return Err(format!(
type_vars.len() "Unexpected number of type parameters: expected {} but got 0",
)])); type_vars.len()
));
}
let fields = chain(
fields.iter().map(|(k, v, m)| (*k, (*v, *m))),
methods.iter().map(|(k, v, _)| (*k, (*v, false))),
)
.collect();
Ok(unifier.add_ty(TypeEnum::TObj {
obj_id,
fields,
params: Default::default(),
}))
} else {
Err(format!("Cannot use function name as type at {}", loc))
} }
let fields = chain(
fields.iter().map(|(k, v, m)| (*k, (*v, *m))),
methods.iter().map(|(k, v, _)| (*k, (*v, false))),
)
.collect();
Ok(unifier.add_ty(TypeEnum::TObj { obj_id, fields, params: VarMap::default() }))
} else {
Err(HashSet::from([format!("Cannot use function name as type at {loc}")]))
} }
} else { Err(_) => {
let ty = let ty = resolver
resolver.get_symbol_type(unifier, top_level_defs, primitives, *id).map_err( .get_symbol_type(unifier, top_level_defs, primitives, *id)
|e| HashSet::from([format!("Unknown type annotation at {loc}: {e}")]), .map_err(|e| format!("Unknown type annotation at {}: {}", loc, e))?;
)?; if let TypeEnum::TVar { .. } = &*unifier.get_ty(ty) {
if let TypeEnum::TVar { .. } = &*unifier.get_ty(ty) { Ok(ty)
Ok(ty) } else {
} else { Err(format!("Unknown type annotation {} at {}", id, loc))
Err(HashSet::from([format!("Unknown type annotation {id} at {loc}")])) }
} }
} }
} }
@ -474,6 +265,9 @@ pub fn parse_type_annotation<T>(
if *id == virtual_id { if *id == virtual_id {
let ty = parse_type_annotation(resolver, top_level_defs, unifier, primitives, slice)?; let ty = parse_type_annotation(resolver, top_level_defs, unifier, primitives, slice)?;
Ok(unifier.add_ty(TypeEnum::TVirtual { ty })) Ok(unifier.add_ty(TypeEnum::TVirtual { ty }))
} else if *id == list_id {
let ty = parse_type_annotation(resolver, top_level_defs, unifier, primitives, slice)?;
Ok(unifier.add_ty(TypeEnum::TList { ty }))
} else if *id == tuple_id { } else if *id == tuple_id {
if let Tuple { elts, .. } = &slice.node { if let Tuple { elts, .. } = &slice.node {
let ty = elts let ty = elts
@ -484,31 +278,8 @@ pub fn parse_type_annotation<T>(
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
Ok(unifier.add_ty(TypeEnum::TTuple { ty })) Ok(unifier.add_ty(TypeEnum::TTuple { ty }))
} else { } else {
Err(HashSet::from(["Expected multiple elements for tuple".into()])) Err("Expected multiple elements for tuple".into())
} }
} else if *id == literal_id {
let mut parse_literal = |elt: &Expr<T>| {
let ty = parse_type_annotation(resolver, top_level_defs, unifier, primitives, elt)?;
let ty_enum = &*unifier.get_ty_immutable(ty);
match ty_enum {
TypeEnum::TLiteral { values, .. } => Ok(values.clone()),
_ => Err(HashSet::from([format!(
"Expected literal in type argument for Literal at {}",
elt.location
)])),
}
};
let values = if let Tuple { elts, .. } = &slice.node {
elts.iter().map(&mut parse_literal).collect::<Result<Vec<_>, _>>()?
} else {
vec![parse_literal(slice)?]
}
.into_iter()
.flatten()
.collect_vec();
Ok(unifier.get_fresh_literal(values, Some(slice.location)))
} else { } else {
let types = if let Tuple { elts, .. } = &slice.node { let types = if let Tuple { elts, .. } = &slice.node {
elts.iter() elts.iter()
@ -524,13 +295,13 @@ pub fn parse_type_annotation<T>(
let def = top_level_defs[obj_id.0].read(); let def = top_level_defs[obj_id.0].read();
if let TopLevelDef::Class { fields, methods, type_vars, .. } = &*def { if let TopLevelDef::Class { fields, methods, type_vars, .. } = &*def {
if types.len() != type_vars.len() { if types.len() != type_vars.len() {
return Err(HashSet::from([format!( return Err(format!(
"Unexpected number of type parameters: expected {} but got {}", "Unexpected number of type parameters: expected {} but got {}",
type_vars.len(), type_vars.len(),
types.len() types.len()
)])); ));
} }
let mut subst = VarMap::new(); let mut subst = HashMap::new();
for (var, ty) in izip!(type_vars.iter(), types.iter()) { for (var, ty) in izip!(type_vars.iter(), types.iter()) {
let id = if let TypeEnum::TVar { id, .. } = &*unifier.get_ty(*var) { let id = if let TypeEnum::TVar { id, .. } = &*unifier.get_ty(*var) {
*id *id
@ -552,7 +323,7 @@ pub fn parse_type_annotation<T>(
})); }));
Ok(unifier.add_ty(TypeEnum::TObj { obj_id, fields, params: subst })) Ok(unifier.add_ty(TypeEnum::TObj { obj_id, fields, params: subst }))
} else { } else {
Err(HashSet::from(["Cannot use function name as type".into()])) Err("Cannot use function name as type".into())
} }
} }
}; };
@ -563,13 +334,10 @@ pub fn parse_type_annotation<T>(
if let Name { id, .. } = &value.node { if let Name { id, .. } = &value.node {
subscript_name_handle(id, slice, unifier) subscript_name_handle(id, slice, unifier)
} else { } else {
Err(HashSet::from([format!("unsupported type expression at {}", expr.location)])) Err(format!("unsupported type expression at {}", expr.location))
} }
} }
Constant { value, .. } => SymbolValue::from_constant_inferred(value) _ => Err(format!("unsupported type expression at {}", expr.location)),
.map(|v| unifier.get_fresh_literal(vec![v], Some(expr.location)))
.map_err(|err| HashSet::from([err])),
_ => Err(HashSet::from([format!("unsupported type expression at {}", expr.location)])),
} }
} }
@ -580,7 +348,7 @@ impl dyn SymbolResolver + Send + Sync {
unifier: &mut Unifier, unifier: &mut Unifier,
primitives: &PrimitiveStore, primitives: &PrimitiveStore,
expr: &Expr<T>, expr: &Expr<T>,
) -> Result<Type, HashSet<String>> { ) -> Result<Type, String> {
parse_type_annotation(self, top_level_defs, unifier, primitives, expr) parse_type_annotation(self, top_level_defs, unifier, primitives, expr)
} }
@ -593,13 +361,13 @@ impl dyn SymbolResolver + Send + Sync {
unifier.internal_stringify( unifier.internal_stringify(
ty, ty,
&mut |id| { &mut |id| {
let TopLevelDef::Class { name, .. } = &*top_level_defs[id].read() else { if let TopLevelDef::Class { name, .. } = &*top_level_defs[id].read() {
name.to_string()
} else {
unreachable!("expected class definition") unreachable!("expected class definition")
}; }
name.to_string()
}, },
&mut |id| format!("typevar{id}"), &mut |id| format!("typevar{}", id),
&mut None, &mut None,
) )
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,298 +1,10 @@
use std::convert::TryInto; use std::convert::TryInto;
use crate::symbol_resolver::SymbolValue; use crate::symbol_resolver::SymbolValue;
use crate::toplevel::numpy::unpack_ndarray_var_tys;
use crate::typecheck::typedef::{into_var_map, iter_type_vars, Mapping, TypeVarId, VarMap};
use nac3parser::ast::{Constant, Location}; use nac3parser::ast::{Constant, Location};
use strum::IntoEnumIterator;
use strum_macros::EnumIter;
use super::*; use super::*;
/// All primitive types and functions in nac3core.
#[derive(Clone, Copy, Debug, EnumIter, PartialEq, Eq)]
pub enum PrimDef {
// Classes
Int32,
Int64,
Float,
Bool,
None,
Range,
Str,
Exception,
UInt32,
UInt64,
Option,
List,
NDArray,
// Member Functions
OptionIsSome,
OptionIsNone,
OptionUnwrap,
NDArrayCopy,
NDArrayFill,
FunInt32,
FunInt64,
FunUInt32,
FunUInt64,
FunFloat,
FunNpNDArray,
FunNpEmpty,
FunNpZeros,
FunNpOnes,
FunNpFull,
FunNpArray,
FunNpEye,
FunNpIdentity,
FunRound,
FunRound64,
FunNpRound,
FunRangeInit,
FunStr,
FunBool,
FunFloor,
FunFloor64,
FunNpFloor,
FunCeil,
FunCeil64,
FunNpCeil,
FunLen,
FunMin,
FunNpMin,
FunNpMinimum,
FunMax,
FunNpMax,
FunNpMaximum,
FunAbs,
FunNpIsNan,
FunNpIsInf,
FunNpSin,
FunNpCos,
FunNpExp,
FunNpExp2,
FunNpLog,
FunNpLog10,
FunNpLog2,
FunNpFabs,
FunNpSqrt,
FunNpRint,
FunNpTan,
FunNpArcsin,
FunNpArccos,
FunNpArctan,
FunNpSinh,
FunNpCosh,
FunNpTanh,
FunNpArcsinh,
FunNpArccosh,
FunNpArctanh,
FunNpExpm1,
FunNpCbrt,
FunSpSpecErf,
FunSpSpecErfc,
FunSpSpecGamma,
FunSpSpecGammaln,
FunSpSpecJ0,
FunSpSpecJ1,
FunNpArctan2,
FunNpCopysign,
FunNpFmax,
FunNpFmin,
FunNpLdExp,
FunNpHypot,
FunNpNextAfter,
// Top-Level Functions
FunSome,
}
/// Associated details of a [`PrimDef`]
pub enum PrimDefDetails {
PrimFunction { name: &'static str, simple_name: &'static str },
PrimClass { name: &'static str },
}
impl PrimDef {
/// Get the assigned [`DefinitionId`] of this [`PrimDef`].
///
/// The assigned definition ID is defined by the position this [`PrimDef`] enum unit variant is defined at,
/// with the first `PrimDef`'s definition id being `0`.
#[must_use]
pub fn id(&self) -> DefinitionId {
DefinitionId(*self as usize)
}
/// Check if a definition ID is that of a [`PrimDef`].
#[must_use]
pub fn contains_id(id: DefinitionId) -> bool {
Self::iter().any(|prim| prim.id() == id)
}
/// Get the definition "simple name" of this [`PrimDef`].
///
/// If the [`PrimDef`] is a function, this corresponds to [`TopLevelDef::Function::simple_name`].
///
/// If the [`PrimDef`] is a class, this returns [`None`].
#[must_use]
pub fn simple_name(&self) -> &'static str {
match self.details() {
PrimDefDetails::PrimFunction { simple_name, .. } => simple_name,
PrimDefDetails::PrimClass { .. } => {
panic!("PrimDef {self:?} has no simple_name as it is not a function.")
}
}
}
/// Get the definition "name" of this [`PrimDef`].
///
/// If the [`PrimDef`] is a function, this corresponds to [`TopLevelDef::Function::name`].
///
/// If the [`PrimDef`] is a class, this corresponds to [`TopLevelDef::Class::name`].
#[must_use]
pub fn name(&self) -> &'static str {
match self.details() {
PrimDefDetails::PrimFunction { name, .. } | PrimDefDetails::PrimClass { name } => name,
}
}
/// Get the associated details of this [`PrimDef`]
#[must_use]
pub fn details(self) -> PrimDefDetails {
fn class(name: &'static str) -> PrimDefDetails {
PrimDefDetails::PrimClass { name }
}
fn fun(name: &'static str, simple_name: Option<&'static str>) -> PrimDefDetails {
PrimDefDetails::PrimFunction { simple_name: simple_name.unwrap_or(name), name }
}
match self {
PrimDef::Int32 => class("int32"),
PrimDef::Int64 => class("int64"),
PrimDef::Float => class("float"),
PrimDef::Bool => class("bool"),
PrimDef::None => class("none"),
PrimDef::Range => class("range"),
PrimDef::Str => class("str"),
PrimDef::Exception => class("Exception"),
PrimDef::UInt32 => class("uint32"),
PrimDef::UInt64 => class("uint64"),
PrimDef::Option => class("Option"),
PrimDef::OptionIsSome => fun("Option.is_some", Some("is_some")),
PrimDef::OptionIsNone => fun("Option.is_none", Some("is_none")),
PrimDef::OptionUnwrap => fun("Option.unwrap", Some("unwrap")),
PrimDef::List => class("list"),
PrimDef::NDArray => class("ndarray"),
PrimDef::NDArrayCopy => fun("ndarray.copy", Some("copy")),
PrimDef::NDArrayFill => fun("ndarray.fill", Some("fill")),
PrimDef::FunInt32 => fun("int32", None),
PrimDef::FunInt64 => fun("int64", None),
PrimDef::FunUInt32 => fun("uint32", None),
PrimDef::FunUInt64 => fun("uint64", None),
PrimDef::FunFloat => fun("float", None),
PrimDef::FunNpNDArray => fun("np_ndarray", None),
PrimDef::FunNpEmpty => fun("np_empty", None),
PrimDef::FunNpZeros => fun("np_zeros", None),
PrimDef::FunNpOnes => fun("np_ones", None),
PrimDef::FunNpFull => fun("np_full", None),
PrimDef::FunNpArray => fun("np_array", None),
PrimDef::FunNpEye => fun("np_eye", None),
PrimDef::FunNpIdentity => fun("np_identity", None),
PrimDef::FunRound => fun("round", None),
PrimDef::FunRound64 => fun("round64", None),
PrimDef::FunNpRound => fun("np_round", None),
PrimDef::FunRangeInit => fun("range.__init__", Some("__init__")),
PrimDef::FunStr => fun("str", None),
PrimDef::FunBool => fun("bool", None),
PrimDef::FunFloor => fun("floor", None),
PrimDef::FunFloor64 => fun("floor64", None),
PrimDef::FunNpFloor => fun("np_floor", None),
PrimDef::FunCeil => fun("ceil", None),
PrimDef::FunCeil64 => fun("ceil64", None),
PrimDef::FunNpCeil => fun("np_ceil", None),
PrimDef::FunLen => fun("len", None),
PrimDef::FunMin => fun("min", None),
PrimDef::FunNpMin => fun("np_min", None),
PrimDef::FunNpMinimum => fun("np_minimum", None),
PrimDef::FunMax => fun("max", None),
PrimDef::FunNpMax => fun("np_max", None),
PrimDef::FunNpMaximum => fun("np_maximum", None),
PrimDef::FunAbs => fun("abs", None),
PrimDef::FunNpIsNan => fun("np_isnan", None),
PrimDef::FunNpIsInf => fun("np_isinf", None),
PrimDef::FunNpSin => fun("np_sin", None),
PrimDef::FunNpCos => fun("np_cos", None),
PrimDef::FunNpExp => fun("np_exp", None),
PrimDef::FunNpExp2 => fun("np_exp2", None),
PrimDef::FunNpLog => fun("np_log", None),
PrimDef::FunNpLog10 => fun("np_log10", None),
PrimDef::FunNpLog2 => fun("np_log2", None),
PrimDef::FunNpFabs => fun("np_fabs", None),
PrimDef::FunNpSqrt => fun("np_sqrt", None),
PrimDef::FunNpRint => fun("np_rint", None),
PrimDef::FunNpTan => fun("np_tan", None),
PrimDef::FunNpArcsin => fun("np_arcsin", None),
PrimDef::FunNpArccos => fun("np_arccos", None),
PrimDef::FunNpArctan => fun("np_arctan", None),
PrimDef::FunNpSinh => fun("np_sinh", None),
PrimDef::FunNpCosh => fun("np_cosh", None),
PrimDef::FunNpTanh => fun("np_tanh", None),
PrimDef::FunNpArcsinh => fun("np_arcsinh", None),
PrimDef::FunNpArccosh => fun("np_arccosh", None),
PrimDef::FunNpArctanh => fun("np_arctanh", None),
PrimDef::FunNpExpm1 => fun("np_expm1", None),
PrimDef::FunNpCbrt => fun("np_cbrt", None),
PrimDef::FunSpSpecErf => fun("sp_spec_erf", None),
PrimDef::FunSpSpecErfc => fun("sp_spec_erfc", None),
PrimDef::FunSpSpecGamma => fun("sp_spec_gamma", None),
PrimDef::FunSpSpecGammaln => fun("sp_spec_gammaln", None),
PrimDef::FunSpSpecJ0 => fun("sp_spec_j0", None),
PrimDef::FunSpSpecJ1 => fun("sp_spec_j1", None),
PrimDef::FunNpArctan2 => fun("np_arctan2", None),
PrimDef::FunNpCopysign => fun("np_copysign", None),
PrimDef::FunNpFmax => fun("np_fmax", None),
PrimDef::FunNpFmin => fun("np_fmin", None),
PrimDef::FunNpLdExp => fun("np_ldexp", None),
PrimDef::FunNpHypot => fun("np_hypot", None),
PrimDef::FunNpNextAfter => fun("np_nextafter", None),
PrimDef::FunSome => fun("Some", None),
}
}
}
/// Asserts that a [`PrimDef`] is in an allowlist.
///
/// Like `debug_assert!`, this statements of this function are only
/// enabled if `cfg!(debug_assertions)` is true.
pub fn debug_assert_prim_is_allowed(prim: PrimDef, allowlist: &[PrimDef]) {
if cfg!(debug_assertions) {
let allowed = allowlist.iter().any(|p| *p == prim);
assert!(
allowed,
"Disallowed primitive definition. Got {prim:?}, but expects it to be in {allowlist:?}"
);
}
}
/// Construct the fields of class `Exception`
/// See [`TypeEnum::TObj::fields`] and [`TopLevelDef::Class::fields`]
#[must_use]
pub fn make_exception_fields(int32: Type, int64: Type, str: Type) -> Vec<(StrRef, Type, bool)> {
vec![
("__name__".into(), int32, true),
("__file__".into(), str, true),
("__line__".into(), int32, true),
("__col__".into(), int32, true),
("__func__".into(), str, true),
("__message__".into(), str, true),
("__param0__".into(), int64, true),
("__param1__".into(), int64, true),
("__param2__".into(), int64, true),
]
}
impl TopLevelDef { impl TopLevelDef {
pub fn to_string(&self, unifier: &mut Unifier) -> String { pub fn to_string(&self, unifier: &mut Unifier) -> String {
match self { match self {
@ -331,160 +43,115 @@ impl TopLevelDef {
} }
impl TopLevelComposer { impl TopLevelComposer {
#[must_use] pub fn make_primitives() -> (PrimitiveStore, Unifier) {
pub fn make_primitives(size_t: u32) -> (PrimitiveStore, Unifier) {
let mut unifier = Unifier::new(); let mut unifier = Unifier::new();
let int32 = unifier.add_ty(TypeEnum::TObj { let int32 = unifier.add_ty(TypeEnum::TObj {
obj_id: PrimDef::Int32.id(), obj_id: DefinitionId(0),
fields: HashMap::new(), fields: HashMap::new(),
params: VarMap::new(), params: HashMap::new(),
}); });
let int64 = unifier.add_ty(TypeEnum::TObj { let int64 = unifier.add_ty(TypeEnum::TObj {
obj_id: PrimDef::Int64.id(), obj_id: DefinitionId(1),
fields: HashMap::new(), fields: HashMap::new(),
params: VarMap::new(), params: HashMap::new(),
}); });
let float = unifier.add_ty(TypeEnum::TObj { let float = unifier.add_ty(TypeEnum::TObj {
obj_id: PrimDef::Float.id(), obj_id: DefinitionId(2),
fields: HashMap::new(), fields: HashMap::new(),
params: VarMap::new(), params: HashMap::new(),
}); });
let bool = unifier.add_ty(TypeEnum::TObj { let bool = unifier.add_ty(TypeEnum::TObj {
obj_id: PrimDef::Bool.id(), obj_id: DefinitionId(3),
fields: HashMap::new(), fields: HashMap::new(),
params: VarMap::new(), params: HashMap::new(),
}); });
let none = unifier.add_ty(TypeEnum::TObj { let none = unifier.add_ty(TypeEnum::TObj {
obj_id: PrimDef::None.id(), obj_id: DefinitionId(4),
fields: HashMap::new(), fields: HashMap::new(),
params: VarMap::new(), params: HashMap::new(),
}); });
let range = unifier.add_ty(TypeEnum::TObj { let range = unifier.add_ty(TypeEnum::TObj {
obj_id: PrimDef::Range.id(), obj_id: DefinitionId(5),
fields: [ fields: HashMap::new(),
("start".into(), (int32, true)), params: HashMap::new(),
("stop".into(), (int32, true)),
("step".into(), (int32, true)),
]
.into_iter()
.collect(),
params: VarMap::new(),
}); });
let str = unifier.add_ty(TypeEnum::TObj { let str = unifier.add_ty(TypeEnum::TObj {
obj_id: PrimDef::Str.id(), obj_id: DefinitionId(6),
fields: HashMap::new(), fields: HashMap::new(),
params: VarMap::new(), params: HashMap::new(),
}); });
let exception = unifier.add_ty(TypeEnum::TObj { let exception = unifier.add_ty(TypeEnum::TObj {
obj_id: PrimDef::Exception.id(), obj_id: DefinitionId(7),
fields: make_exception_fields(int32, int64, str) fields: vec![
.into_iter() ("__name__".into(), (int32, true)),
.map(|(name, ty, mutable)| (name, (ty, mutable))) ("__file__".into(), (str, true)),
.collect(), ("__line__".into(), (int32, true)),
params: VarMap::new(), ("__col__".into(), (int32, true)),
("__func__".into(), (str, true)),
("__message__".into(), (str, true)),
("__param0__".into(), (int64, true)),
("__param1__".into(), (int64, true)),
("__param2__".into(), (int64, true)),
]
.into_iter()
.collect::<HashMap<_, _>>(),
params: HashMap::new(),
}); });
let uint32 = unifier.add_ty(TypeEnum::TObj { let uint32 = unifier.add_ty(TypeEnum::TObj {
obj_id: PrimDef::UInt32.id(), obj_id: DefinitionId(8),
fields: HashMap::new(), fields: HashMap::new(),
params: VarMap::new(), params: HashMap::new(),
}); });
let uint64 = unifier.add_ty(TypeEnum::TObj { let uint64 = unifier.add_ty(TypeEnum::TObj {
obj_id: PrimDef::UInt64.id(), obj_id: DefinitionId(9),
fields: HashMap::new(), fields: HashMap::new(),
params: VarMap::new(), params: HashMap::new(),
}); });
let option_type_var = unifier.get_fresh_var(Some("option_type_var".into()), None); let option_type_var = unifier.get_fresh_var(Some("option_type_var".into()), None);
let is_some_type_fun_ty = unifier.add_ty(TypeEnum::TFunc(FunSignature { let is_some_type_fun_ty = unifier.add_ty(TypeEnum::TFunc(FunSignature {
args: vec![], args: vec![],
ret: bool, ret: bool,
vars: into_var_map([option_type_var]), vars: HashMap::from([(option_type_var.1, option_type_var.0)]),
})); }));
let unwrap_fun_ty = unifier.add_ty(TypeEnum::TFunc(FunSignature { let unwrap_fun_ty = unifier.add_ty(TypeEnum::TFunc(FunSignature {
args: vec![], args: vec![],
ret: option_type_var.ty, ret: option_type_var.0,
vars: into_var_map([option_type_var]), vars: HashMap::from([(option_type_var.1, option_type_var.0)]),
})); }));
let option = unifier.add_ty(TypeEnum::TObj { let option = unifier.add_ty(TypeEnum::TObj {
obj_id: PrimDef::Option.id(), obj_id: DefinitionId(10),
fields: vec![ fields: vec![
(PrimDef::OptionIsSome.simple_name().into(), (is_some_type_fun_ty, true)), ("is_some".into(), (is_some_type_fun_ty, true)),
(PrimDef::OptionIsNone.simple_name().into(), (is_some_type_fun_ty, true)), ("is_none".into(), (is_some_type_fun_ty, true)),
(PrimDef::OptionUnwrap.simple_name().into(), (unwrap_fun_ty, true)), ("unwrap".into(), (unwrap_fun_ty, true)),
] ]
.into_iter() .into_iter()
.collect::<HashMap<_, _>>(), .collect::<HashMap<_, _>>(),
params: into_var_map([option_type_var]), params: HashMap::from([(option_type_var.1, option_type_var.0)]),
}); });
let size_t_ty = match size_t {
32 => uint32,
64 => uint64,
_ => unreachable!(),
};
let list_elem_tvar = unifier.get_fresh_var(Some("list_elem".into()), None);
let list = unifier.add_ty(TypeEnum::TObj {
obj_id: PrimDef::List.id(),
fields: Mapping::new(),
params: into_var_map([list_elem_tvar]),
});
let ndarray_dtype_tvar = unifier.get_fresh_var(Some("ndarray_dtype".into()), None);
let ndarray_ndims_tvar =
unifier.get_fresh_const_generic_var(size_t_ty, Some("ndarray_ndims".into()), None);
let ndarray_copy_fun_ret_ty = unifier.get_fresh_var(None, None);
let ndarray_copy_fun_ty = unifier.add_ty(TypeEnum::TFunc(FunSignature {
args: vec![],
ret: ndarray_copy_fun_ret_ty.ty,
vars: into_var_map([ndarray_dtype_tvar, ndarray_ndims_tvar]),
}));
let ndarray_fill_fun_ty = unifier.add_ty(TypeEnum::TFunc(FunSignature {
args: vec![FuncArg {
name: "value".into(),
ty: ndarray_dtype_tvar.ty,
default_value: None,
}],
ret: none,
vars: into_var_map([ndarray_dtype_tvar, ndarray_ndims_tvar]),
}));
let ndarray = unifier.add_ty(TypeEnum::TObj {
obj_id: PrimDef::NDArray.id(),
fields: Mapping::from([
(PrimDef::NDArrayCopy.simple_name().into(), (ndarray_copy_fun_ty, true)),
(PrimDef::NDArrayFill.simple_name().into(), (ndarray_fill_fun_ty, true)),
]),
params: into_var_map([ndarray_dtype_tvar, ndarray_ndims_tvar]),
});
unifier.unify(ndarray_copy_fun_ret_ty.ty, ndarray).unwrap();
let primitives = PrimitiveStore { let primitives = PrimitiveStore {
int32, int32,
int64, int64,
uint32,
uint64,
float, float,
bool, bool,
none, none,
range, range,
str, str,
exception, exception,
uint32,
uint64,
option, option,
list,
ndarray,
size_t,
}; };
unifier.put_primitive_store(&primitives);
crate::typecheck::magic_methods::set_primitives_magic_methods(&primitives, &mut unifier); crate::typecheck::magic_methods::set_primitives_magic_methods(&primitives, &mut unifier);
(primitives, unifier) (primitives, unifier)
} }
/// already include the `definition_id` of itself inside the ancestors vector /// already include the definition_id of itself inside the ancestors vector
/// when first registering, the `type_vars`, fields, methods, ancestors are invalid /// when first registering, the type_vars, fields, methods, ancestors are invalid
#[must_use]
pub fn make_top_level_class_def( pub fn make_top_level_class_def(
obj_id: DefinitionId, index: usize,
resolver: Option<Arc<dyn SymbolResolver + Send + Sync>>, resolver: Option<Arc<dyn SymbolResolver + Send + Sync>>,
name: StrRef, name: StrRef,
constructor: Option<Type>, constructor: Option<Type>,
@ -492,12 +159,11 @@ impl TopLevelComposer {
) -> TopLevelDef { ) -> TopLevelDef {
TopLevelDef::Class { TopLevelDef::Class {
name, name,
object_id: obj_id, object_id: DefinitionId(index),
type_vars: Vec::default(), type_vars: Default::default(),
fields: Vec::default(), fields: Default::default(),
attributes: Vec::default(), methods: Default::default(),
methods: Vec::default(), ancestors: Default::default(),
ancestors: Vec::default(),
constructor, constructor,
resolver, resolver,
loc, loc,
@ -505,7 +171,6 @@ impl TopLevelComposer {
} }
/// when first registering, the type is a invalid value /// when first registering, the type is a invalid value
#[must_use]
pub fn make_top_level_function_def( pub fn make_top_level_function_def(
name: String, name: String,
simple_name: StrRef, simple_name: StrRef,
@ -517,16 +182,15 @@ impl TopLevelComposer {
name, name,
simple_name, simple_name,
signature: ty, signature: ty,
var_id: Vec::default(), var_id: Default::default(),
instance_to_symbol: HashMap::default(), instance_to_symbol: Default::default(),
instance_to_stmt: HashMap::default(), instance_to_stmt: Default::default(),
resolver, resolver,
codegen_callback: None, codegen_callback: None,
loc, loc,
} }
} }
#[must_use]
pub fn make_class_method_name(mut class_name: String, method_name: &str) -> String { pub fn make_class_method_name(mut class_name: String, method_name: &str) -> String {
class_name.push('.'); class_name.push('.');
class_name.push_str(method_name); class_name.push_str(method_name);
@ -536,13 +200,13 @@ impl TopLevelComposer {
pub fn get_class_method_def_info( pub fn get_class_method_def_info(
class_methods_def: &[(StrRef, Type, DefinitionId)], class_methods_def: &[(StrRef, Type, DefinitionId)],
method_name: StrRef, method_name: StrRef,
) -> Result<(Type, DefinitionId), HashSet<String>> { ) -> Result<(Type, DefinitionId), String> {
for (name, ty, def_id) in class_methods_def { for (name, ty, def_id) in class_methods_def {
if name == &method_name { if name == &method_name {
return Ok((*ty, *def_id)); return Ok((*ty, *def_id));
} }
} }
Err(HashSet::from([format!("no method {method_name} in the current class")])) Err(format!("no method {} in the current class", method_name))
} }
/// get all base class def id of a class, excluding itself. \ /// get all base class def id of a class, excluding itself. \
@ -553,7 +217,7 @@ impl TopLevelComposer {
pub fn get_all_ancestors_helper( pub fn get_all_ancestors_helper(
child: &TypeAnnotation, child: &TypeAnnotation,
temp_def_list: &[Arc<RwLock<TopLevelDef>>], temp_def_list: &[Arc<RwLock<TopLevelDef>>],
) -> Result<Vec<TypeAnnotation>, HashSet<String>> { ) -> Result<Vec<TypeAnnotation>, String> {
let mut result: Vec<TypeAnnotation> = Vec::new(); let mut result: Vec<TypeAnnotation> = Vec::new();
let mut parent = Self::get_parent(child, temp_def_list); let mut parent = Self::get_parent(child, temp_def_list);
while let Some(p) = parent { while let Some(p) = parent {
@ -565,16 +229,16 @@ impl TopLevelComposer {
}; };
// check cycle // check cycle
let no_cycle = result.iter().all(|x| { let no_cycle = result.iter().all(|x| {
let TypeAnnotation::CustomClass { id, .. } = x else { if let TypeAnnotation::CustomClass { id, .. } = x {
id.0 != p_id.0
} else {
unreachable!("must be class kind annotation") unreachable!("must be class kind annotation")
}; }
id.0 != p_id.0
}); });
if no_cycle { if no_cycle {
result.push(p); result.push(p);
} else { } else {
return Err(HashSet::from(["cyclic inheritance detected".into()])); return Err("cyclic inheritance detected".into());
} }
} }
Ok(result) Ok(result)
@ -592,23 +256,23 @@ impl TopLevelComposer {
}; };
let child_def = temp_def_list.get(child_id.0).unwrap(); let child_def = temp_def_list.get(child_id.0).unwrap();
let child_def = child_def.read(); let child_def = child_def.read();
let TopLevelDef::Class { ancestors, .. } = &*child_def else { if let TopLevelDef::Class { ancestors, .. } = &*child_def {
unreachable!("child must be top level class def") if !ancestors.is_empty() {
}; Some(ancestors[0].clone())
} else {
if ancestors.is_empty() { None
None }
} else { } else {
Some(ancestors[0].clone()) unreachable!("child must be top level class def")
} }
} }
/// get the `var_id` of a given `TVar` type /// get the var_id of a given TVar type
pub fn get_var_id(var_ty: Type, unifier: &mut Unifier) -> Result<TypeVarId, HashSet<String>> { pub fn get_var_id(var_ty: Type, unifier: &mut Unifier) -> Result<u32, String> {
if let TypeEnum::TVar { id, .. } = unifier.get_ty(var_ty).as_ref() { if let TypeEnum::TVar { id, .. } = unifier.get_ty(var_ty).as_ref() {
Ok(*id) Ok(*id)
} else { } else {
Err(HashSet::from(["not type var".to_string()])) Err("not type var".to_string())
} }
} }
@ -622,17 +286,13 @@ impl TopLevelComposer {
let this = this.as_ref(); let this = this.as_ref();
let other = unifier.get_ty(other); let other = unifier.get_ty(other);
let other = other.as_ref(); let other = other.as_ref();
let ( if let (
TypeEnum::TFunc(FunSignature { args: this_args, ret: this_ret, .. }), TypeEnum::TFunc(FunSignature { args: this_args, ret: this_ret, .. }),
TypeEnum::TFunc(FunSignature { args: other_args, ret: other_ret, .. }), TypeEnum::TFunc(FunSignature { args: other_args, ret: other_ret, .. }),
) = (this, other) ) = (this, other)
else { {
unreachable!("this function must be called with function type") // check args
}; let args_ok = this_args
// check args
let args_ok =
this_args
.iter() .iter()
.map(|FuncArg { name, ty, .. }| (name, type_var_to_concrete_def.get(ty).unwrap())) .map(|FuncArg { name, ty, .. }| (name, type_var_to_concrete_def.get(ty).unwrap()))
.zip(other_args.iter().map(|FuncArg { name, ty, .. }| { .zip(other_args.iter().map(|FuncArg { name, ty, .. }| {
@ -647,15 +307,18 @@ impl TopLevelComposer {
} }
}); });
// check rets // check rets
let ret_ok = check_overload_type_annotation_compatible( let ret_ok = check_overload_type_annotation_compatible(
type_var_to_concrete_def.get(this_ret).unwrap(), type_var_to_concrete_def.get(this_ret).unwrap(),
type_var_to_concrete_def.get(other_ret).unwrap(), type_var_to_concrete_def.get(other_ret).unwrap(),
unifier, unifier,
); );
// return // return
args_ok && ret_ok args_ok && ret_ok
} else {
unreachable!("this function must be called with function type")
}
} }
pub fn check_overload_field_type( pub fn check_overload_field_type(
@ -671,7 +334,7 @@ impl TopLevelComposer {
) )
} }
pub fn get_all_assigned_field(stmts: &[Stmt<()>]) -> Result<HashSet<StrRef>, HashSet<String>> { pub fn get_all_assigned_field(stmts: &[ast::Stmt<()>]) -> Result<HashSet<StrRef>, String> {
let mut result = HashSet::new(); let mut result = HashSet::new();
for s in stmts { for s in stmts {
match &s.node { match &s.node {
@ -688,10 +351,10 @@ impl TopLevelComposer {
} }
} => } =>
{ {
return Err(HashSet::from([format!( return Err(format!(
"redundant type annotation for class fields at {}", "redundant type annotation for class fields at {}",
s.location s.location
)])) ))
} }
ast::StmtKind::Assign { targets, .. } => { ast::StmtKind::Assign { targets, .. } => {
for t in targets { for t in targets {
@ -713,14 +376,14 @@ impl TopLevelComposer {
ast::StmtKind::If { body, orelse, .. } => { ast::StmtKind::If { body, orelse, .. } => {
let inited_for_sure = Self::get_all_assigned_field(body.as_slice())? let inited_for_sure = Self::get_all_assigned_field(body.as_slice())?
.intersection(&Self::get_all_assigned_field(orelse.as_slice())?) .intersection(&Self::get_all_assigned_field(orelse.as_slice())?)
.copied() .cloned()
.collect::<HashSet<_>>(); .collect::<HashSet<_>>();
result.extend(inited_for_sure); result.extend(inited_for_sure);
} }
ast::StmtKind::Try { body, orelse, finalbody, .. } => { ast::StmtKind::Try { body, orelse, finalbody, .. } => {
let inited_for_sure = Self::get_all_assigned_field(body.as_slice())? let inited_for_sure = Self::get_all_assigned_field(body.as_slice())?
.intersection(&Self::get_all_assigned_field(orelse.as_slice())?) .intersection(&Self::get_all_assigned_field(orelse.as_slice())?)
.copied() .cloned()
.collect::<HashSet<_>>(); .collect::<HashSet<_>>();
result.extend(inited_for_sure); result.extend(inited_for_sure);
result.extend(Self::get_all_assigned_field(finalbody.as_slice())?); result.extend(Self::get_all_assigned_field(finalbody.as_slice())?);
@ -728,9 +391,9 @@ impl TopLevelComposer {
ast::StmtKind::With { body, .. } => { ast::StmtKind::With { body, .. } => {
result.extend(Self::get_all_assigned_field(body.as_slice())?); result.extend(Self::get_all_assigned_field(body.as_slice())?);
} }
ast::StmtKind::Pass { .. } ast::StmtKind::Pass { .. } => {}
| ast::StmtKind::Assert { .. } ast::StmtKind::Assert { .. } => {}
| ast::StmtKind::Expr { .. } => {} ast::StmtKind::Expr { .. } => {}
_ => { _ => {
unimplemented!() unimplemented!()
@ -743,7 +406,7 @@ impl TopLevelComposer {
pub fn parse_parameter_default_value( pub fn parse_parameter_default_value(
default: &ast::Expr, default: &ast::Expr,
resolver: &(dyn SymbolResolver + Send + Sync), resolver: &(dyn SymbolResolver + Send + Sync),
) -> Result<SymbolValue, HashSet<String>> { ) -> Result<SymbolValue, String> {
parse_parameter_default_value(default, resolver) parse_parameter_default_value(default, resolver)
} }
@ -753,6 +416,40 @@ impl TopLevelComposer {
primitive: &PrimitiveStore, primitive: &PrimitiveStore,
unifier: &mut Unifier, unifier: &mut Unifier,
) -> Result<(), String> { ) -> Result<(), String> {
fn type_default_param(
val: &SymbolValue,
primitive: &PrimitiveStore,
unifier: &mut Unifier,
) -> TypeAnnotation {
match val {
SymbolValue::Bool(..) => TypeAnnotation::Primitive(primitive.bool),
SymbolValue::Double(..) => TypeAnnotation::Primitive(primitive.float),
SymbolValue::I32(..) => TypeAnnotation::Primitive(primitive.int32),
SymbolValue::I64(..) => TypeAnnotation::Primitive(primitive.int64),
SymbolValue::U32(..) => TypeAnnotation::Primitive(primitive.uint32),
SymbolValue::U64(..) => TypeAnnotation::Primitive(primitive.uint64),
SymbolValue::Str(..) => TypeAnnotation::Primitive(primitive.str),
SymbolValue::Tuple(vs) => {
let vs_tys = vs
.iter()
.map(|v| type_default_param(v, primitive, unifier))
.collect::<Vec<_>>();
TypeAnnotation::Tuple(vs_tys)
}
SymbolValue::OptionNone => TypeAnnotation::CustomClass {
id: primitive.option.get_obj_id(unifier),
params: Default::default(),
},
SymbolValue::OptionSome(v) => {
let ty = type_default_param(v, primitive, unifier);
TypeAnnotation::CustomClass {
id: primitive.option.get_obj_id(unifier),
params: vec![ty],
}
}
}
}
fn is_compatible( fn is_compatible(
found: &TypeAnnotation, found: &TypeAnnotation,
expect: &TypeAnnotation, expect: &TypeAnnotation,
@ -768,7 +465,7 @@ impl TopLevelComposer {
TypeAnnotation::CustomClass { id: e_id, params: e_param }, TypeAnnotation::CustomClass { id: e_id, params: e_param },
) => { ) => {
*f_id == *e_id *f_id == *e_id
&& *f_id == primitive.option.obj_id(unifier).unwrap() && *f_id == primitive.option.get_obj_id(unifier)
&& (f_param.is_empty() && (f_param.is_empty()
|| (f_param.len() == 1 || (f_param.len() == 1
&& e_param.len() == 1 && e_param.len() == 1
@ -784,15 +481,15 @@ impl TopLevelComposer {
} }
} }
let found = val.get_type_annotation(primitive, unifier); let found = type_default_param(val, primitive, unifier);
if is_compatible(&found, ty, unifier, primitive) { if !is_compatible(&found, ty, unifier, primitive) {
Ok(())
} else {
Err(format!( Err(format!(
"incompatible default parameter type, expect {}, found {}", "incompatible default parameter type, expect {}, found {}",
ty.stringify(unifier), ty.stringify(unifier),
found.stringify(unifier), found.stringify(unifier),
)) ))
} else {
Ok(())
} }
} }
} }
@ -800,14 +497,14 @@ impl TopLevelComposer {
pub fn parse_parameter_default_value( pub fn parse_parameter_default_value(
default: &ast::Expr, default: &ast::Expr,
resolver: &(dyn SymbolResolver + Send + Sync), resolver: &(dyn SymbolResolver + Send + Sync),
) -> Result<SymbolValue, HashSet<String>> { ) -> Result<SymbolValue, String> {
fn handle_constant(val: &Constant, loc: &Location) -> Result<SymbolValue, HashSet<String>> { fn handle_constant(val: &Constant, loc: &Location) -> Result<SymbolValue, String> {
match val { match val {
Constant::Int(v) => { Constant::Int(v) => {
if let Ok(v) = (*v).try_into() { if let Ok(v) = (*v).try_into() {
Ok(SymbolValue::I32(v)) Ok(SymbolValue::I32(v))
} else { } else {
Err(HashSet::from([format!("integer value out of range at {loc}")])) Err(format!("integer value out of range at {}", loc))
} }
} }
Constant::Float(v) => Ok(SymbolValue::Double(*v)), Constant::Float(v) => Ok(SymbolValue::Double(*v)),
@ -815,126 +512,74 @@ pub fn parse_parameter_default_value(
Constant::Tuple(tuple) => Ok(SymbolValue::Tuple( Constant::Tuple(tuple) => Ok(SymbolValue::Tuple(
tuple.iter().map(|x| handle_constant(x, loc)).collect::<Result<Vec<_>, _>>()?, tuple.iter().map(|x| handle_constant(x, loc)).collect::<Result<Vec<_>, _>>()?,
)), )),
Constant::None => Err(HashSet::from([format!( Constant::None => Err(format!(
"`None` is not supported, use `none` for option type instead ({loc})" "`None` is not supported, use `none` for option type instead ({})",
)])), loc
)),
_ => unimplemented!("this constant is not supported at {}", loc), _ => unimplemented!("this constant is not supported at {}", loc),
} }
} }
match &default.node { match &default.node {
ast::ExprKind::Constant { value, .. } => handle_constant(value, &default.location), ast::ExprKind::Constant { value, .. } => handle_constant(value, &default.location),
ast::ExprKind::Call { func, args, .. } if args.len() == 1 => match &func.node { ast::ExprKind::Call { func, args, .. } if args.len() == 1 => {
ast::ExprKind::Name { id, .. } if *id == "int64".into() => match &args[0].node { match &func.node {
ast::ExprKind::Constant { value: Constant::Int(v), .. } => { ast::ExprKind::Name { id, .. } if *id == "int64".into() => match &args[0].node {
let v: Result<i64, _> = (*v).try_into(); ast::ExprKind::Constant { value: Constant::Int(v), .. } => {
match v { let v: Result<i64, _> = (*v).try_into();
Ok(v) => Ok(SymbolValue::I64(v)), match v {
_ => Err(HashSet::from([format!( Ok(v) => Ok(SymbolValue::I64(v)),
"default param value out of range at {}", _ => Err(format!("default param value out of range at {}", default.location)),
default.location }
)])),
} }
_ => Err(format!("only allow constant integer here at {}", default.location))
} }
_ => Err(HashSet::from([format!( ast::ExprKind::Name { id, .. } if *id == "uint32".into() => match &args[0].node {
"only allow constant integer here at {}", ast::ExprKind::Constant { value: Constant::Int(v), .. } => {
default.location let v: Result<u32, _> = (*v).try_into();
)])), match v {
}, Ok(v) => Ok(SymbolValue::U32(v)),
ast::ExprKind::Name { id, .. } if *id == "uint32".into() => match &args[0].node { _ => Err(format!("default param value out of range at {}", default.location)),
ast::ExprKind::Constant { value: Constant::Int(v), .. } => { }
let v: Result<u32, _> = (*v).try_into();
match v {
Ok(v) => Ok(SymbolValue::U32(v)),
_ => Err(HashSet::from([format!(
"default param value out of range at {}",
default.location
)])),
} }
_ => Err(format!("only allow constant integer here at {}", default.location))
} }
_ => Err(HashSet::from([format!( ast::ExprKind::Name { id, .. } if *id == "uint64".into() => match &args[0].node {
"only allow constant integer here at {}", ast::ExprKind::Constant { value: Constant::Int(v), .. } => {
default.location let v: Result<u64, _> = (*v).try_into();
)])), match v {
}, Ok(v) => Ok(SymbolValue::U64(v)),
ast::ExprKind::Name { id, .. } if *id == "uint64".into() => match &args[0].node { _ => Err(format!("default param value out of range at {}", default.location)),
ast::ExprKind::Constant { value: Constant::Int(v), .. } => { }
let v: Result<u64, _> = (*v).try_into();
match v {
Ok(v) => Ok(SymbolValue::U64(v)),
_ => Err(HashSet::from([format!(
"default param value out of range at {}",
default.location
)])),
} }
_ => Err(format!("only allow constant integer here at {}", default.location))
} }
_ => Err(HashSet::from([format!( ast::ExprKind::Name { id, .. } if *id == "Some".into() => Ok(
"only allow constant integer here at {}", SymbolValue::OptionSome(
default.location Box::new(parse_parameter_default_value(&args[0], resolver)?)
)])), )
}, ),
ast::ExprKind::Name { id, .. } if *id == "Some".into() => Ok(SymbolValue::OptionSome( _ => Err(format!("unsupported default parameter at {}", default.location)),
Box::new(parse_parameter_default_value(&args[0], resolver)?), }
)), }
_ => Err(HashSet::from([format!( ast::ExprKind::Tuple { elts, .. } => Ok(SymbolValue::Tuple(elts
"unsupported default parameter at {}", .iter()
default.location .map(|x| parse_parameter_default_value(x, resolver))
)])), .collect::<Result<Vec<_>, _>>()?
},
ast::ExprKind::Tuple { elts, .. } => Ok(SymbolValue::Tuple(
elts.iter()
.map(|x| parse_parameter_default_value(x, resolver))
.collect::<Result<Vec<_>, _>>()?,
)), )),
ast::ExprKind::Name { id, .. } if id == &"none".into() => Ok(SymbolValue::OptionNone), ast::ExprKind::Name { id, .. } if id == &"none".into() => Ok(SymbolValue::OptionNone),
ast::ExprKind::Name { id, .. } => { ast::ExprKind::Name { id, .. } => {
resolver.get_default_param_value(default).ok_or_else(|| { resolver.get_default_param_value(default).ok_or_else(
HashSet::from([format!( || format!(
"`{}` cannot be used as a default parameter at {} \ "`{}` cannot be used as a default parameter at {} \
(not primitive type, option or tuple / not defined?)", (not primitive type, option or tuple / not defined?)",
id, default.location id,
)]) default.location
}) )
)
} }
_ => Err(HashSet::from([format!( _ => Err(format!(
"unsupported default parameter (not primitive type, option or tuple) at {}", "unsupported default parameter (not primitive type, option or tuple) at {}",
default.location default.location
)])), ))
}
}
/// Obtains the element type of an array-like type.
pub fn arraylike_flatten_element_type(unifier: &mut Unifier, ty: Type) -> Type {
match &*unifier.get_ty(ty) {
TypeEnum::TObj { obj_id, .. } if *obj_id == PrimDef::NDArray.id() => {
unpack_ndarray_var_tys(unifier, ty).0
}
TypeEnum::TObj { obj_id, params, .. } if *obj_id == PrimDef::List.id() => {
arraylike_flatten_element_type(unifier, iter_type_vars(params).next().unwrap().ty)
}
_ => ty,
}
}
/// Obtains the number of dimensions of an array-like type.
pub fn arraylike_get_ndims(unifier: &mut Unifier, ty: Type) -> u64 {
match &*unifier.get_ty(ty) {
TypeEnum::TObj { obj_id, .. } if *obj_id == PrimDef::NDArray.id() => {
let ndims = unpack_ndarray_var_tys(unifier, ty).1;
let TypeEnum::TLiteral { values, .. } = &*unifier.get_ty_immutable(ndims) else {
panic!("Expected TLiteral for ndarray.ndims, got {}", unifier.stringify(ndims))
};
if values.len() > 1 {
todo!("Getting num of dimensions for ndarray with more than one ndim bound is unimplemented")
}
u64::try_from(values[0].clone()).unwrap()
}
TypeEnum::TObj { obj_id, params, .. } if *obj_id == PrimDef::List.id() => {
arraylike_get_ndims(unifier, iter_type_vars(params).next().unwrap().ty) + 1
}
_ => 0,
} }
} }

View File

@ -3,24 +3,20 @@ use std::{
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
fmt::Debug, fmt::Debug,
iter::FromIterator, iter::FromIterator,
ops::{Deref, DerefMut},
sync::Arc, sync::Arc,
}; };
use super::codegen::CodeGenContext; use super::codegen::CodeGenContext;
use super::typecheck::type_inferencer::PrimitiveStore; use super::typecheck::type_inferencer::PrimitiveStore;
use super::typecheck::typedef::{ use super::typecheck::typedef::{FunSignature, FuncArg, SharedUnifier, Type, TypeEnum, Unifier};
FunSignature, FuncArg, SharedUnifier, Type, TypeEnum, Unifier, VarMap,
};
use crate::{ use crate::{
codegen::CodeGenerator, codegen::CodeGenerator,
symbol_resolver::{SymbolResolver, ValueEnum}, symbol_resolver::{SymbolResolver, ValueEnum},
typecheck::{ typecheck::{type_inferencer::CodeLocation, typedef::CallId},
type_inferencer::CodeLocation,
typedef::{CallId, TypeVarId},
},
}; };
use inkwell::values::BasicValueEnum; use inkwell::values::BasicValueEnum;
use itertools::Itertools; use itertools::{izip, Itertools};
use nac3parser::ast::{self, Location, Stmt, StrRef}; use nac3parser::ast::{self, Location, Stmt, StrRef};
use parking_lot::RwLock; use parking_lot::RwLock;
@ -30,43 +26,36 @@ pub struct DefinitionId(pub usize);
pub mod builtins; pub mod builtins;
pub mod composer; pub mod composer;
pub mod helper; pub mod helper;
pub mod numpy;
pub mod type_annotation; pub mod type_annotation;
use composer::*; use composer::*;
use type_annotation::*; use type_annotation::*;
#[cfg(test)] #[cfg(test)]
mod test; mod test;
type GenCallCallback = dyn for<'ctx, 'a> Fn( type GenCallCallback = Box<
&mut CodeGenContext<'ctx, 'a>, dyn for<'ctx, 'a> Fn(
Option<(Type, ValueEnum<'ctx>)>, &mut CodeGenContext<'ctx, 'a>,
(&FunSignature, DefinitionId), Option<(Type, ValueEnum<'ctx>)>,
Vec<(Option<StrRef>, ValueEnum<'ctx>)>, (&FunSignature, DefinitionId),
&mut dyn CodeGenerator, Vec<(Option<StrRef>, ValueEnum<'ctx>)>,
) -> Result<Option<BasicValueEnum<'ctx>>, String> &mut dyn CodeGenerator,
+ Send ) -> Result<Option<BasicValueEnum<'ctx>>, String>
+ Sync; + Send
+ Sync,
>;
pub struct GenCall { pub struct GenCall {
fp: Box<GenCallCallback>, fp: GenCallCallback,
} }
impl GenCall { impl GenCall {
#[must_use] pub fn new(fp: GenCallCallback) -> GenCall {
pub fn new(fp: Box<GenCallCallback>) -> GenCall {
GenCall { fp } GenCall { fp }
} }
/// Creates a dummy instance of [`GenCall`], which invokes [`unreachable!()`] with the given pub fn run<'ctx, 'a>(
/// `reason`.
#[must_use]
pub fn create_dummy(reason: String) -> GenCall {
Self::new(Box::new(move |_, _, _, _, _| unreachable!("{reason}")))
}
pub fn run<'ctx>(
&self, &self,
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, 'a>,
obj: Option<(Type, ValueEnum<'ctx>)>, obj: Option<(Type, ValueEnum<'ctx>)>,
fun: (&FunSignature, DefinitionId), fun: (&FunSignature, DefinitionId),
args: Vec<(Option<StrRef>, ValueEnum<'ctx>)>, args: Vec<(Option<StrRef>, ValueEnum<'ctx>)>,
@ -86,66 +75,58 @@ impl Debug for GenCall {
pub struct FunInstance { pub struct FunInstance {
pub body: Arc<Vec<Stmt<Option<Type>>>>, pub body: Arc<Vec<Stmt<Option<Type>>>>,
pub calls: Arc<HashMap<CodeLocation, CallId>>, pub calls: Arc<HashMap<CodeLocation, CallId>>,
pub subst: VarMap, pub subst: HashMap<u32, Type>,
pub unifier_id: usize, pub unifier_id: usize,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum TopLevelDef { pub enum TopLevelDef {
Class { Class {
/// Name for error messages and symbols. // name for error messages and symbols
name: StrRef, name: StrRef,
/// Object ID used for [`TypeEnum`]. // object ID used for TypeEnum
object_id: DefinitionId, object_id: DefinitionId,
/// type variables bounded to the class. /// type variables bounded to the class.
type_vars: Vec<Type>, type_vars: Vec<Type>,
/// Class fields. // class fields
/// // name, type, is mutable
/// Name and type is mutable.
fields: Vec<(StrRef, Type, bool)>, fields: Vec<(StrRef, Type, bool)>,
/// Class Attributes. // class methods, pointing to the corresponding function definition.
///
/// Name, type, value.
attributes: Vec<(StrRef, Type, ast::Constant)>,
/// Class methods, pointing to the corresponding function definition.
methods: Vec<(StrRef, Type, DefinitionId)>, methods: Vec<(StrRef, Type, DefinitionId)>,
/// Ancestor classes, including itself. // ancestor classes, including itself.
ancestors: Vec<TypeAnnotation>, ancestors: Vec<TypeAnnotation>,
/// Symbol resolver of the module defined the class; [None] if it is built-in type. // symbol resolver of the module defined the class, none if it is built-in type
resolver: Option<Arc<dyn SymbolResolver + Send + Sync>>, resolver: Option<Arc<dyn SymbolResolver + Send + Sync>>,
/// Constructor type. // constructor type
constructor: Option<Type>, constructor: Option<Type>,
/// Definition location. // definition location
loc: Option<Location>, loc: Option<Location>,
}, },
Function { Function {
/// Prefix for symbol, should be unique globally. // prefix for symbol, should be unique globally
name: String, name: String,
/// Simple name, the same as in method/function definition. // simple name, the same as in method/function definition
simple_name: StrRef, simple_name: StrRef,
/// Function signature. // function signature.
signature: Type, signature: Type,
/// Instantiated type variable IDs. // instantiated type variable IDs
var_id: Vec<TypeVarId>, var_id: Vec<u32>,
/// Function instance to symbol mapping /// Function instance to symbol mapping
/// /// Key: string representation of type variable values, sorted by variable ID in ascending
/// * Key: String representation of type variable values, sorted by variable ID in ascending
/// order, including type variables associated with the class. /// order, including type variables associated with the class.
/// * Value: Function symbol name. /// Value: function symbol name.
instance_to_symbol: HashMap<String, String>, instance_to_symbol: HashMap<String, String>,
/// Function instances to annotated AST mapping /// Function instances to annotated AST mapping
/// /// Key: string representation of type variable values, sorted by variable ID in ascending
/// * Key: String representation of type variable values, sorted by variable ID in ascending
/// order, including type variables associated with the class. Excluding rigid type /// order, including type variables associated with the class. Excluding rigid type
/// variables. /// variables.
/// /// rigid type variables that would be substituted when the function is instantiated.
/// Rigid type variables that would be substituted when the function is instantiated.
instance_to_stmt: HashMap<String, FunInstance>, instance_to_stmt: HashMap<String, FunInstance>,
/// Symbol resolver of the module defined the class. // symbol resolver of the module defined the class
resolver: Option<Arc<dyn SymbolResolver + Send + Sync>>, resolver: Option<Arc<dyn SymbolResolver + Send + Sync>>,
/// Custom code generation callback. // custom codegen callback
codegen_callback: Option<Arc<GenCall>>, codegen_callback: Option<Arc<GenCall>>,
/// Definition location. // definition location
loc: Option<Location>, loc: Option<Location>,
}, },
} }

View File

@ -1,85 +0,0 @@
use crate::{
toplevel::helper::PrimDef,
typecheck::{
type_inferencer::PrimitiveStore,
typedef::{Type, TypeEnum, TypeVarId, Unifier, VarMap},
},
};
use itertools::Itertools;
/// Creates a `ndarray` [`Type`] with the given type arguments.
///
/// * `dtype` - The element type of the `ndarray`, or [`None`] if the type variable is not
/// specialized.
/// * `ndims` - The number of dimensions of the `ndarray`, or [`None`] if the type variable is not
/// specialized.
pub fn make_ndarray_ty(
unifier: &mut Unifier,
primitives: &PrimitiveStore,
dtype: Option<Type>,
ndims: Option<Type>,
) -> Type {
subst_ndarray_tvars(unifier, primitives.ndarray, dtype, ndims)
}
/// Substitutes type variables in `ndarray`.
///
/// * `dtype` - The element type of the `ndarray`, or [`None`] if the type variable is not
/// specialized.
/// * `ndims` - The number of dimensions of the `ndarray`, or [`None`] if the type variable is not
/// specialized.
pub fn subst_ndarray_tvars(
unifier: &mut Unifier,
ndarray: Type,
dtype: Option<Type>,
ndims: Option<Type>,
) -> Type {
let TypeEnum::TObj { obj_id, params, .. } = &*unifier.get_ty_immutable(ndarray) else {
panic!("Expected `ndarray` to be TObj, but got {}", unifier.stringify(ndarray))
};
debug_assert_eq!(*obj_id, PrimDef::NDArray.id());
if dtype.is_none() && ndims.is_none() {
return ndarray;
}
let tvar_ids = params.iter().map(|(obj_id, _)| *obj_id).collect_vec();
debug_assert_eq!(tvar_ids.len(), 2);
let mut tvar_subst = VarMap::new();
if let Some(dtype) = dtype {
tvar_subst.insert(tvar_ids[0], dtype);
}
if let Some(ndims) = ndims {
tvar_subst.insert(tvar_ids[1], ndims);
}
unifier.subst(ndarray, &tvar_subst).unwrap_or(ndarray)
}
fn unpack_ndarray_tvars(unifier: &mut Unifier, ndarray: Type) -> Vec<(TypeVarId, Type)> {
let TypeEnum::TObj { obj_id, params, .. } = &*unifier.get_ty_immutable(ndarray) else {
panic!("Expected `ndarray` to be TObj, but got {}", unifier.stringify(ndarray))
};
debug_assert_eq!(*obj_id, PrimDef::NDArray.id());
debug_assert_eq!(params.len(), 2);
params
.iter()
.sorted_by_key(|(obj_id, _)| *obj_id)
.map(|(var_id, ty)| (*var_id, *ty))
.collect_vec()
}
/// Unpacks the type variable IDs of `ndarray` into a tuple. The elements of the tuple corresponds
/// to `dtype` (the element type) and `ndims` (the number of dimensions) of the `ndarray`
/// respectively.
pub fn unpack_ndarray_var_ids(unifier: &mut Unifier, ndarray: Type) -> (TypeVarId, TypeVarId) {
unpack_ndarray_tvars(unifier, ndarray).into_iter().map(|v| v.0).collect_tuple().unwrap()
}
/// Unpacks the type variables of `ndarray` into a tuple. The elements of the tuple corresponds to
/// `dtype` (the element type) and `ndims` (the number of dimensions) of the `ndarray` respectively.
pub fn unpack_ndarray_var_tys(unifier: &mut Unifier, ndarray: Type) -> (Type, Type) {
unpack_ndarray_tvars(unifier, ndarray).into_iter().map(|v| v.1).collect_tuple().unwrap()
}

View File

@ -1,11 +1,13 @@
--- ---
source: nac3core/src/toplevel/test.rs source: nac3core/src/toplevel/test.rs
assertion_line: 549
expression: res_vec expression: res_vec
--- ---
[ [
"Class {\nname: \"Generic_A\",\nancestors: [\"Generic_A[V]\", \"B\"],\nfields: [\"aa\", \"a\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"foo\", \"fn[[b:T], none]\"), (\"fun\", \"fn[[a:int32], V]\")],\ntype_vars: [\"V\"]\n}\n", "Class {\nname: \"Generic_A\",\nancestors: [\"Generic_A[V]\", \"B\"],\nfields: [\"aa\", \"a\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"foo\", \"fn[[b:T], none]\"), (\"fun\", \"fn[[a:int32], V]\")],\ntype_vars: [\"V\"]\n}\n",
"Function {\nname: \"Generic_A.__init__\",\nsig: \"fn[[], none]\",\nvar_id: []\n}\n", "Function {\nname: \"Generic_A.__init__\",\nsig: \"fn[[], none]\",\nvar_id: []\n}\n",
"Function {\nname: \"Generic_A.fun\",\nsig: \"fn[[a:int32], V]\",\nvar_id: [TypeVarId(245)]\n}\n", "Function {\nname: \"Generic_A.fun\",\nsig: \"fn[[a:int32], V]\",\nvar_id: [18]\n}\n",
"Class {\nname: \"B\",\nancestors: [\"B\"],\nfields: [\"aa\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"foo\", \"fn[[b:T], none]\")],\ntype_vars: []\n}\n", "Class {\nname: \"B\",\nancestors: [\"B\"],\nfields: [\"aa\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"foo\", \"fn[[b:T], none]\")],\ntype_vars: []\n}\n",
"Function {\nname: \"B.__init__\",\nsig: \"fn[[], none]\",\nvar_id: []\n}\n", "Function {\nname: \"B.__init__\",\nsig: \"fn[[], none]\",\nvar_id: []\n}\n",
"Function {\nname: \"B.foo\",\nsig: \"fn[[b:T], none]\",\nvar_id: []\n}\n", "Function {\nname: \"B.foo\",\nsig: \"fn[[b:T], none]\",\nvar_id: []\n}\n",

View File

@ -1,13 +1,15 @@
--- ---
source: nac3core/src/toplevel/test.rs source: nac3core/src/toplevel/test.rs
assertion_line: 549
expression: res_vec expression: res_vec
--- ---
[ [
"Class {\nname: \"A\",\nancestors: [\"A[T]\"],\nfields: [\"a\", \"b\", \"c\"],\nmethods: [(\"__init__\", \"fn[[t:T], none]\"), (\"fun\", \"fn[[a:int32, b:T], list[virtual[B[bool]]]]\"), (\"foo\", \"fn[[c:C], none]\")],\ntype_vars: [\"T\"]\n}\n", "Class {\nname: \"A\",\nancestors: [\"A[T]\"],\nfields: [\"a\", \"b\", \"c\"],\nmethods: [(\"__init__\", \"fn[[t:T], none]\"), (\"fun\", \"fn[[a:int32, b:T], list[virtual[B[bool]]]]\"), (\"foo\", \"fn[[c:C], none]\")],\ntype_vars: [\"T\"]\n}\n",
"Function {\nname: \"A.__init__\",\nsig: \"fn[[t:T], none]\",\nvar_id: []\n}\n", "Function {\nname: \"A.__init__\",\nsig: \"fn[[t:T], none]\",\nvar_id: []\n}\n",
"Function {\nname: \"A.fun\",\nsig: \"fn[[a:int32, b:T], list[virtual[B[bool]]]]\",\nvar_id: []\n}\n", "Function {\nname: \"A.fun\",\nsig: \"fn[[a:int32, b:T], list[virtual[B[bool]]]]\",\nvar_id: []\n}\n",
"Function {\nname: \"A.foo\",\nsig: \"fn[[c:C], none]\",\nvar_id: []\n}\n", "Function {\nname: \"A.foo\",\nsig: \"fn[[c:C], none]\",\nvar_id: []\n}\n",
"Class {\nname: \"B\",\nancestors: [\"B[typevar234]\", \"A[float]\"],\nfields: [\"a\", \"b\", \"c\", \"d\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"fun\", \"fn[[a:int32, b:T], list[virtual[B[bool]]]]\"), (\"foo\", \"fn[[c:C], none]\")],\ntype_vars: [\"typevar234\"]\n}\n", "Class {\nname: \"B\",\nancestors: [\"B[typevar7]\", \"A[float]\"],\nfields: [\"a\", \"b\", \"c\", \"d\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"fun\", \"fn[[a:int32, b:T], list[virtual[B[bool]]]]\"), (\"foo\", \"fn[[c:C], none]\")],\ntype_vars: [\"typevar7\"]\n}\n",
"Function {\nname: \"B.__init__\",\nsig: \"fn[[], none]\",\nvar_id: []\n}\n", "Function {\nname: \"B.__init__\",\nsig: \"fn[[], none]\",\nvar_id: []\n}\n",
"Function {\nname: \"B.fun\",\nsig: \"fn[[a:int32, b:T], list[virtual[B[bool]]]]\",\nvar_id: []\n}\n", "Function {\nname: \"B.fun\",\nsig: \"fn[[a:int32, b:T], list[virtual[B[bool]]]]\",\nvar_id: []\n}\n",
"Class {\nname: \"C\",\nancestors: [\"C\", \"B[bool]\", \"A[float]\"],\nfields: [\"a\", \"b\", \"c\", \"d\", \"e\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"fun\", \"fn[[a:int32, b:T], list[virtual[B[bool]]]]\"), (\"foo\", \"fn[[c:C], none]\")],\ntype_vars: []\n}\n", "Class {\nname: \"C\",\nancestors: [\"C\", \"B[bool]\", \"A[float]\"],\nfields: [\"a\", \"b\", \"c\", \"d\", \"e\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"fun\", \"fn[[a:int32, b:T], list[virtual[B[bool]]]]\"), (\"foo\", \"fn[[c:C], none]\")],\ntype_vars: []\n}\n",

View File

@ -1,13 +1,15 @@
--- ---
source: nac3core/src/toplevel/test.rs source: nac3core/src/toplevel/test.rs
assertion_line: 549
expression: res_vec expression: res_vec
--- ---
[ [
"Function {\nname: \"foo\",\nsig: \"fn[[a:list[int32], b:tuple[T, float]], A[B, bool]]\",\nvar_id: []\n}\n", "Function {\nname: \"foo\",\nsig: \"fn[[a:list[int32], b:tuple[T, float]], A[B, bool]]\",\nvar_id: []\n}\n",
"Class {\nname: \"A\",\nancestors: [\"A[T, V]\"],\nfields: [\"a\", \"b\"],\nmethods: [(\"__init__\", \"fn[[v:V], none]\"), (\"fun\", \"fn[[a:T], V]\")],\ntype_vars: [\"T\", \"V\"]\n}\n", "Class {\nname: \"A\",\nancestors: [\"A[T, V]\"],\nfields: [\"a\", \"b\"],\nmethods: [(\"__init__\", \"fn[[v:V], none]\"), (\"fun\", \"fn[[a:T], V]\")],\ntype_vars: [\"T\", \"V\"]\n}\n",
"Function {\nname: \"A.__init__\",\nsig: \"fn[[v:V], none]\",\nvar_id: [TypeVarId(247)]\n}\n", "Function {\nname: \"A.__init__\",\nsig: \"fn[[v:V], none]\",\nvar_id: [20]\n}\n",
"Function {\nname: \"A.fun\",\nsig: \"fn[[a:T], V]\",\nvar_id: [TypeVarId(252)]\n}\n", "Function {\nname: \"A.fun\",\nsig: \"fn[[a:T], V]\",\nvar_id: [25]\n}\n",
"Function {\nname: \"gfun\",\nsig: \"fn[[a:A[list[float], int32]], none]\",\nvar_id: []\n}\n", "Function {\nname: \"gfun\",\nsig: \"fn[[a:A[int32, list[float]]], none]\",\nvar_id: []\n}\n",
"Class {\nname: \"B\",\nancestors: [\"B\"],\nfields: [],\nmethods: [(\"__init__\", \"fn[[], none]\")],\ntype_vars: []\n}\n", "Class {\nname: \"B\",\nancestors: [\"B\"],\nfields: [],\nmethods: [(\"__init__\", \"fn[[], none]\")],\ntype_vars: []\n}\n",
"Function {\nname: \"B.__init__\",\nsig: \"fn[[], none]\",\nvar_id: []\n}\n", "Function {\nname: \"B.__init__\",\nsig: \"fn[[], none]\",\nvar_id: []\n}\n",
] ]

View File

@ -1,13 +1,15 @@
--- ---
source: nac3core/src/toplevel/test.rs source: nac3core/src/toplevel/test.rs
assertion_line: 549
expression: res_vec expression: res_vec
--- ---
[ [
"Class {\nname: \"A\",\nancestors: [\"A[typevar233, typevar234]\"],\nfields: [\"a\", \"b\"],\nmethods: [(\"__init__\", \"fn[[a:A[float, bool], b:B], none]\"), (\"fun\", \"fn[[a:A[float, bool]], A[bool, int32]]\")],\ntype_vars: [\"typevar233\", \"typevar234\"]\n}\n", "Class {\nname: \"A\",\nancestors: [\"A[typevar6, typevar7]\"],\nfields: [\"a\", \"b\"],\nmethods: [(\"__init__\", \"fn[[a:A[bool, float], b:B], none]\"), (\"fun\", \"fn[[a:A[bool, float]], A[bool, int32]]\")],\ntype_vars: [\"typevar6\", \"typevar7\"]\n}\n",
"Function {\nname: \"A.__init__\",\nsig: \"fn[[a:A[float, bool], b:B], none]\",\nvar_id: []\n}\n", "Function {\nname: \"A.__init__\",\nsig: \"fn[[a:A[bool, float], b:B], none]\",\nvar_id: []\n}\n",
"Function {\nname: \"A.fun\",\nsig: \"fn[[a:A[float, bool]], A[bool, int32]]\",\nvar_id: []\n}\n", "Function {\nname: \"A.fun\",\nsig: \"fn[[a:A[bool, float]], A[bool, int32]]\",\nvar_id: []\n}\n",
"Class {\nname: \"B\",\nancestors: [\"B\", \"A[int64, bool]\"],\nfields: [\"a\", \"b\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"fun\", \"fn[[a:A[float, bool]], A[bool, int32]]\"), (\"foo\", \"fn[[b:B], B]\"), (\"bar\", \"fn[[a:A[list[B], int32]], tuple[A[virtual[A[B, int32]], bool], B]]\")],\ntype_vars: []\n}\n", "Class {\nname: \"B\",\nancestors: [\"B\", \"A[int64, bool]\"],\nfields: [\"a\", \"b\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"fun\", \"fn[[a:A[bool, float]], A[bool, int32]]\"), (\"foo\", \"fn[[b:B], B]\"), (\"bar\", \"fn[[a:A[int32, list[B]]], tuple[A[bool, virtual[A[B, int32]]], B]]\")],\ntype_vars: []\n}\n",
"Function {\nname: \"B.__init__\",\nsig: \"fn[[], none]\",\nvar_id: []\n}\n", "Function {\nname: \"B.__init__\",\nsig: \"fn[[], none]\",\nvar_id: []\n}\n",
"Function {\nname: \"B.foo\",\nsig: \"fn[[b:B], B]\",\nvar_id: []\n}\n", "Function {\nname: \"B.foo\",\nsig: \"fn[[b:B], B]\",\nvar_id: []\n}\n",
"Function {\nname: \"B.bar\",\nsig: \"fn[[a:A[list[B], int32]], tuple[A[virtual[A[B, int32]], bool], B]]\",\nvar_id: []\n}\n", "Function {\nname: \"B.bar\",\nsig: \"fn[[a:A[int32, list[B]]], tuple[A[bool, virtual[A[B, int32]]], B]]\",\nvar_id: []\n}\n",
] ]

View File

@ -1,17 +1,19 @@
--- ---
source: nac3core/src/toplevel/test.rs source: nac3core/src/toplevel/test.rs
assertion_line: 549
expression: res_vec expression: res_vec
--- ---
[ [
"Class {\nname: \"A\",\nancestors: [\"A\"],\nfields: [\"a\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"fun\", \"fn[[b:B], none]\"), (\"foo\", \"fn[[a:T, b:V], none]\")],\ntype_vars: []\n}\n", "Class {\nname: \"A\",\nancestors: [\"A\"],\nfields: [\"a\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"fun\", \"fn[[b:B], none]\"), (\"foo\", \"fn[[a:T, b:V], none]\")],\ntype_vars: []\n}\n",
"Function {\nname: \"A.__init__\",\nsig: \"fn[[], none]\",\nvar_id: []\n}\n", "Function {\nname: \"A.__init__\",\nsig: \"fn[[], none]\",\nvar_id: []\n}\n",
"Function {\nname: \"A.fun\",\nsig: \"fn[[b:B], none]\",\nvar_id: []\n}\n", "Function {\nname: \"A.fun\",\nsig: \"fn[[b:B], none]\",\nvar_id: []\n}\n",
"Function {\nname: \"A.foo\",\nsig: \"fn[[a:T, b:V], none]\",\nvar_id: [TypeVarId(253)]\n}\n", "Function {\nname: \"A.foo\",\nsig: \"fn[[a:T, b:V], none]\",\nvar_id: [26]\n}\n",
"Class {\nname: \"B\",\nancestors: [\"B\", \"C\", \"A\"],\nfields: [\"a\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"fun\", \"fn[[b:B], none]\"), (\"foo\", \"fn[[a:T, b:V], none]\")],\ntype_vars: []\n}\n", "Class {\nname: \"B\",\nancestors: [\"B\", \"C\", \"A\"],\nfields: [\"a\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"fun\", \"fn[[b:B], none]\"), (\"foo\", \"fn[[a:T, b:V], none]\")],\ntype_vars: []\n}\n",
"Function {\nname: \"B.__init__\",\nsig: \"fn[[], none]\",\nvar_id: []\n}\n", "Function {\nname: \"B.__init__\",\nsig: \"fn[[], none]\",\nvar_id: []\n}\n",
"Class {\nname: \"C\",\nancestors: [\"C\", \"A\"],\nfields: [\"a\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"fun\", \"fn[[b:B], none]\"), (\"foo\", \"fn[[a:T, b:V], none]\")],\ntype_vars: []\n}\n", "Class {\nname: \"C\",\nancestors: [\"C\", \"A\"],\nfields: [\"a\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"fun\", \"fn[[b:B], none]\"), (\"foo\", \"fn[[a:T, b:V], none]\")],\ntype_vars: []\n}\n",
"Function {\nname: \"C.__init__\",\nsig: \"fn[[], none]\",\nvar_id: []\n}\n", "Function {\nname: \"C.__init__\",\nsig: \"fn[[], none]\",\nvar_id: []\n}\n",
"Function {\nname: \"C.fun\",\nsig: \"fn[[b:B], none]\",\nvar_id: []\n}\n", "Function {\nname: \"C.fun\",\nsig: \"fn[[b:B], none]\",\nvar_id: []\n}\n",
"Function {\nname: \"foo\",\nsig: \"fn[[a:A], none]\",\nvar_id: []\n}\n", "Function {\nname: \"foo\",\nsig: \"fn[[a:A], none]\",\nvar_id: []\n}\n",
"Function {\nname: \"ff\",\nsig: \"fn[[a:T], V]\",\nvar_id: [TypeVarId(261)]\n}\n", "Function {\nname: \"ff\",\nsig: \"fn[[a:T], V]\",\nvar_id: [34]\n}\n",
] ]

View File

@ -1,6 +1,3 @@
use super::*;
use crate::toplevel::helper::PrimDef;
use crate::typecheck::typedef::into_var_map;
use crate::{ use crate::{
codegen::CodeGenContext, codegen::CodeGenContext,
symbol_resolver::{SymbolResolver, ValueEnum}, symbol_resolver::{SymbolResolver, ValueEnum},
@ -11,12 +8,13 @@ use crate::{
}, },
}; };
use indoc::indoc; use indoc::indoc;
use nac3parser::ast::FileName;
use nac3parser::{ast::fold::Fold, parser::parse_program}; use nac3parser::{ast::fold::Fold, parser::parse_program};
use parking_lot::Mutex; use parking_lot::Mutex;
use std::{collections::HashMap, sync::Arc}; use std::{collections::HashMap, sync::Arc};
use test_case::test_case; use test_case::test_case;
use super::*;
struct ResolverInternal { struct ResolverInternal {
id_to_type: Mutex<HashMap<StrRef, Type>>, id_to_type: Mutex<HashMap<StrRef, Type>>,
id_to_def: Mutex<HashMap<StrRef, DefinitionId>>, id_to_def: Mutex<HashMap<StrRef, DefinitionId>>,
@ -38,7 +36,7 @@ struct Resolver(Arc<ResolverInternal>);
impl SymbolResolver for Resolver { impl SymbolResolver for Resolver {
fn get_default_param_value( fn get_default_param_value(
&self, &self,
_: &ast::Expr, _: &nac3parser::ast::Expr,
) -> Option<crate::symbol_resolver::SymbolValue> { ) -> Option<crate::symbol_resolver::SymbolValue> {
unimplemented!() unimplemented!()
} }
@ -54,25 +52,20 @@ impl SymbolResolver for Resolver {
.id_to_type .id_to_type
.lock() .lock()
.get(&str) .get(&str)
.copied() .cloned()
.ok_or_else(|| format!("cannot find symbol `{str}`")) .ok_or_else(|| format!("cannot find symbol `{}`", str))
} }
fn get_symbol_value<'ctx>( fn get_symbol_value<'ctx, 'a>(
&self, &self,
_: StrRef, _: StrRef,
_: &mut CodeGenContext<'ctx, '_>, _: &mut CodeGenContext<'ctx, 'a>,
) -> Option<ValueEnum<'ctx>> { ) -> Option<ValueEnum<'ctx>> {
unimplemented!() unimplemented!()
} }
fn get_identifier_def(&self, id: StrRef) -> Result<DefinitionId, HashSet<String>> { fn get_identifier_def(&self, id: StrRef) -> Result<DefinitionId, String> {
self.0 self.0.id_to_def.lock().get(&id).cloned().ok_or_else(|| "Unknown identifier".to_string())
.id_to_def
.lock()
.get(&id)
.copied()
.ok_or_else(|| HashSet::from(["Unknown identifier".to_string()]))
} }
fn get_string_id(&self, _: &str) -> i32 { fn get_string_id(&self, _: &str) -> i32 {
@ -117,13 +110,13 @@ impl SymbolResolver for Resolver {
"register" "register"
)] )]
fn test_simple_register(source: Vec<&str>) { fn test_simple_register(source: Vec<&str>) {
let mut composer = TopLevelComposer::new(Vec::new(), ComposerConfig::default(), 64).0; let mut composer: TopLevelComposer = Default::default();
for s in source { for s in source {
let ast = parse_program(s, FileName::default()).unwrap(); let ast = parse_program(s, Default::default()).unwrap();
let ast = ast[0].clone(); let ast = ast[0].clone();
composer.register_top_level(ast, None, "", false).unwrap(); composer.register_top_level(ast, None, "".into(), false).unwrap();
} }
} }
@ -137,14 +130,14 @@ fn test_simple_register(source: Vec<&str>) {
"register" "register"
)] )]
fn test_simple_register_without_constructor(source: &str) { fn test_simple_register_without_constructor(source: &str) {
let mut composer = TopLevelComposer::new(Vec::new(), ComposerConfig::default(), 64).0; let mut composer: TopLevelComposer = Default::default();
let ast = parse_program(source, FileName::default()).unwrap(); let ast = parse_program(source, Default::default()).unwrap();
let ast = ast[0].clone(); let ast = ast[0].clone();
composer.register_top_level(ast, None, "", true).unwrap(); composer.register_top_level(ast, None, "".into(), true).unwrap();
} }
#[test_case( #[test_case(
&[ vec![
indoc! {" indoc! {"
def fun(a: int32) -> int32: def fun(a: int32) -> int32:
return a return a
@ -158,35 +151,35 @@ fn test_simple_register_without_constructor(source: &str) {
return 3 return 3
"}, "},
], ],
&[ vec![
"fn[[a:0], 0]", "fn[[a:0], 0]",
"fn[[a:2], 4]", "fn[[a:2], 4]",
"fn[[b:1], 0]", "fn[[b:1], 0]",
], ],
&[ vec![
"fun", "fun",
"foo", "foo",
"f" "f"
]; ];
"function compose" "function compose"
)] )]
fn test_simple_function_analyze(source: &[&str], tys: &[&str], names: &[&str]) { fn test_simple_function_analyze(source: Vec<&str>, tys: Vec<&str>, names: Vec<&str>) {
let mut composer = TopLevelComposer::new(Vec::new(), ComposerConfig::default(), 64).0; let mut composer: TopLevelComposer = Default::default();
let internal_resolver = Arc::new(ResolverInternal { let internal_resolver = Arc::new(ResolverInternal {
id_to_def: Mutex::default(), id_to_def: Default::default(),
id_to_type: Mutex::default(), id_to_type: Default::default(),
class_names: Mutex::default(), class_names: Default::default(),
}); });
let resolver = let resolver =
Arc::new(Resolver(internal_resolver.clone())) as Arc<dyn SymbolResolver + Send + Sync>; Arc::new(Resolver(internal_resolver.clone())) as Arc<dyn SymbolResolver + Send + Sync>;
for s in source { for s in source {
let ast = parse_program(s, FileName::default()).unwrap(); let ast = parse_program(s, Default::default()).unwrap();
let ast = ast[0].clone(); let ast = ast[0].clone();
let (id, def_id, ty) = let (id, def_id, ty) =
composer.register_top_level(ast, Some(resolver.clone()), "", false).unwrap(); composer.register_top_level(ast, Some(resolver.clone()), "".into(), false).unwrap();
internal_resolver.add_id_def(id, def_id); internal_resolver.add_id_def(id, def_id);
if let Some(ty) = ty { if let Some(ty) = ty {
internal_resolver.add_id_type(id, ty); internal_resolver.add_id_type(id, ty);
@ -212,7 +205,7 @@ fn test_simple_function_analyze(source: &[&str], tys: &[&str], names: &[&str]) {
} }
#[test_case( #[test_case(
&[ vec![
indoc! {" indoc! {"
class A(): class A():
a: int32 a: int32
@ -245,11 +238,11 @@ fn test_simple_function_analyze(source: &[&str], tys: &[&str], names: &[&str]) {
pass pass
"} "}
], ],
&[]; vec![];
"simple class compose" "simple class compose"
)] )]
#[test_case( #[test_case(
&[ vec![
indoc! {" indoc! {"
class Generic_A(Generic[V], B): class Generic_A(Generic[V], B):
a: int64 a: int64
@ -267,11 +260,11 @@ fn test_simple_function_analyze(source: &[&str], tys: &[&str], names: &[&str]) {
pass pass
"} "}
], ],
&[]; vec![];
"generic class" "generic class"
)] )]
#[test_case( #[test_case(
&[ vec![
indoc! {" indoc! {"
def foo(a: list[int32], b: tuple[T, float]) -> A[B, bool]: def foo(a: list[int32], b: tuple[T, float]) -> A[B, bool]:
pass pass
@ -296,11 +289,11 @@ fn test_simple_function_analyze(source: &[&str], tys: &[&str], names: &[&str]) {
pass pass
"} "}
], ],
&[]; vec![];
"list tuple generic" "list tuple generic"
)] )]
#[test_case( #[test_case(
&[ vec![
indoc! {" indoc! {"
class A(Generic[T, V]): class A(Generic[T, V]):
a: A[float, bool] a: A[float, bool]
@ -321,11 +314,11 @@ fn test_simple_function_analyze(source: &[&str], tys: &[&str], names: &[&str]) {
pass pass
"} "}
], ],
&[]; vec![];
"self1" "self1"
)] )]
#[test_case( #[test_case(
&[ vec![
indoc! {" indoc! {"
class A(Generic[T]): class A(Generic[T]):
a: int32 a: int32
@ -355,11 +348,11 @@ fn test_simple_function_analyze(source: &[&str], tys: &[&str], names: &[&str]) {
pass pass
"} "}
], ],
&[]; vec![];
"inheritance_override" "inheritance_override"
)] )]
#[test_case( #[test_case(
&[ vec![
indoc! {" indoc! {"
class A(Generic[T]): class A(Generic[T]):
def __init__(self): def __init__(self):
@ -368,11 +361,11 @@ fn test_simple_function_analyze(source: &[&str], tys: &[&str], names: &[&str]) {
pass pass
"} "}
], ],
&["application of type vars to generic class is not currently supported (at unknown:4:24)"]; vec!["application of type vars to generic class is not currently supported (at unknown: line 4 column 24)"];
"err no type var in generic app" "err no type var in generic app"
)] )]
#[test_case( #[test_case(
&[ vec![
indoc! {" indoc! {"
class A(B): class A(B):
def __init__(self): def __init__(self):
@ -384,11 +377,11 @@ fn test_simple_function_analyze(source: &[&str], tys: &[&str], names: &[&str]) {
pass pass
"} "}
], ],
&["cyclic inheritance detected"]; vec!["cyclic inheritance detected"];
"cyclic1" "cyclic1"
)] )]
#[test_case( #[test_case(
&[ vec![
indoc! {" indoc! {"
class A(B[bool, int64]): class A(B[bool, int64]):
def __init__(self): def __init__(self):
@ -405,30 +398,30 @@ fn test_simple_function_analyze(source: &[&str], tys: &[&str], names: &[&str]) {
pass pass
"}, "},
], ],
&["cyclic inheritance detected"]; vec!["cyclic inheritance detected"];
"cyclic2" "cyclic2"
)] )]
#[test_case( #[test_case(
&[ vec![
indoc! {" indoc! {"
class A: class A:
pass pass
"} "}
], ],
&["5: Class {\nname: \"A\",\ndef_id: DefinitionId(5),\nancestors: [CustomClassKind { id: DefinitionId(5), params: [] }],\nfields: [],\nmethods: [],\ntype_vars: []\n}"]; vec!["5: Class {\nname: \"A\",\ndef_id: DefinitionId(5),\nancestors: [CustomClassKind { id: DefinitionId(5), params: [] }],\nfields: [],\nmethods: [],\ntype_vars: []\n}"];
"simple pass in class" "simple pass in class"
)] )]
#[test_case( #[test_case(
&[indoc! {" vec![indoc! {"
class A: class A:
def __init__(): def __init__():
pass pass
"}], "}],
&["__init__ method must have a `self` parameter (at unknown:2:5)"]; vec!["__init__ method must have a `self` parameter (at unknown: line 2 column 5)"];
"err no self_1" "err no self_1"
)] )]
#[test_case( #[test_case(
&[ vec![
indoc! {" indoc! {"
class A(B, Generic[T], C): class A(B, Generic[T], C):
def __init__(self): def __init__(self):
@ -446,11 +439,11 @@ fn test_simple_function_analyze(source: &[&str], tys: &[&str], names: &[&str]) {
"} "}
], ],
&["a class definition can only have at most one base class declaration and one generic declaration (at unknown:1:24)"]; vec!["a class definition can only have at most one base class declaration and one generic declaration (at unknown: line 1 column 24)"];
"err multiple inheritance" "err multiple inheritance"
)] )]
#[test_case( #[test_case(
&[ vec![
indoc! {" indoc! {"
class A(Generic[T]): class A(Generic[T]):
a: int32 a: int32
@ -471,11 +464,11 @@ fn test_simple_function_analyze(source: &[&str], tys: &[&str], names: &[&str]) {
pass pass
"} "}
], ],
&["method fun has same name as ancestors' method, but incompatible type"]; vec!["method fun has same name as ancestors' method, but incompatible type"];
"err_incompatible_inheritance_method" "err_incompatible_inheritance_method"
)] )]
#[test_case( #[test_case(
&[ vec![
indoc! {" indoc! {"
class A(Generic[T]): class A(Generic[T]):
a: int32 a: int32
@ -497,11 +490,11 @@ fn test_simple_function_analyze(source: &[&str], tys: &[&str], names: &[&str]) {
pass pass
"} "}
], ],
&["field `a` has already declared in the ancestor classes"]; vec!["field `a` has already declared in the ancestor classes"];
"err_incompatible_inheritance_field" "err_incompatible_inheritance_field"
)] )]
#[test_case( #[test_case(
&[ vec![
indoc! {" indoc! {"
class A: class A:
def __init__(self): def __init__(self):
@ -514,12 +507,12 @@ fn test_simple_function_analyze(source: &[&str], tys: &[&str], names: &[&str]) {
pass pass
"} "}
], ],
&["duplicate definition of class `A` (at unknown:1:1)"]; vec!["duplicate definition of class `A` (at unknown: line 1 column 1)"];
"class same name" "class same name"
)] )]
fn test_analyze(source: &[&str], res: &[&str]) { fn test_analyze(source: Vec<&str>, res: Vec<&str>) {
let print = false; let print = false;
let mut composer = TopLevelComposer::new(Vec::new(), ComposerConfig::default(), 64).0; let mut composer: TopLevelComposer = Default::default();
let internal_resolver = make_internal_resolver_with_tvar( let internal_resolver = make_internal_resolver_with_tvar(
vec![ vec![
@ -534,15 +527,15 @@ fn test_analyze(source: &[&str], res: &[&str]) {
Arc::new(Resolver(internal_resolver.clone())) as Arc<dyn SymbolResolver + Send + Sync>; Arc::new(Resolver(internal_resolver.clone())) as Arc<dyn SymbolResolver + Send + Sync>;
for s in source { for s in source {
let ast = parse_program(s, FileName::default()).unwrap(); let ast = parse_program(s, Default::default()).unwrap();
let ast = ast[0].clone(); let ast = ast[0].clone();
let (id, def_id, ty) = { let (id, def_id, ty) = {
match composer.register_top_level(ast, Some(resolver.clone()), "", false) { match composer.register_top_level(ast, Some(resolver.clone()), "".into(), false) {
Ok(x) => x, Ok(x) => x,
Err(msg) => { Err(msg) => {
if print { if print {
println!("{msg}"); println!("{}", msg);
} else { } else {
assert_eq!(res[0], msg); assert_eq!(res[0], msg);
} }
@ -558,9 +551,9 @@ fn test_analyze(source: &[&str], res: &[&str]) {
if let Err(msg) = composer.start_analysis(false) { if let Err(msg) = composer.start_analysis(false) {
if print { if print {
println!("{}", msg.iter().sorted().join("\n----------\n")); println!("{}", msg);
} else { } else {
assert_eq!(res[0], msg.iter().next().unwrap()); assert_eq!(res[0], msg);
} }
} else { } else {
// skip 5 to skip primitives // skip 5 to skip primitives
@ -588,7 +581,7 @@ fn test_analyze(source: &[&str], res: &[&str]) {
return fib(n - 1) return fib(n - 1)
"} "}
], ],
&[]; vec![];
"simple function" "simple function"
)] )]
#[test_case( #[test_case(
@ -621,7 +614,7 @@ fn test_analyze(source: &[&str], res: &[&str]) {
return a.fun() + 2 return a.fun() + 2
"} "}
], ],
&[]; vec![];
"simple class body" "simple class body"
)] )]
#[test_case( #[test_case(
@ -646,7 +639,7 @@ fn test_analyze(source: &[&str], res: &[&str]) {
return [a, b] return [a, b]
"} "}
], ],
&[]; vec![];
"type var fun" "type var fun"
)] )]
#[test_case( #[test_case(
@ -667,7 +660,7 @@ fn test_analyze(source: &[&str], res: &[&str]) {
return ret if self.b else self.fun(self.a) return ret if self.b else self.fun(self.a)
"} "}
], ],
&[]; vec![];
"type var class" "type var class"
)] )]
#[test_case( #[test_case(
@ -691,12 +684,12 @@ fn test_analyze(source: &[&str], res: &[&str]) {
self.b = True self.b = True
"} "}
], ],
&[]; vec![];
"no_init_inst_check" "no_init_inst_check"
)] )]
fn test_inference(source: Vec<&str>, res: &[&str]) { fn test_inference(source: Vec<&str>, res: Vec<&str>) {
let print = true; let print = true;
let mut composer = TopLevelComposer::new(Vec::new(), ComposerConfig::default(), 64).0; let mut composer: TopLevelComposer = Default::default();
let internal_resolver = make_internal_resolver_with_tvar( let internal_resolver = make_internal_resolver_with_tvar(
vec![ vec![
@ -718,15 +711,15 @@ fn test_inference(source: Vec<&str>, res: &[&str]) {
Arc::new(Resolver(internal_resolver.clone())) as Arc<dyn SymbolResolver + Send + Sync>; Arc::new(Resolver(internal_resolver.clone())) as Arc<dyn SymbolResolver + Send + Sync>;
for s in source { for s in source {
let ast = parse_program(s, FileName::default()).unwrap(); let ast = parse_program(s, Default::default()).unwrap();
let ast = ast[0].clone(); let ast = ast[0].clone();
let (id, def_id, ty) = { let (id, def_id, ty) = {
match composer.register_top_level(ast, Some(resolver.clone()), "", false) { match composer.register_top_level(ast, Some(resolver.clone()), "".into(), false) {
Ok(x) => x, Ok(x) => x,
Err(msg) => { Err(msg) => {
if print { if print {
println!("{msg}"); println!("{}", msg);
} else { } else {
assert_eq!(res[0], msg); assert_eq!(res[0], msg);
} }
@ -742,14 +735,16 @@ fn test_inference(source: Vec<&str>, res: &[&str]) {
if let Err(msg) = composer.start_analysis(true) { if let Err(msg) = composer.start_analysis(true) {
if print { if print {
println!("{}", msg.iter().sorted().join("\n----------\n")); println!("{}", msg);
} else { } else {
assert_eq!(res[0], msg.iter().next().unwrap()); assert_eq!(res[0], msg);
} }
} else { } else {
// skip 5 to skip primitives // skip 5 to skip primitives
let mut stringify_folder = TypeToStringFolder { unifier: &mut composer.unifier }; let mut stringify_folder = TypeToStringFolder { unifier: &mut composer.unifier };
for (def, _) in composer.definition_ast_list.iter().skip(composer.builtin_num) { for (_i, (def, _)) in
composer.definition_ast_list.iter().skip(composer.builtin_num).enumerate()
{
let def = &*def.read(); let def = &*def.read();
if let TopLevelDef::Function { instance_to_stmt, name, .. } = def { if let TopLevelDef::Function { instance_to_stmt, name, .. } = def {
@ -758,7 +753,7 @@ fn test_inference(source: Vec<&str>, res: &[&str]) {
name, name,
instance_to_stmt.len() instance_to_stmt.len()
); );
for inst in instance_to_stmt { for inst in instance_to_stmt.iter() {
let ast = &inst.1.body; let ast = &inst.1.body;
for b in ast.iter() { for b in ast.iter() {
println!("{:?}", stringify_folder.fold_stmt(b.clone()).unwrap()); println!("{:?}", stringify_folder.fold_stmt(b.clone()).unwrap());
@ -776,29 +771,22 @@ fn make_internal_resolver_with_tvar(
unifier: &mut Unifier, unifier: &mut Unifier,
print: bool, print: bool,
) -> Arc<ResolverInternal> { ) -> Arc<ResolverInternal> {
let list_elem_tvar = unifier.get_fresh_var(Some("list_elem".into()), None);
let list = unifier.add_ty(TypeEnum::TObj {
obj_id: PrimDef::List.id(),
fields: HashMap::new(),
params: into_var_map([list_elem_tvar]),
});
let res: Arc<ResolverInternal> = ResolverInternal { let res: Arc<ResolverInternal> = ResolverInternal {
id_to_def: Mutex::new(HashMap::from([("list".into(), PrimDef::List.id())])), id_to_def: Default::default(),
id_to_type: tvars id_to_type: tvars
.into_iter() .into_iter()
.map(|(name, range)| { .map(|(name, range)| {
(name, { (name, {
let tvar = unifier.get_fresh_var_with_range(range.as_slice(), None, None); let (ty, id) = unifier.get_fresh_var_with_range(range.as_slice(), None, None);
if print { if print {
println!("{}: {:?}, typevar{}", name, tvar.ty, tvar.id); println!("{}: {:?}, typevar{}", name, ty, id);
} }
tvar.ty ty
}) })
}) })
.collect::<HashMap<_, _>>() .collect::<HashMap<_, _>>()
.into(), .into(),
class_names: Mutex::new(HashMap::from([("list".into(), list)])), class_names: Default::default(),
} }
.into(); .into();
if print { if print {
@ -818,8 +806,8 @@ impl<'a> Fold<Option<Type>> for TypeToStringFolder<'a> {
Ok(if let Some(ty) = user { Ok(if let Some(ty) = user {
self.unifier.internal_stringify( self.unifier.internal_stringify(
ty, ty,
&mut |id| format!("class{id}"), &mut |id| format!("class{}", id.to_string()),
&mut |id| format!("typevar{id}"), &mut |id| format!("typevar{}", id.to_string()),
&mut None, &mut None,
) )
} else { } else {

View File

@ -1,8 +1,4 @@
use super::*; use super::*;
use crate::symbol_resolver::SymbolValue;
use crate::toplevel::helper::PrimDef;
use crate::typecheck::typedef::VarMap;
use nac3parser::ast::Constant;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum TypeAnnotation { pub enum TypeAnnotation {
@ -16,8 +12,7 @@ pub enum TypeAnnotation {
// can only be CustomClassKind // can only be CustomClassKind
Virtual(Box<TypeAnnotation>), Virtual(Box<TypeAnnotation>),
TypeVar(Type), TypeVar(Type),
/// A `Literal` allowing a subset of literals. List(Box<TypeAnnotation>),
Literal(Vec<Constant>),
Tuple(Vec<TypeAnnotation>), Tuple(Vec<TypeAnnotation>),
} }
@ -27,57 +22,52 @@ impl TypeAnnotation {
match self { match self {
Primitive(ty) | TypeVar(ty) => unifier.stringify(*ty), Primitive(ty) | TypeVar(ty) => unifier.stringify(*ty),
CustomClass { id, params } => { CustomClass { id, params } => {
let class_name = if let Some(ref top) = unifier.top_level { let class_name = match unifier.top_level {
if let TopLevelDef::Class { name, .. } = &*top.definitions.read()[id.0].read() { Some(ref top) => {
(*name).into() if let TopLevelDef::Class { name, .. } =
} else { &*top.definitions.read()[id.0].read()
unreachable!() {
(*name).into()
} else {
unreachable!()
}
} }
} else { None => format!("class_def_{}", id.0),
format!("class_def_{}", id.0)
}; };
format!("{}{}", class_name, { format!(
let param_list = "{}{}",
params.iter().map(|p| p.stringify(unifier)).collect_vec().join(", "); class_name,
if param_list.is_empty() { {
String::new() let param_list = params.iter().map(|p| p.stringify(unifier)).collect_vec().join(", ");
} else { if param_list.is_empty() {
format!("[{param_list}]") "".into()
} else {
format!("[{}]", param_list)
}
} }
}) )
}
Literal(values) => {
format!("Literal({})", values.iter().map(|v| format!("{v:?}")).join(", "))
} }
Virtual(ty) => format!("virtual[{}]", ty.stringify(unifier)), Virtual(ty) => format!("virtual[{}]", ty.stringify(unifier)),
List(ty) => format!("list[{}]", ty.stringify(unifier)),
Tuple(types) => { Tuple(types) => {
format!( format!("tuple[{}]", types.iter().map(|p| p.stringify(unifier)).collect_vec().join(", "))
"tuple[{}]",
types.iter().map(|p| p.stringify(unifier)).collect_vec().join(", ")
)
} }
} }
} }
} }
/// Parses an AST expression `expr` into a [`TypeAnnotation`]. pub fn parse_ast_to_type_annotation_kinds<T>(
///
/// * `locked` - A [`HashMap`] containing the IDs of known definitions, mapped to a [`Vec`] of all
/// generic variables associated with the definition.
/// * `type_var` - The type variable associated with the type argument currently being parsed. Pass
/// [`None`] when this function is invoked externally.
pub fn parse_ast_to_type_annotation_kinds<T, S: std::hash::BuildHasher + Clone>(
resolver: &(dyn SymbolResolver + Send + Sync), resolver: &(dyn SymbolResolver + Send + Sync),
top_level_defs: &[Arc<RwLock<TopLevelDef>>], top_level_defs: &[Arc<RwLock<TopLevelDef>>],
unifier: &mut Unifier, unifier: &mut Unifier,
primitives: &PrimitiveStore, primitives: &PrimitiveStore,
expr: &ast::Expr<T>, expr: &ast::Expr<T>,
// the key stores the type_var of this topleveldef::class, we only need this field here // the key stores the type_var of this topleveldef::class, we only need this field here
locked: HashMap<DefinitionId, Vec<Type>, S>, locked: HashMap<DefinitionId, Vec<Type>>,
) -> Result<TypeAnnotation, HashSet<String>> { ) -> Result<TypeAnnotation, String> {
let name_handle = |id: &StrRef, let name_handle = |id: &StrRef,
unifier: &mut Unifier, unifier: &mut Unifier,
locked: HashMap<DefinitionId, Vec<Type>, S>| { locked: HashMap<DefinitionId, Vec<Type>>| {
if id == &"int32".into() { if id == &"int32".into() {
Ok(TypeAnnotation::Primitive(primitives.int32)) Ok(TypeAnnotation::Primitive(primitives.int32))
} else if id == &"int64".into() { } else if id == &"int64".into() {
@ -93,7 +83,7 @@ pub fn parse_ast_to_type_annotation_kinds<T, S: std::hash::BuildHasher + Clone>(
} else if id == &"str".into() { } else if id == &"str".into() {
Ok(TypeAnnotation::Primitive(primitives.str)) Ok(TypeAnnotation::Primitive(primitives.str))
} else if id == &"Exception".into() { } else if id == &"Exception".into() {
Ok(TypeAnnotation::CustomClass { id: PrimDef::Exception.id(), params: Vec::default() }) Ok(TypeAnnotation::CustomClass { id: DefinitionId(7), params: Default::default() })
} else if let Ok(obj_id) = resolver.get_identifier_def(*id) { } else if let Ok(obj_id) = resolver.get_identifier_def(*id) {
let type_vars = { let type_vars = {
let def_read = top_level_defs[obj_id.0].try_read(); let def_read = top_level_defs[obj_id.0].try_read();
@ -101,10 +91,10 @@ pub fn parse_ast_to_type_annotation_kinds<T, S: std::hash::BuildHasher + Clone>(
if let TopLevelDef::Class { type_vars, .. } = &*def_read { if let TopLevelDef::Class { type_vars, .. } = &*def_read {
type_vars.clone() type_vars.clone()
} else { } else {
return Err(HashSet::from([format!( return Err(format!(
"function cannot be used as a type (at {})", "function cannot be used as a type (at {})",
expr.location expr.location
)])); ));
} }
} else { } else {
locked.get(&obj_id).unwrap().clone() locked.get(&obj_id).unwrap().clone()
@ -112,29 +102,23 @@ pub fn parse_ast_to_type_annotation_kinds<T, S: std::hash::BuildHasher + Clone>(
}; };
// check param number here // check param number here
if !type_vars.is_empty() { if !type_vars.is_empty() {
return Err(HashSet::from([format!( return Err(format!(
"expect {} type variable parameter but got 0 (at {})", "expect {} type variable parameter but got 0 (at {})",
type_vars.len(), type_vars.len(),
expr.location, expr.location,
)])); ));
} }
Ok(TypeAnnotation::CustomClass { id: obj_id, params: vec![] }) Ok(TypeAnnotation::CustomClass { id: obj_id, params: vec![] })
} else if let Ok(ty) = resolver.get_symbol_type(unifier, top_level_defs, primitives, *id) { } else if let Ok(ty) = resolver.get_symbol_type(unifier, top_level_defs, primitives, *id) {
if let TypeEnum::TVar { .. } = unifier.get_ty(ty).as_ref() { if let TypeEnum::TVar { .. } = unifier.get_ty(ty).as_ref() {
let var = unifier.get_fresh_var(Some(*id), Some(expr.location)).ty; let var = unifier.get_fresh_var(Some(*id), Some(expr.location)).0;
unifier.unify(var, ty).unwrap(); unifier.unify(var, ty).unwrap();
Ok(TypeAnnotation::TypeVar(ty)) Ok(TypeAnnotation::TypeVar(ty))
} else { } else {
Err(HashSet::from([format!( Err(format!("`{}` is not a valid type annotation (at {})", id, expr.location))
"`{}` is not a valid type annotation (at {})",
id, expr.location
)]))
} }
} else { } else {
Err(HashSet::from([format!( Err(format!("`{}` is not a valid type annotation (at {})", id, expr.location))
"`{}` is not a valid type annotation (at {})",
id, expr.location
)]))
} }
}; };
@ -142,22 +126,20 @@ pub fn parse_ast_to_type_annotation_kinds<T, S: std::hash::BuildHasher + Clone>(
|id: &StrRef, |id: &StrRef,
slice: &ast::Expr<T>, slice: &ast::Expr<T>,
unifier: &mut Unifier, unifier: &mut Unifier,
mut locked: HashMap<DefinitionId, Vec<Type>, S>| { mut locked: HashMap<DefinitionId, Vec<Type>>| {
if ["virtual".into(), "Generic".into(), "tuple".into(), "Option".into()].contains(id) { if vec!["virtual".into(), "Generic".into(), "list".into(), "tuple".into()].contains(id)
return Err(HashSet::from([format!( {
"keywords cannot be class name (at {})", return Err(format!("keywords cannot be class name (at {})", expr.location));
expr.location
)]));
} }
let obj_id = resolver.get_identifier_def(*id)?; let obj_id = resolver.get_identifier_def(*id)?;
let type_vars = { let type_vars = {
let def_read = top_level_defs[obj_id.0].try_read(); let def_read = top_level_defs[obj_id.0].try_read();
if let Some(def_read) = def_read { if let Some(def_read) = def_read {
let TopLevelDef::Class { type_vars, .. } = &*def_read else { if let TopLevelDef::Class { type_vars, .. } = &*def_read {
type_vars.clone()
} else {
unreachable!("must be class here") unreachable!("must be class here")
}; }
type_vars.clone()
} else { } else {
locked.get(&obj_id).unwrap().clone() locked.get(&obj_id).unwrap().clone()
} }
@ -170,12 +152,12 @@ pub fn parse_ast_to_type_annotation_kinds<T, S: std::hash::BuildHasher + Clone>(
vec![slice] vec![slice]
}; };
if type_vars.len() != params_ast.len() { if type_vars.len() != params_ast.len() {
return Err(HashSet::from([format!( return Err(format!(
"expect {} type parameters but got {} (at {})", "expect {} type parameters but got {} (at {})",
type_vars.len(), type_vars.len(),
params_ast.len(), params_ast.len(),
params_ast[0].location, params_ast[0].location,
)])); ));
} }
let result = params_ast let result = params_ast
.iter() .iter()
@ -199,17 +181,15 @@ pub fn parse_ast_to_type_annotation_kinds<T, S: std::hash::BuildHasher + Clone>(
if no_type_var { if no_type_var {
result result
} else { } else {
return Err(HashSet::from([ return Err(format!(
format!( "application of type vars to generic class \
"application of type vars to generic class is not currently supported (at {})", is not currently supported (at {})",
params_ast[0].location params_ast[0].location
), ));
]));
} }
}; };
Ok(TypeAnnotation::CustomClass { id: obj_id, params: param_type_infos }) Ok(TypeAnnotation::CustomClass { id: obj_id, params: param_type_infos })
}; };
match &expr.node { match &expr.node {
ast::ExprKind::Name { id, .. } => name_handle(id, unifier, locked), ast::ExprKind::Name { id, .. } => name_handle(id, unifier, locked),
// virtual // virtual
@ -232,6 +212,23 @@ pub fn parse_ast_to_type_annotation_kinds<T, S: std::hash::BuildHasher + Clone>(
Ok(TypeAnnotation::Virtual(def.into())) Ok(TypeAnnotation::Virtual(def.into()))
} }
// list
ast::ExprKind::Subscript { value, slice, .. }
if {
matches!(&value.node, ast::ExprKind::Name { id, .. } if id == &"list".into())
} =>
{
let def_ann = parse_ast_to_type_annotation_kinds(
resolver,
top_level_defs,
unifier,
primitives,
slice.as_ref(),
locked,
)?;
Ok(TypeAnnotation::List(def_ann.into()))
}
// option // option
ast::ExprKind::Subscript { value, slice, .. } ast::ExprKind::Subscript { value, slice, .. }
if { if {
@ -284,70 +281,16 @@ pub fn parse_ast_to_type_annotation_kinds<T, S: std::hash::BuildHasher + Clone>(
Ok(TypeAnnotation::Tuple(type_annotations)) Ok(TypeAnnotation::Tuple(type_annotations))
} }
// Literal
ast::ExprKind::Subscript { value, slice, .. }
if {
matches!(&value.node, ast::ExprKind::Name { id, .. } if id == &"Literal".into())
} =>
{
let tup_elts = {
if let ast::ExprKind::Tuple { elts, .. } = &slice.node {
elts.as_slice()
} else {
std::slice::from_ref(slice.as_ref())
}
};
let type_annotations = tup_elts
.iter()
.map(|e| match &e.node {
ast::ExprKind::Constant { value, .. } => {
Ok(TypeAnnotation::Literal(vec![value.clone()]))
}
_ => parse_ast_to_type_annotation_kinds(
resolver,
top_level_defs,
unifier,
primitives,
e,
locked.clone(),
),
})
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.flat_map(|type_ann| match type_ann {
TypeAnnotation::Literal(values) => values,
_ => unreachable!(),
})
.collect_vec();
if type_annotations.len() == 1 {
Ok(TypeAnnotation::Literal(type_annotations))
} else {
Err(HashSet::from([format!(
"multiple literal bounds are currently unsupported (at {})",
value.location
)]))
}
}
// custom class // custom class
ast::ExprKind::Subscript { value, slice, .. } => { ast::ExprKind::Subscript { value, slice, .. } => {
if let ast::ExprKind::Name { id, .. } = &value.node { if let ast::ExprKind::Name { id, .. } = &value.node {
class_name_handle(id, slice, unifier, locked) class_name_handle(id, slice, unifier, locked)
} else { } else {
Err(HashSet::from([format!( Err(format!("unsupported expression type for class name (at {})", value.location))
"unsupported expression type for class name (at {})",
value.location
)]))
} }
} }
ast::ExprKind::Constant { value, .. } => Ok(TypeAnnotation::Literal(vec![value.clone()])), _ => Err(format!("unsupported expression for type annotation (at {})", expr.location)),
_ => Err(HashSet::from([format!(
"unsupported expression for type annotation (at {})",
expr.location
)])),
} }
} }
@ -357,149 +300,127 @@ pub fn parse_ast_to_type_annotation_kinds<T, S: std::hash::BuildHasher + Clone>(
pub fn get_type_from_type_annotation_kinds( pub fn get_type_from_type_annotation_kinds(
top_level_defs: &[Arc<RwLock<TopLevelDef>>], top_level_defs: &[Arc<RwLock<TopLevelDef>>],
unifier: &mut Unifier, unifier: &mut Unifier,
primitives: &PrimitiveStore,
ann: &TypeAnnotation, ann: &TypeAnnotation,
subst_list: &mut Option<Vec<Type>>, subst_list: &mut Option<Vec<Type>>
) -> Result<Type, HashSet<String>> { ) -> Result<Type, String> {
match ann { match ann {
TypeAnnotation::CustomClass { id: obj_id, params } => { TypeAnnotation::CustomClass { id: obj_id, params } => {
let def_read = top_level_defs[obj_id.0].read(); let def_read = top_level_defs[obj_id.0].read();
let class_def: &TopLevelDef = &def_read; let class_def: &TopLevelDef = def_read.deref();
let TopLevelDef::Class { fields, methods, type_vars, .. } = class_def else { if let TopLevelDef::Class { fields, methods, type_vars, .. } = class_def {
unreachable!("should be class def here") if type_vars.len() != params.len() {
}; Err(format!(
"unexpected number of type parameters: expected {} but got {}",
type_vars.len(),
params.len()
))
} else {
let param_ty = params
.iter()
.map(|x| {
get_type_from_type_annotation_kinds(
top_level_defs,
unifier,
primitives,
x,
subst_list
)
})
.collect::<Result<Vec<_>, _>>()?;
if type_vars.len() != params.len() { let subst = {
return Err(HashSet::from([format!( // check for compatible range
"unexpected number of type parameters: expected {} but got {}", // TODO: if allow type var to be applied(now this disallowed in the parse_to_type_annotation), need more check
type_vars.len(), let mut result: HashMap<u32, Type> = HashMap::new();
params.len() for (tvar, p) in type_vars.iter().zip(param_ty) {
)])); if let TypeEnum::TVar { id, range, fields: None, name, loc } =
} unifier.get_ty(*tvar).as_ref()
{
let param_ty = params let ok: bool = {
.iter() // create a temp type var and unify to check compatibility
.map(|x| { p == *tvar || {
get_type_from_type_annotation_kinds(top_level_defs, unifier, x, subst_list) let temp = unifier.get_fresh_var_with_range(
}) range.as_slice(),
.collect::<Result<Vec<_>, _>>()?; *name,
*loc,
let subst = { );
// check for compatible range unifier.unify(temp.0, p).is_ok()
// TODO: if allow type var to be applied(now this disallowed in the parse_to_type_annotation), need more check }
let mut result = VarMap::new(); };
for (tvar, p) in type_vars.iter().zip(param_ty) { if ok {
match unifier.get_ty(*tvar).as_ref() { result.insert(*id, p);
TypeEnum::TVar { } else {
id, return Err(format!(
range, "cannot apply type {} to type variable with id {:?}",
fields: None, unifier.internal_stringify(
name, p,
loc, &mut |id| format!("class{}", id),
is_const_generic: false, &mut |id| format!("typevar{}", id),
} => { &mut None
let ok: bool = { ),
// create a temp type var and unify to check compatibility *id
p == *tvar || { ));
let temp = unifier.get_fresh_var_with_range(
range.as_slice(),
*name,
*loc,
);
unifier.unify(temp.ty, p).is_ok()
} }
};
if ok {
result.insert(*id, p);
} else { } else {
return Err(HashSet::from([format!( unreachable!("must be generic type var")
"cannot apply type {} to type variable with id {:?}",
unifier.internal_stringify(
p,
&mut |id| format!("class{id}"),
&mut |id| format!("typevar{id}"),
&mut None
),
*id
)]));
} }
} }
result
TypeEnum::TVar { id, range, name, loc, is_const_generic: true, .. } => { };
let ty = range[0]; let mut tobj_fields = methods
let ok: bool = { .iter()
// create a temp type var and unify to check compatibility .map(|(name, ty, _)| {
p == *tvar || { let subst_ty = unifier.subst(*ty, &subst).unwrap_or(*ty);
let temp = unifier.get_fresh_const_generic_var(ty, *name, *loc); // methods are immutable
unifier.unify(temp.ty, p).is_ok() (*name, (subst_ty, false))
} })
}; .collect::<HashMap<_, _>>();
if ok { tobj_fields.extend(fields.iter().map(|(name, ty, mutability)| {
result.insert(*id, p); let subst_ty = unifier.subst(*ty, &subst).unwrap_or(*ty);
} else { (*name, (subst_ty, *mutability))
return Err(HashSet::from([format!( }));
"cannot apply type {} to type variable {}", let need_subst = !subst.is_empty();
unifier.stringify(p), let ty = unifier.add_ty(TypeEnum::TObj {
name.unwrap_or_else(|| format!("typevar{id}").into()), obj_id: *obj_id,
)])); fields: tobj_fields,
} params: subst,
} });
if need_subst {
_ => unreachable!("must be generic type var"), subst_list.as_mut().map(|wl| wl.push(ty));
} }
Ok(ty)
} }
result } else {
}; unreachable!("should be class def here")
// Class Attributes keep a copy with Class Definition and are not added to objects
let mut tobj_fields = methods
.iter()
.map(|(name, ty, _)| {
let subst_ty = unifier.subst(*ty, &subst).unwrap_or(*ty);
// methods are immutable
(*name, (subst_ty, false))
})
.collect::<HashMap<_, _>>();
tobj_fields.extend(fields.iter().map(|(name, ty, mutability)| {
let subst_ty = unifier.subst(*ty, &subst).unwrap_or(*ty);
(*name, (subst_ty, *mutability))
}));
let need_subst = !subst.is_empty();
let ty = unifier.add_ty(TypeEnum::TObj {
obj_id: *obj_id,
fields: tobj_fields,
params: subst,
});
if need_subst {
if let Some(wl) = subst_list.as_mut() {
wl.push(ty);
}
} }
Ok(ty)
} }
TypeAnnotation::Primitive(ty) | TypeAnnotation::TypeVar(ty) => Ok(*ty), TypeAnnotation::Primitive(ty) | TypeAnnotation::TypeVar(ty) => Ok(*ty),
TypeAnnotation::Literal(values) => {
let values = values
.iter()
.map(SymbolValue::from_constant_inferred)
.collect::<Result<Vec<_>, _>>()
.map_err(|err| HashSet::from([err]))?;
let var = unifier.get_fresh_literal(values, None);
Ok(var)
}
TypeAnnotation::Virtual(ty) => { TypeAnnotation::Virtual(ty) => {
let ty = get_type_from_type_annotation_kinds( let ty = get_type_from_type_annotation_kinds(
top_level_defs, top_level_defs,
unifier, unifier,
primitives,
ty.as_ref(), ty.as_ref(),
subst_list, subst_list
)?; )?;
Ok(unifier.add_ty(TypeEnum::TVirtual { ty })) Ok(unifier.add_ty(TypeEnum::TVirtual { ty }))
} }
TypeAnnotation::List(ty) => {
let ty = get_type_from_type_annotation_kinds(
top_level_defs,
unifier,
primitives,
ty.as_ref(),
subst_list
)?;
Ok(unifier.add_ty(TypeEnum::TList { ty }))
}
TypeAnnotation::Tuple(tys) => { TypeAnnotation::Tuple(tys) => {
let tys = tys let tys = tys
.iter() .iter()
.map(|x| { .map(|x| {
get_type_from_type_annotation_kinds(top_level_defs, unifier, x, subst_list) get_type_from_type_annotation_kinds(top_level_defs, unifier, primitives, x, subst_list)
}) })
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
Ok(unifier.add_ty(TypeEnum::TTuple { ty: tys })) Ok(unifier.add_ty(TypeEnum::TTuple { ty: tys }))
@ -516,10 +437,9 @@ pub fn get_type_from_type_annotation_kinds(
/// considered to be type variables associated with the class \ /// considered to be type variables associated with the class \
/// \ /// \
/// But note that here we do not make a duplication of `T`, `V`, we directly /// But note that here we do not make a duplication of `T`, `V`, we directly
/// use them as they are in the [`TopLevelDef::Class`] since those in the /// use them as they are in the TopLevelDef::Class since those in the
/// `TopLevelDef::Class.type_vars` will be substitute later when seeing applications/instantiations /// TopLevelDef::Class.type_vars will be substitute later when seeing applications/instantiations
/// the Type of their fields and methods will also be subst when application/instantiation /// the Type of their fields and methods will also be subst when application/instantiation
#[must_use]
pub fn make_self_type_annotation(type_vars: &[Type], object_id: DefinitionId) -> TypeAnnotation { pub fn make_self_type_annotation(type_vars: &[Type], object_id: DefinitionId) -> TypeAnnotation {
TypeAnnotation::CustomClass { TypeAnnotation::CustomClass {
id: object_id, id: object_id,
@ -530,25 +450,27 @@ pub fn make_self_type_annotation(type_vars: &[Type], object_id: DefinitionId) ->
/// get all the occurences of type vars contained in a type annotation /// get all the occurences of type vars contained in a type annotation
/// e.g. `A[int, B[T], V, virtual[C[G]]]` => [T, V, G] /// e.g. `A[int, B[T], V, virtual[C[G]]]` => [T, V, G]
/// this function will not make a duplicate of type var /// this function will not make a duplicate of type var
#[must_use]
pub fn get_type_var_contained_in_type_annotation(ann: &TypeAnnotation) -> Vec<TypeAnnotation> { pub fn get_type_var_contained_in_type_annotation(ann: &TypeAnnotation) -> Vec<TypeAnnotation> {
let mut result: Vec<TypeAnnotation> = Vec::new(); let mut result: Vec<TypeAnnotation> = Vec::new();
match ann { match ann {
TypeAnnotation::TypeVar(..) => result.push(ann.clone()), TypeAnnotation::TypeVar(..) => result.push(ann.clone()),
TypeAnnotation::Virtual(ann) => { TypeAnnotation::Virtual(ann) => {
result.extend(get_type_var_contained_in_type_annotation(ann.as_ref())); result.extend(get_type_var_contained_in_type_annotation(ann.as_ref()))
} }
TypeAnnotation::CustomClass { params, .. } => { TypeAnnotation::CustomClass { params, .. } => {
for p in params { for p in params {
result.extend(get_type_var_contained_in_type_annotation(p)); result.extend(get_type_var_contained_in_type_annotation(p));
} }
} }
TypeAnnotation::List(ann) => {
result.extend(get_type_var_contained_in_type_annotation(ann.as_ref()))
}
TypeAnnotation::Tuple(anns) => { TypeAnnotation::Tuple(anns) => {
for a in anns { for a in anns {
result.extend(get_type_var_contained_in_type_annotation(a)); result.extend(get_type_var_contained_in_type_annotation(a));
} }
} }
TypeAnnotation::Primitive(..) | TypeAnnotation::Literal { .. } => {} TypeAnnotation::Primitive(..) => {}
} }
result result
} }
@ -563,20 +485,21 @@ pub fn check_overload_type_annotation_compatible(
(TypeAnnotation::Primitive(a), TypeAnnotation::Primitive(b)) => a == b, (TypeAnnotation::Primitive(a), TypeAnnotation::Primitive(b)) => a == b,
(TypeAnnotation::TypeVar(a), TypeAnnotation::TypeVar(b)) => { (TypeAnnotation::TypeVar(a), TypeAnnotation::TypeVar(b)) => {
let a = unifier.get_ty(*a); let a = unifier.get_ty(*a);
let a = &*a; let a = a.deref();
let b = unifier.get_ty(*b); let b = unifier.get_ty(*b);
let b = &*b; let b = b.deref();
let ( if let (
TypeEnum::TVar { id: a, fields: None, .. }, TypeEnum::TVar { id: a, fields: None, .. },
TypeEnum::TVar { id: b, fields: None, .. }, TypeEnum::TVar { id: b, fields: None, .. },
) = (a, b) ) = (a, b)
else { {
a == b
} else {
unreachable!("must be type var") unreachable!("must be type var")
}; }
a == b
} }
(TypeAnnotation::Virtual(a), TypeAnnotation::Virtual(b)) => { (TypeAnnotation::Virtual(a), TypeAnnotation::Virtual(b))
| (TypeAnnotation::List(a), TypeAnnotation::List(b)) => {
check_overload_type_annotation_compatible(a.as_ref(), b.as_ref(), unifier) check_overload_type_annotation_compatible(a.as_ref(), b.as_ref(), unifier)
} }

View File

@ -1,18 +1,14 @@
use crate::toplevel::helper::PrimDef; use crate::typecheck::typedef::TypeEnum;
use super::type_inferencer::Inferencer; use super::type_inferencer::Inferencer;
use super::typedef::{Type, TypeEnum}; use super::typedef::Type;
use nac3parser::ast::{ use nac3parser::ast::{self, Expr, ExprKind, Stmt, StmtKind, StrRef};
self, Constant, Expr, ExprKind,
Operator::{LShift, RShift},
Stmt, StmtKind, StrRef,
};
use std::{collections::HashSet, iter::once}; use std::{collections::HashSet, iter::once};
impl<'a> Inferencer<'a> { impl<'a> Inferencer<'a> {
fn should_have_value(&mut self, expr: &Expr<Option<Type>>) -> Result<(), HashSet<String>> { fn should_have_value(&mut self, expr: &Expr<Option<Type>>) -> Result<(), String> {
if matches!(expr.custom, Some(ty) if self.unifier.unioned(ty, self.primitives.none)) { if matches!(expr.custom, Some(ty) if self.unifier.unioned(ty, self.primitives.none)) {
Err(HashSet::from([format!("Error at {}: cannot have value none", expr.location)])) Err(format!("Error at {}: cannot have value none", expr.location))
} else { } else {
Ok(()) Ok(())
} }
@ -22,11 +18,10 @@ impl<'a> Inferencer<'a> {
&mut self, &mut self,
pattern: &Expr<Option<Type>>, pattern: &Expr<Option<Type>>,
defined_identifiers: &mut HashSet<StrRef>, defined_identifiers: &mut HashSet<StrRef>,
) -> Result<(), HashSet<String>> { ) -> Result<(), String> {
match &pattern.node { match &pattern.node {
ExprKind::Name { id, .. } if id == &"none".into() => { ast::ExprKind::Name { id, .. } if id == &"none".into() =>
Err(HashSet::from([format!("cannot assign to a `none` (at {})", pattern.location)])) Err(format!("cannot assign to a `none` (at {})", pattern.location)),
}
ExprKind::Name { id, .. } => { ExprKind::Name { id, .. } => {
if !defined_identifiers.contains(id) { if !defined_identifiers.contains(id) {
defined_identifiers.insert(*id); defined_identifiers.insert(*id);
@ -35,7 +30,7 @@ impl<'a> Inferencer<'a> {
Ok(()) Ok(())
} }
ExprKind::Tuple { elts, .. } => { ExprKind::Tuple { elts, .. } => {
for elt in elts { for elt in elts.iter() {
self.check_pattern(elt, defined_identifiers)?; self.check_pattern(elt, defined_identifiers)?;
self.should_have_value(elt)?; self.should_have_value(elt)?;
} }
@ -46,17 +41,16 @@ impl<'a> Inferencer<'a> {
self.should_have_value(value)?; self.should_have_value(value)?;
self.check_expr(slice, defined_identifiers)?; self.check_expr(slice, defined_identifiers)?;
if let TypeEnum::TTuple { .. } = &*self.unifier.get_ty(value.custom.unwrap()) { if let TypeEnum::TTuple { .. } = &*self.unifier.get_ty(value.custom.unwrap()) {
return Err(HashSet::from([format!( return Err(format!(
"Error at {}: cannot assign to tuple element", "Error at {}: cannot assign to tuple element",
value.location value.location
)])); ));
} }
Ok(()) Ok(())
} }
ExprKind::Constant { .. } => Err(HashSet::from([format!( ExprKind::Constant { .. } => {
"cannot assign to a constant (at {})", Err(format!("cannot assign to a constant (at {})", pattern.location))
pattern.location }
)])),
_ => self.check_expr(pattern, defined_identifiers), _ => self.check_expr(pattern, defined_identifiers),
} }
} }
@ -65,18 +59,15 @@ impl<'a> Inferencer<'a> {
&mut self, &mut self,
expr: &Expr<Option<Type>>, expr: &Expr<Option<Type>>,
defined_identifiers: &mut HashSet<StrRef>, defined_identifiers: &mut HashSet<StrRef>,
) -> Result<(), HashSet<String>> { ) -> Result<(), String> {
// there are some cases where the custom field is None // there are some cases where the custom field is None
if let Some(ty) = &expr.custom { if let Some(ty) = &expr.custom {
if !matches!(&expr.node, ExprKind::Constant { value: Constant::Ellipsis, .. }) if !self.unifier.is_concrete(*ty, &self.function_data.bound_variables) {
&& !ty.obj_id(self.unifier).is_some_and(|id| id == PrimDef::List.id()) return Err(format!(
&& !self.unifier.is_concrete(*ty, &self.function_data.bound_variables)
{
return Err(HashSet::from([format!(
"expected concrete type at {} but got {}", "expected concrete type at {} but got {}",
expr.location, expr.location,
self.unifier.get_ty(*ty).get_type_name() self.unifier.get_ty(*ty).get_type_name()
)])); ));
} }
} }
match &expr.node { match &expr.node {
@ -96,10 +87,10 @@ impl<'a> Inferencer<'a> {
self.defined_identifiers.insert(*id); self.defined_identifiers.insert(*id);
} }
Err(e) => { Err(e) => {
return Err(HashSet::from([format!( return Err(format!(
"type error at identifier `{}` ({}) at {}", "type error at identifier `{}` ({}) at {}",
id, e, expr.location id, e, expr.location
)])) ));
} }
} }
} }
@ -107,7 +98,7 @@ impl<'a> Inferencer<'a> {
ExprKind::List { elts, .. } ExprKind::List { elts, .. }
| ExprKind::Tuple { elts, .. } | ExprKind::Tuple { elts, .. }
| ExprKind::BoolOp { values: elts, .. } => { | ExprKind::BoolOp { values: elts, .. } => {
for elt in elts { for elt in elts.iter() {
self.check_expr(elt, defined_identifiers)?; self.check_expr(elt, defined_identifiers)?;
self.should_have_value(elt)?; self.should_have_value(elt)?;
} }
@ -116,25 +107,11 @@ impl<'a> Inferencer<'a> {
self.check_expr(value, defined_identifiers)?; self.check_expr(value, defined_identifiers)?;
self.should_have_value(value)?; self.should_have_value(value)?;
} }
ExprKind::BinOp { left, op, right } => { ExprKind::BinOp { left, right, .. } => {
self.check_expr(left, defined_identifiers)?; self.check_expr(left, defined_identifiers)?;
self.check_expr(right, defined_identifiers)?; self.check_expr(right, defined_identifiers)?;
self.should_have_value(left)?; self.should_have_value(left)?;
self.should_have_value(right)?; self.should_have_value(right)?;
// Check whether a bitwise shift has a negative RHS constant value
if *op == LShift || *op == RShift {
if let ExprKind::Constant { value, .. } = &right.node {
let Constant::Int(rhs_val) = value else { unreachable!() };
if *rhs_val < 0 {
return Err(HashSet::from([format!(
"shift count is negative at {}",
right.location
)]));
}
}
}
} }
ExprKind::UnaryOp { operand, .. } => { ExprKind::UnaryOp { operand, .. } => {
self.check_expr(operand, defined_identifiers)?; self.check_expr(operand, defined_identifiers)?;
@ -164,7 +141,7 @@ impl<'a> Inferencer<'a> {
} }
ExprKind::Lambda { args, body } => { ExprKind::Lambda { args, body } => {
let mut defined_identifiers = defined_identifiers.clone(); let mut defined_identifiers = defined_identifiers.clone();
for arg in &args.args { for arg in args.args.iter() {
// TODO: should we check the types here? // TODO: should we check the types here?
if !defined_identifiers.contains(&arg.node.arg) { if !defined_identifiers.contains(&arg.node.arg) {
defined_identifiers.insert(arg.node.arg); defined_identifiers.insert(arg.node.arg);
@ -202,45 +179,24 @@ impl<'a> Inferencer<'a> {
Ok(()) Ok(())
} }
/// Check that the return value is a non-`alloca` type, effectively only allowing primitive types.
///
/// This is a workaround preventing the caller from using a variable `alloca`-ed in the body, which
/// is freed when the function returns.
fn check_return_value_ty(&mut self, ret_ty: Type) -> bool {
match &*self.unifier.get_ty_immutable(ret_ty) {
TypeEnum::TObj { .. } => [
self.primitives.int32,
self.primitives.int64,
self.primitives.uint32,
self.primitives.uint64,
self.primitives.float,
self.primitives.bool,
]
.iter()
.any(|allowed_ty| self.unifier.unioned(ret_ty, *allowed_ty)),
TypeEnum::TTuple { ty } => ty.iter().all(|t| self.check_return_value_ty(*t)),
_ => false,
}
}
// check statements for proper identifier def-use and return on all paths // check statements for proper identifier def-use and return on all paths
fn check_stmt( fn check_stmt(
&mut self, &mut self,
stmt: &Stmt<Option<Type>>, stmt: &Stmt<Option<Type>>,
defined_identifiers: &mut HashSet<StrRef>, defined_identifiers: &mut HashSet<StrRef>,
) -> Result<bool, HashSet<String>> { ) -> Result<bool, String> {
match &stmt.node { match &stmt.node {
StmtKind::For { target, iter, body, orelse, .. } => { StmtKind::For { target, iter, body, orelse, .. } => {
self.check_expr(iter, defined_identifiers)?; self.check_expr(iter, defined_identifiers)?;
self.should_have_value(iter)?; self.should_have_value(iter)?;
let mut local_defined_identifiers = defined_identifiers.clone(); let mut local_defined_identifiers = defined_identifiers.clone();
for stmt in orelse { for stmt in orelse.iter() {
self.check_stmt(stmt, &mut local_defined_identifiers)?; self.check_stmt(stmt, &mut local_defined_identifiers)?;
} }
let mut local_defined_identifiers = defined_identifiers.clone(); let mut local_defined_identifiers = defined_identifiers.clone();
self.check_pattern(target, &mut local_defined_identifiers)?; self.check_pattern(target, &mut local_defined_identifiers)?;
self.should_have_value(target)?; self.should_have_value(target)?;
for stmt in body { for stmt in body.iter() {
self.check_stmt(stmt, &mut local_defined_identifiers)?; self.check_stmt(stmt, &mut local_defined_identifiers)?;
} }
Ok(false) Ok(false)
@ -253,7 +209,7 @@ impl<'a> Inferencer<'a> {
let body_returned = self.check_block(body, &mut body_identifiers)?; let body_returned = self.check_block(body, &mut body_identifiers)?;
let orelse_returned = self.check_block(orelse, &mut orelse_identifiers)?; let orelse_returned = self.check_block(orelse, &mut orelse_identifiers)?;
for ident in &body_identifiers { for ident in body_identifiers.iter() {
if !defined_identifiers.contains(ident) && orelse_identifiers.contains(ident) { if !defined_identifiers.contains(ident) && orelse_identifiers.contains(ident) {
defined_identifiers.insert(*ident); defined_identifiers.insert(*ident);
} }
@ -270,7 +226,7 @@ impl<'a> Inferencer<'a> {
} }
StmtKind::With { items, body, .. } => { StmtKind::With { items, body, .. } => {
let mut new_defined_identifiers = defined_identifiers.clone(); let mut new_defined_identifiers = defined_identifiers.clone();
for item in items { for item in items.iter() {
self.check_expr(&item.context_expr, defined_identifiers)?; self.check_expr(&item.context_expr, defined_identifiers)?;
if let Some(var) = item.optional_vars.as_ref() { if let Some(var) = item.optional_vars.as_ref() {
self.check_pattern(var, &mut new_defined_identifiers)?; self.check_pattern(var, &mut new_defined_identifiers)?;
@ -282,7 +238,7 @@ impl<'a> Inferencer<'a> {
StmtKind::Try { body, handlers, orelse, finalbody, .. } => { StmtKind::Try { body, handlers, orelse, finalbody, .. } => {
self.check_block(body, &mut defined_identifiers.clone())?; self.check_block(body, &mut defined_identifiers.clone())?;
self.check_block(orelse, &mut defined_identifiers.clone())?; self.check_block(orelse, &mut defined_identifiers.clone())?;
for handler in handlers { for handler in handlers.iter() {
let mut defined_identifiers = defined_identifiers.clone(); let mut defined_identifiers = defined_identifiers.clone();
let ast::ExcepthandlerKind::ExceptHandler { name, body, .. } = &handler.node; let ast::ExcepthandlerKind::ExceptHandler { name, body, .. } = &handler.node;
if let Some(name) = name { if let Some(name) = name {
@ -317,30 +273,6 @@ impl<'a> Inferencer<'a> {
if let Some(value) = value { if let Some(value) = value {
self.check_expr(value, defined_identifiers)?; self.check_expr(value, defined_identifiers)?;
self.should_have_value(value)?; self.should_have_value(value)?;
// Check that the return value is a non-`alloca` type, effectively only allowing primitive types.
// This is a workaround preventing the caller from using a variable `alloca`-ed in the body, which
// is freed when the function returns.
if let Some(ret_ty) = value.custom {
// Explicitly allow ellipsis as a return value, as the type of the ellipsis is contextually
// inferred and just generates an unconditional assertion
if matches!(
value.node,
ExprKind::Constant { value: Constant::Ellipsis, .. }
) {
return Ok(true);
}
if !self.check_return_value_ty(ret_ty) {
return Err(HashSet::from([
format!(
"return value of type {} must be a primitive or a tuple of primitives at {}",
self.unifier.stringify(ret_ty),
value.location,
),
]));
}
}
} }
Ok(true) Ok(true)
} }
@ -359,11 +291,11 @@ impl<'a> Inferencer<'a> {
&mut self, &mut self,
block: &[Stmt<Option<Type>>], block: &[Stmt<Option<Type>>],
defined_identifiers: &mut HashSet<StrRef>, defined_identifiers: &mut HashSet<StrRef>,
) -> Result<bool, HashSet<String>> { ) -> Result<bool, String> {
let mut ret = false; let mut ret = false;
for stmt in block { for stmt in block {
if ret { if ret {
eprintln!("warning: dead code at {}\n", stmt.location); return Err(format!("dead code at {:?}", stmt.location));
} }
if self.check_stmt(stmt, defined_identifiers)? { if self.check_stmt(stmt, defined_identifiers)? {
ret = true; ret = true;

View File

@ -1,150 +1,69 @@
use crate::symbol_resolver::SymbolValue;
use crate::toplevel::helper::PrimDef;
use crate::toplevel::numpy::{make_ndarray_ty, unpack_ndarray_var_tys};
use crate::typecheck::{ use crate::typecheck::{
type_inferencer::*, type_inferencer::*,
typedef::{FunSignature, FuncArg, Type, TypeEnum, Unifier, VarMap}, typedef::{FunSignature, FuncArg, Type, TypeEnum, Unifier},
}; };
use itertools::{iproduct, Itertools}; use nac3parser::ast::{self, StrRef};
use nac3parser::ast::StrRef;
use nac3parser::ast::{Cmpop, Operator, Unaryop}; use nac3parser::ast::{Cmpop, Operator, Unaryop};
use std::cmp::max;
use std::collections::HashMap; use std::collections::HashMap;
use std::rc::Rc; use std::rc::Rc;
use strum::IntoEnumIterator;
/// The variant of a binary operator. pub fn binop_name(op: &Operator) -> &'static str {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BinopVariant {
/// The normal variant.
/// For addition, it would be `+`.
Normal,
/// The "Augmented Assigning Operator" variant.
/// For addition, it would be `+=`.
AugAssign,
}
/// A binary operator with its variant.
#[derive(Debug, Clone, Copy)]
pub struct Binop {
/// The base [`Operator`] of this binary operator.
pub base: Operator,
/// The variant of this binary operator.
pub variant: BinopVariant,
}
impl Binop {
/// Make a [`Binop`] of the normal variant from an [`Operator`].
#[must_use]
pub fn normal(base: Operator) -> Self {
Binop { base, variant: BinopVariant::Normal }
}
/// Make a [`Binop`] of the aug assign variant from an [`Operator`].
#[must_use]
pub fn aug_assign(base: Operator) -> Self {
Binop { base, variant: BinopVariant::AugAssign }
}
}
/// Details about an operator (unary, binary, etc...) in Python
#[derive(Debug, Clone, Copy)]
pub struct OpInfo {
/// The method name of the binary operator.
/// For addition, this would be `__add__`, and `__iadd__` if
/// it is the augmented assigning variant.
pub method_name: &'static str,
/// The symbol of the binary operator.
/// For addition, this would be `+`, and `+=` if
/// it is the augmented assigning variant.
pub symbol: &'static str,
}
/// Helper macro to conveniently build an [`OpInfo`].
///
/// Example usage: `make_info("add", "+")` generates `OpInfo { name: "__add__", symbol: "+" }`
macro_rules! make_op_info {
($name:expr, $symbol:expr) => {
OpInfo { method_name: concat!("__", $name, "__"), symbol: $symbol }
};
}
pub trait HasOpInfo {
fn op_info(&self) -> OpInfo;
}
fn try_get_cmpop_info(op: Cmpop) -> Option<OpInfo> {
match op { match op {
Cmpop::Lt => Some(make_op_info!("lt", "<")), Operator::Add => "__add__",
Cmpop::LtE => Some(make_op_info!("le", "<=")), Operator::Sub => "__sub__",
Cmpop::Gt => Some(make_op_info!("gt", ">")), Operator::Div => "__truediv__",
Cmpop::GtE => Some(make_op_info!("ge", ">=")), Operator::Mod => "__mod__",
Cmpop::Eq => Some(make_op_info!("eq", "==")), Operator::Mult => "__mul__",
Cmpop::NotEq => Some(make_op_info!("ne", "!=")), Operator::Pow => "__pow__",
Operator::BitOr => "__or__",
Operator::BitXor => "__xor__",
Operator::BitAnd => "__and__",
Operator::LShift => "__lshift__",
Operator::RShift => "__rshift__",
Operator::FloorDiv => "__floordiv__",
Operator::MatMult => "__matmul__",
}
}
pub fn binop_assign_name(op: &Operator) -> &'static str {
match op {
Operator::Add => "__iadd__",
Operator::Sub => "__isub__",
Operator::Div => "__itruediv__",
Operator::Mod => "__imod__",
Operator::Mult => "__imul__",
Operator::Pow => "__ipow__",
Operator::BitOr => "__ior__",
Operator::BitXor => "__ixor__",
Operator::BitAnd => "__iand__",
Operator::LShift => "__ilshift__",
Operator::RShift => "__irshift__",
Operator::FloorDiv => "__ifloordiv__",
Operator::MatMult => "__imatmul__",
}
}
pub fn unaryop_name(op: &Unaryop) -> &'static str {
match op {
Unaryop::UAdd => "__pos__",
Unaryop::USub => "__neg__",
Unaryop::Not => "__not__",
Unaryop::Invert => "__inv__",
}
}
pub fn comparison_name(op: &Cmpop) -> Option<&'static str> {
match op {
Cmpop::Lt => Some("__lt__"),
Cmpop::LtE => Some("__le__"),
Cmpop::Gt => Some("__gt__"),
Cmpop::GtE => Some("__ge__"),
Cmpop::Eq => Some("__eq__"),
Cmpop::NotEq => Some("__ne__"),
_ => None, _ => None,
} }
} }
impl OpInfo {
#[must_use]
pub fn supports_cmpop(op: Cmpop) -> bool {
try_get_cmpop_info(op).is_some()
}
}
impl HasOpInfo for Cmpop {
fn op_info(&self) -> OpInfo {
try_get_cmpop_info(*self).expect("{self:?} is not supported")
}
}
impl HasOpInfo for Binop {
fn op_info(&self) -> OpInfo {
// Helper macro to generate both the normal variant [`OpInfo`] and the
// augmented assigning variant [`OpInfo`] for a binary operator conveniently.
macro_rules! info {
($name:literal, $symbol:literal) => {
(
make_op_info!($name, $symbol),
make_op_info!(concat!("i", $name), concat!($symbol, "=")),
)
};
}
let (normal_variant, aug_assign_variant) = match self.base {
Operator::Add => info!("add", "+"),
Operator::Sub => info!("sub", "-"),
Operator::Div => info!("truediv", "/"),
Operator::Mod => info!("mod", "%"),
Operator::Mult => info!("mul", "*"),
Operator::Pow => info!("pow", "**"),
Operator::BitOr => info!("or", "|"),
Operator::BitXor => info!("xor", "^"),
Operator::BitAnd => info!("and", "&"),
Operator::LShift => info!("lshift", "<<"),
Operator::RShift => info!("rshift", ">>"),
Operator::FloorDiv => info!("floordiv", "//"),
Operator::MatMult => info!("matmul", "@"),
};
match self.variant {
BinopVariant::Normal => normal_variant,
BinopVariant::AugAssign => aug_assign_variant,
}
}
}
impl HasOpInfo for Unaryop {
fn op_info(&self) -> OpInfo {
match self {
Unaryop::UAdd => make_op_info!("pos", "+"),
Unaryop::USub => make_op_info!("neg", "-"),
Unaryop::Not => make_op_info!("not", "not"), // i.e., `not False`, so the symbol is just `not`.
Unaryop::Invert => make_op_info!("inv", "~"),
}
}
}
pub(super) fn with_fields<F>(unifier: &mut Unifier, ty: Type, f: F) pub(super) fn with_fields<F>(unifier: &mut Unifier, ty: Type, f: F)
where where
F: FnOnce(&mut Unifier, &mut HashMap<StrRef, (Type, bool)>), F: FnOnce(&mut Unifier, &mut HashMap<StrRef, (Type, bool)>),
@ -167,28 +86,38 @@ pub fn impl_binop(
_store: &PrimitiveStore, _store: &PrimitiveStore,
ty: Type, ty: Type,
other_ty: &[Type], other_ty: &[Type],
ret_ty: Option<Type>, ret_ty: Type,
ops: &[Operator], ops: &[ast::Operator],
) { ) {
with_fields(unifier, ty, |unifier, fields| { with_fields(unifier, ty, |unifier, fields| {
let (other_ty, other_var_id) = if other_ty.len() == 1 { let (other_ty, other_var_id) = if other_ty.len() == 1 {
(other_ty[0], None) (other_ty[0], None)
} else { } else {
let tvar = unifier.get_fresh_var_with_range(other_ty, Some("N".into()), None); let (ty, var_id) = unifier.get_fresh_var_with_range(other_ty, Some("N".into()), None);
(tvar.ty, Some(tvar.id)) (ty, Some(var_id))
}; };
let function_vars = if let Some(var_id) = other_var_id { let function_vars = if let Some(var_id) = other_var_id {
vec![(var_id, other_ty)].into_iter().collect::<VarMap>() vec![(var_id, other_ty)].into_iter().collect::<HashMap<_, _>>()
} else { } else {
VarMap::new() HashMap::new()
}; };
for op in ops {
fields.insert(binop_name(op).into(), {
(
unifier.add_ty(TypeEnum::TFunc(FunSignature {
ret: ret_ty,
vars: function_vars.clone(),
args: vec![FuncArg {
ty: other_ty,
default_value: None,
name: "other".into(),
}],
})),
false,
)
});
let ret_ty = ret_ty.unwrap_or_else(|| unifier.get_fresh_var(None, None).ty); fields.insert(binop_assign_name(op).into(), {
for (base_op, variant) in iproduct!(ops, [BinopVariant::Normal, BinopVariant::AugAssign]) {
let op = Binop { base: *base_op, variant };
fields.insert(op.op_info().method_name.into(), {
( (
unifier.add_ty(TypeEnum::TFunc(FunSignature { unifier.add_ty(TypeEnum::TFunc(FunSignature {
ret: ret_ty, ret: ret_ty,
@ -206,17 +135,15 @@ pub fn impl_binop(
}); });
} }
pub fn impl_unaryop(unifier: &mut Unifier, ty: Type, ret_ty: Option<Type>, ops: &[Unaryop]) { pub fn impl_unaryop(unifier: &mut Unifier, ty: Type, ret_ty: Type, ops: &[ast::Unaryop]) {
with_fields(unifier, ty, |unifier, fields| { with_fields(unifier, ty, |unifier, fields| {
let ret_ty = ret_ty.unwrap_or_else(|| unifier.get_fresh_var(None, None).ty);
for op in ops { for op in ops {
fields.insert( fields.insert(
op.op_info().method_name.into(), unaryop_name(op).into(),
( (
unifier.add_ty(TypeEnum::TFunc(FunSignature { unifier.add_ty(TypeEnum::TFunc(FunSignature {
ret: ret_ty, ret: ret_ty,
vars: VarMap::new(), vars: HashMap::new(),
args: vec![], args: vec![],
})), })),
false, false,
@ -228,35 +155,19 @@ pub fn impl_unaryop(unifier: &mut Unifier, ty: Type, ret_ty: Option<Type>, ops:
pub fn impl_cmpop( pub fn impl_cmpop(
unifier: &mut Unifier, unifier: &mut Unifier,
_store: &PrimitiveStore, store: &PrimitiveStore,
ty: Type, ty: Type,
other_ty: &[Type], other_ty: Type,
ops: &[Cmpop], ops: &[ast::Cmpop],
ret_ty: Option<Type>,
) { ) {
with_fields(unifier, ty, |unifier, fields| { with_fields(unifier, ty, |unifier, fields| {
let (other_ty, other_var_id) = if other_ty.len() == 1 {
(other_ty[0], None)
} else {
let tvar = unifier.get_fresh_var_with_range(other_ty, Some("N".into()), None);
(tvar.ty, Some(tvar.id))
};
let function_vars = if let Some(var_id) = other_var_id {
vec![(var_id, other_ty)].into_iter().collect::<VarMap>()
} else {
VarMap::new()
};
let ret_ty = ret_ty.unwrap_or_else(|| unifier.get_fresh_var(None, None).ty);
for op in ops { for op in ops {
fields.insert( fields.insert(
op.op_info().method_name.into(), comparison_name(op).unwrap().into(),
( (
unifier.add_ty(TypeEnum::TFunc(FunSignature { unifier.add_ty(TypeEnum::TFunc(FunSignature {
ret: ret_ty, ret: store.bool,
vars: function_vars.clone(), vars: HashMap::new(),
args: vec![FuncArg { args: vec![FuncArg {
ty: other_ty, ty: other_ty,
default_value: None, default_value: None,
@ -270,13 +181,13 @@ pub fn impl_cmpop(
}); });
} }
/// `Add`, `Sub`, `Mult` /// Add, Sub, Mult
pub fn impl_basic_arithmetic( pub fn impl_basic_arithmetic(
unifier: &mut Unifier, unifier: &mut Unifier,
store: &PrimitiveStore, store: &PrimitiveStore,
ty: Type, ty: Type,
other_ty: &[Type], other_ty: &[Type],
ret_ty: Option<Type>, ret_ty: Type,
) { ) {
impl_binop( impl_binop(
unifier, unifier,
@ -284,390 +195,94 @@ pub fn impl_basic_arithmetic(
ty, ty,
other_ty, other_ty,
ret_ty, ret_ty,
&[Operator::Add, Operator::Sub, Operator::Mult], &[ast::Operator::Add, ast::Operator::Sub, ast::Operator::Mult],
); )
} }
/// `Pow` /// Pow
pub fn impl_pow( pub fn impl_pow(
unifier: &mut Unifier, unifier: &mut Unifier,
store: &PrimitiveStore, store: &PrimitiveStore,
ty: Type, ty: Type,
other_ty: &[Type], other_ty: &[Type],
ret_ty: Option<Type>, ret_ty: Type,
) { ) {
impl_binop(unifier, store, ty, other_ty, ret_ty, &[Operator::Pow]); impl_binop(unifier, store, ty, other_ty, ret_ty, &[ast::Operator::Pow])
} }
/// `BitOr`, `BitXor`, `BitAnd` /// BitOr, BitXor, BitAnd
pub fn impl_bitwise_arithmetic(unifier: &mut Unifier, store: &PrimitiveStore, ty: Type) { pub fn impl_bitwise_arithmetic(unifier: &mut Unifier, store: &PrimitiveStore, ty: Type) {
impl_binop( impl_binop(
unifier, unifier,
store, store,
ty, ty,
&[ty], &[ty],
Some(ty),
&[Operator::BitAnd, Operator::BitOr, Operator::BitXor],
);
}
/// `LShift`, `RShift`
pub fn impl_bitwise_shift(unifier: &mut Unifier, store: &PrimitiveStore, ty: Type) {
impl_binop(
unifier,
store,
ty, ty,
&[store.int32, store.uint32], &[ast::Operator::BitAnd, ast::Operator::BitOr, ast::Operator::BitXor],
Some(ty), )
&[Operator::LShift, Operator::RShift],
);
} }
/// `Div` /// LShift, RShift
pub fn impl_div( pub fn impl_bitwise_shift(unifier: &mut Unifier, store: &PrimitiveStore, ty: Type) {
unifier: &mut Unifier, impl_binop(unifier, store, ty, &[ty], ty, &[ast::Operator::LShift, ast::Operator::RShift])
store: &PrimitiveStore,
ty: Type,
other_ty: &[Type],
ret_ty: Option<Type>,
) {
impl_binop(unifier, store, ty, other_ty, ret_ty, &[Operator::Div]);
} }
/// `FloorDiv` /// Div
pub fn impl_div(unifier: &mut Unifier, store: &PrimitiveStore, ty: Type, other_ty: &[Type]) {
impl_binop(unifier, store, ty, other_ty, store.float, &[ast::Operator::Div])
}
/// FloorDiv
pub fn impl_floordiv( pub fn impl_floordiv(
unifier: &mut Unifier, unifier: &mut Unifier,
store: &PrimitiveStore, store: &PrimitiveStore,
ty: Type, ty: Type,
other_ty: &[Type], other_ty: &[Type],
ret_ty: Option<Type>, ret_ty: Type,
) { ) {
impl_binop(unifier, store, ty, other_ty, ret_ty, &[Operator::FloorDiv]); impl_binop(unifier, store, ty, other_ty, ret_ty, &[ast::Operator::FloorDiv])
} }
/// `Mod` /// Mod
pub fn impl_mod( pub fn impl_mod(
unifier: &mut Unifier, unifier: &mut Unifier,
store: &PrimitiveStore, store: &PrimitiveStore,
ty: Type, ty: Type,
other_ty: &[Type], other_ty: &[Type],
ret_ty: Option<Type>, ret_ty: Type,
) { ) {
impl_binop(unifier, store, ty, other_ty, ret_ty, &[Operator::Mod]); impl_binop(unifier, store, ty, other_ty, ret_ty, &[ast::Operator::Mod])
} }
/// [`Operator::MatMult`] /// UAdd, USub
pub fn impl_matmul( pub fn impl_sign(unifier: &mut Unifier, _store: &PrimitiveStore, ty: Type) {
unifier: &mut Unifier, impl_unaryop(unifier, ty, ty, &[ast::Unaryop::UAdd, ast::Unaryop::USub])
store: &PrimitiveStore,
ty: Type,
other_ty: &[Type],
ret_ty: Option<Type>,
) {
impl_binop(unifier, store, ty, other_ty, ret_ty, &[Operator::MatMult]);
} }
/// `UAdd`, `USub` /// Invert
pub fn impl_sign(unifier: &mut Unifier, _store: &PrimitiveStore, ty: Type, ret_ty: Option<Type>) { pub fn impl_invert(unifier: &mut Unifier, _store: &PrimitiveStore, ty: Type) {
impl_unaryop(unifier, ty, ret_ty, &[Unaryop::UAdd, Unaryop::USub]); impl_unaryop(unifier, ty, ty, &[ast::Unaryop::Invert])
} }
/// `Invert` /// Not
pub fn impl_invert(unifier: &mut Unifier, _store: &PrimitiveStore, ty: Type, ret_ty: Option<Type>) { pub fn impl_not(unifier: &mut Unifier, store: &PrimitiveStore, ty: Type) {
impl_unaryop(unifier, ty, ret_ty, &[Unaryop::Invert]); impl_unaryop(unifier, ty, store.bool, &[ast::Unaryop::Not])
} }
/// `Not` /// Lt, LtE, Gt, GtE
pub fn impl_not(unifier: &mut Unifier, _store: &PrimitiveStore, ty: Type, ret_ty: Option<Type>) { pub fn impl_comparison(unifier: &mut Unifier, store: &PrimitiveStore, ty: Type, other_ty: Type) {
impl_unaryop(unifier, ty, ret_ty, &[Unaryop::Not]);
}
/// `Lt`, `LtE`, `Gt`, `GtE`
pub fn impl_comparison(
unifier: &mut Unifier,
store: &PrimitiveStore,
ty: Type,
other_ty: &[Type],
ret_ty: Option<Type>,
) {
impl_cmpop( impl_cmpop(
unifier, unifier,
store, store,
ty, ty,
other_ty, other_ty,
&[Cmpop::Lt, Cmpop::Gt, Cmpop::LtE, Cmpop::GtE], &[ast::Cmpop::Lt, ast::Cmpop::Gt, ast::Cmpop::LtE, ast::Cmpop::GtE],
ret_ty, )
);
} }
/// `Eq`, `NotEq` /// Eq, NotEq
pub fn impl_eq( pub fn impl_eq(unifier: &mut Unifier, store: &PrimitiveStore, ty: Type) {
unifier: &mut Unifier, impl_cmpop(unifier, store, ty, ty, &[ast::Cmpop::Eq, ast::Cmpop::NotEq])
store: &PrimitiveStore,
ty: Type,
other_ty: &[Type],
ret_ty: Option<Type>,
) {
impl_cmpop(unifier, store, ty, other_ty, &[Cmpop::Eq, Cmpop::NotEq], ret_ty);
}
/// Returns the expected return type of binary operations with at least one `ndarray` operand.
pub fn typeof_ndarray_broadcast(
unifier: &mut Unifier,
primitives: &PrimitiveStore,
left: Type,
right: Type,
) -> Result<Type, String> {
let is_left_ndarray = left.obj_id(unifier).is_some_and(|id| id == PrimDef::NDArray.id());
let is_right_ndarray = right.obj_id(unifier).is_some_and(|id| id == PrimDef::NDArray.id());
assert!(is_left_ndarray || is_right_ndarray);
if is_left_ndarray && is_right_ndarray {
// Perform broadcasting on two ndarray operands.
let (left_ty_dtype, left_ty_ndims) = unpack_ndarray_var_tys(unifier, left);
let (right_ty_dtype, right_ty_ndims) = unpack_ndarray_var_tys(unifier, right);
assert!(unifier.unioned(left_ty_dtype, right_ty_dtype));
let left_ty_ndims = match &*unifier.get_ty_immutable(left_ty_ndims) {
TypeEnum::TLiteral { values, .. } => values.clone(),
_ => unreachable!(),
};
let right_ty_ndims = match &*unifier.get_ty_immutable(right_ty_ndims) {
TypeEnum::TLiteral { values, .. } => values.clone(),
_ => unreachable!(),
};
let res_ndims = left_ty_ndims
.into_iter()
.cartesian_product(right_ty_ndims)
.map(|(left, right)| {
let left_val = u64::try_from(left).unwrap();
let right_val = u64::try_from(right).unwrap();
max(left_val, right_val)
})
.unique()
.map(SymbolValue::U64)
.collect_vec();
let res_ndims = unifier.get_fresh_literal(res_ndims, None);
Ok(make_ndarray_ty(unifier, primitives, Some(left_ty_dtype), Some(res_ndims)))
} else {
let (ndarray_ty, scalar_ty) = if is_left_ndarray { (left, right) } else { (right, left) };
let (ndarray_ty_dtype, _) = unpack_ndarray_var_tys(unifier, ndarray_ty);
if unifier.unioned(ndarray_ty_dtype, scalar_ty) {
Ok(ndarray_ty)
} else {
let (expected_ty, actual_ty) = if is_left_ndarray {
(ndarray_ty_dtype, scalar_ty)
} else {
(scalar_ty, ndarray_ty_dtype)
};
Err(format!(
"Expected right-hand side operand to be {}, got {}",
unifier.stringify(expected_ty),
unifier.stringify(actual_ty),
))
}
}
}
/// Returns the return type given a binary operator and its primitive operands.
pub fn typeof_binop(
unifier: &mut Unifier,
primitives: &PrimitiveStore,
op: Operator,
lhs: Type,
rhs: Type,
) -> Result<Option<Type>, String> {
let op = Binop { base: op, variant: BinopVariant::Normal };
let is_left_list = lhs.obj_id(unifier).is_some_and(|id| id == PrimDef::List.id());
let is_right_list = rhs.obj_id(unifier).is_some_and(|id| id == PrimDef::List.id());
let is_left_ndarray = lhs.obj_id(unifier).is_some_and(|id| id == PrimDef::NDArray.id());
let is_right_ndarray = rhs.obj_id(unifier).is_some_and(|id| id == PrimDef::NDArray.id());
Ok(Some(match op.base {
Operator::Add | Operator::Sub | Operator::Mult | Operator::Mod | Operator::FloorDiv => {
if is_left_list || is_right_list {
if ![Operator::Add, Operator::Mult].contains(&op.base) {
return Err(format!(
"Binary operator {} not supported for list",
op.op_info().symbol
));
}
if is_left_list {
lhs
} else {
rhs
}
} else if is_left_ndarray || is_right_ndarray {
typeof_ndarray_broadcast(unifier, primitives, lhs, rhs)?
} else if unifier.unioned(lhs, rhs) {
lhs
} else {
return Ok(None);
}
}
Operator::MatMult => {
let (_, lhs_ndims) = unpack_ndarray_var_tys(unifier, lhs);
let lhs_ndims = match &*unifier.get_ty_immutable(lhs_ndims) {
TypeEnum::TLiteral { values, .. } => {
assert_eq!(values.len(), 1);
u64::try_from(values[0].clone()).unwrap()
}
_ => unreachable!(),
};
let (_, rhs_ndims) = unpack_ndarray_var_tys(unifier, rhs);
let rhs_ndims = match &*unifier.get_ty_immutable(rhs_ndims) {
TypeEnum::TLiteral { values, .. } => {
assert_eq!(values.len(), 1);
u64::try_from(values[0].clone()).unwrap()
}
_ => unreachable!(),
};
match (lhs_ndims, rhs_ndims) {
(2, 2) => typeof_ndarray_broadcast(unifier, primitives, lhs, rhs)?,
(lhs, rhs) if lhs == 0 || rhs == 0 => {
return Err(format!(
"Input operand {} does not have enough dimensions (has {lhs}, requires {rhs})",
u8::from(rhs == 0)
))
}
(lhs, rhs) => {
return Err(format!(
"ndarray.__matmul__ on {lhs}D and {rhs}D operands not supported"
))
}
}
}
Operator::Div => {
if is_left_ndarray || is_right_ndarray {
typeof_ndarray_broadcast(unifier, primitives, lhs, rhs)?
} else if unifier.unioned(lhs, rhs) {
primitives.float
} else {
return Ok(None);
}
}
Operator::Pow => {
if is_left_ndarray || is_right_ndarray {
typeof_ndarray_broadcast(unifier, primitives, lhs, rhs)?
} else if [
primitives.int32,
primitives.int64,
primitives.uint32,
primitives.uint64,
primitives.float,
]
.into_iter()
.any(|ty| unifier.unioned(lhs, ty))
{
lhs
} else {
return Ok(None);
}
}
Operator::LShift | Operator::RShift => lhs,
Operator::BitOr | Operator::BitXor | Operator::BitAnd => {
if unifier.unioned(lhs, rhs) {
lhs
} else {
return Ok(None);
}
}
}))
}
pub fn typeof_unaryop(
unifier: &mut Unifier,
primitives: &PrimitiveStore,
op: Unaryop,
operand: Type,
) -> Result<Option<Type>, String> {
let operand_obj_id = operand.obj_id(unifier);
if op == Unaryop::Not
&& operand_obj_id.is_some_and(|id| id == primitives.ndarray.obj_id(unifier).unwrap())
{
return Err(
"The truth value of an array with more than one element is ambiguous".to_string()
);
}
Ok(match op {
Unaryop::Not => match operand_obj_id {
Some(v) if v == PrimDef::NDArray.id() => Some(operand),
Some(_) => Some(primitives.bool),
_ => None,
},
Unaryop::Invert => {
if operand_obj_id.is_some_and(|id| id == PrimDef::Bool.id()) {
Some(primitives.int32)
} else if operand_obj_id.is_some_and(|id| PrimDef::iter().any(|prim| id == prim.id())) {
Some(operand)
} else {
None
}
}
Unaryop::UAdd | Unaryop::USub => {
if operand_obj_id.is_some_and(|id| id == PrimDef::NDArray.id()) {
let (dtype, _) = unpack_ndarray_var_tys(unifier, operand);
if dtype.obj_id(unifier).is_some_and(|id| id == PrimDef::Bool.id()) {
return Err(if op == Unaryop::UAdd {
"The ufunc 'positive' cannot be applied to ndarray[bool, N]".to_string()
} else {
"The numpy boolean negative, the `-` operator, is not supported, use the `~` operator function instead.".to_string()
});
}
Some(operand)
} else if operand_obj_id.is_some_and(|id| id == PrimDef::Bool.id()) {
Some(primitives.int32)
} else if operand_obj_id.is_some_and(|id| PrimDef::iter().any(|prim| id == prim.id())) {
Some(operand)
} else {
None
}
}
})
}
/// Returns the return type given a comparison operator and its primitive operands.
pub fn typeof_cmpop(
unifier: &mut Unifier,
primitives: &PrimitiveStore,
_op: Cmpop,
lhs: Type,
rhs: Type,
) -> Result<Option<Type>, String> {
let is_left_ndarray = lhs.obj_id(unifier).is_some_and(|id| id == PrimDef::NDArray.id());
let is_right_ndarray = rhs.obj_id(unifier).is_some_and(|id| id == PrimDef::NDArray.id());
Ok(Some(if is_left_ndarray || is_right_ndarray {
let brd = typeof_ndarray_broadcast(unifier, primitives, lhs, rhs)?;
let (_, ndims) = unpack_ndarray_var_tys(unifier, brd);
make_ndarray_ty(unifier, primitives, Some(primitives.bool), Some(ndims))
} else if unifier.unioned(lhs, rhs) {
primitives.bool
} else {
return Ok(None);
}))
} }
pub fn set_primitives_magic_methods(store: &PrimitiveStore, unifier: &mut Unifier) { pub fn set_primitives_magic_methods(store: &PrimitiveStore, unifier: &mut Unifier) {
@ -678,77 +293,38 @@ pub fn set_primitives_magic_methods(store: &PrimitiveStore, unifier: &mut Unifie
bool: bool_t, bool: bool_t,
uint32: uint32_t, uint32: uint32_t,
uint64: uint64_t, uint64: uint64_t,
list: list_t,
ndarray: ndarray_t,
.. ..
} = *store; } = *store;
let size_t = store.usize();
/* int ======== */ /* int ======== */
for t in [int32_t, int64_t, uint32_t, uint64_t] { for t in [int32_t, int64_t, uint32_t, uint64_t] {
let ndarray_int_t = make_ndarray_ty(unifier, store, Some(t), None); impl_basic_arithmetic(unifier, store, t, &[t], t);
impl_basic_arithmetic(unifier, store, t, &[t, ndarray_int_t], None); impl_pow(unifier, store, t, &[t], t);
impl_pow(unifier, store, t, &[t, ndarray_int_t], None);
impl_bitwise_arithmetic(unifier, store, t); impl_bitwise_arithmetic(unifier, store, t);
impl_bitwise_shift(unifier, store, t); impl_bitwise_shift(unifier, store, t);
impl_div(unifier, store, t, &[t, ndarray_int_t], None); impl_div(unifier, store, t, &[t]);
impl_floordiv(unifier, store, t, &[t, ndarray_int_t], None); impl_floordiv(unifier, store, t, &[t], t);
impl_mod(unifier, store, t, &[t, ndarray_int_t], None); impl_mod(unifier, store, t, &[t], t);
impl_invert(unifier, store, t, Some(t)); impl_invert(unifier, store, t);
impl_not(unifier, store, t, Some(bool_t)); impl_not(unifier, store, t);
impl_comparison(unifier, store, t, &[t, ndarray_int_t], None); impl_comparison(unifier, store, t, t);
impl_eq(unifier, store, t, &[t, ndarray_int_t], None); impl_eq(unifier, store, t);
} }
for t in [int32_t, int64_t] { for t in [int32_t, int64_t] {
impl_sign(unifier, store, t, Some(t)); impl_sign(unifier, store, t);
} }
/* float ======== */ /* float ======== */
let ndarray_float_t = make_ndarray_ty(unifier, store, Some(float_t), None); impl_basic_arithmetic(unifier, store, float_t, &[float_t], float_t);
let ndarray_int32_t = make_ndarray_ty(unifier, store, Some(int32_t), None); impl_pow(unifier, store, float_t, &[int32_t, float_t], float_t);
impl_basic_arithmetic(unifier, store, float_t, &[float_t, ndarray_float_t], None); impl_div(unifier, store, float_t, &[float_t]);
impl_pow(unifier, store, float_t, &[int32_t, float_t, ndarray_int32_t, ndarray_float_t], None); impl_floordiv(unifier, store, float_t, &[float_t], float_t);
impl_div(unifier, store, float_t, &[float_t, ndarray_float_t], None); impl_mod(unifier, store, float_t, &[float_t], float_t);
impl_floordiv(unifier, store, float_t, &[float_t, ndarray_float_t], None); impl_sign(unifier, store, float_t);
impl_mod(unifier, store, float_t, &[float_t, ndarray_float_t], None); impl_not(unifier, store, float_t);
impl_sign(unifier, store, float_t, Some(float_t)); impl_comparison(unifier, store, float_t, float_t);
impl_not(unifier, store, float_t, Some(bool_t)); impl_eq(unifier, store, float_t);
impl_comparison(unifier, store, float_t, &[float_t, ndarray_float_t], None);
impl_eq(unifier, store, float_t, &[float_t, ndarray_float_t], None);
/* bool ======== */ /* bool ======== */
let ndarray_bool_t = make_ndarray_ty(unifier, store, Some(bool_t), None); impl_not(unifier, store, bool_t);
impl_invert(unifier, store, bool_t, Some(int32_t)); impl_eq(unifier, store, bool_t);
impl_not(unifier, store, bool_t, Some(bool_t));
impl_sign(unifier, store, bool_t, Some(int32_t));
impl_eq(unifier, store, bool_t, &[bool_t, ndarray_bool_t], None);
/* list ======== */
impl_binop(unifier, store, list_t, &[list_t], Some(list_t), &[Operator::Add]);
impl_binop(unifier, store, list_t, &[int32_t, int64_t], Some(list_t), &[Operator::Mult]);
impl_cmpop(unifier, store, list_t, &[list_t], &[Cmpop::Eq, Cmpop::NotEq], Some(bool_t));
/* ndarray ===== */
let ndarray_usized_ndims_tvar =
unifier.get_fresh_const_generic_var(size_t, Some("ndarray_ndims".into()), None);
let ndarray_unsized_t =
make_ndarray_ty(unifier, store, None, Some(ndarray_usized_ndims_tvar.ty));
let (ndarray_dtype_t, _) = unpack_ndarray_var_tys(unifier, ndarray_t);
let (ndarray_unsized_dtype_t, _) = unpack_ndarray_var_tys(unifier, ndarray_unsized_t);
impl_basic_arithmetic(
unifier,
store,
ndarray_t,
&[ndarray_unsized_t, ndarray_unsized_dtype_t],
None,
);
impl_pow(unifier, store, ndarray_t, &[ndarray_unsized_t, ndarray_unsized_dtype_t], None);
impl_div(unifier, store, ndarray_t, &[ndarray_t, ndarray_dtype_t], None);
impl_floordiv(unifier, store, ndarray_t, &[ndarray_unsized_t, ndarray_unsized_dtype_t], None);
impl_mod(unifier, store, ndarray_t, &[ndarray_unsized_t, ndarray_unsized_dtype_t], None);
impl_matmul(unifier, store, ndarray_t, &[ndarray_t], Some(ndarray_t));
impl_sign(unifier, store, ndarray_t, Some(ndarray_t));
impl_invert(unifier, store, ndarray_t, Some(ndarray_t));
impl_eq(unifier, store, ndarray_t, &[ndarray_unsized_t, ndarray_unsized_dtype_t], None);
impl_comparison(unifier, store, ndarray_t, &[ndarray_unsized_t, ndarray_unsized_dtype_t], None);
} }

View File

@ -1,46 +1,24 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::Display; use std::fmt::Display;
use crate::typecheck::{magic_methods::HasOpInfo, typedef::TypeEnum}; use crate::typecheck::typedef::TypeEnum;
use super::{ use super::typedef::{RecordKey, Type, Unifier};
magic_methods::Binop, use nac3parser::ast::{Location, StrRef};
typedef::{RecordKey, Type, Unifier},
};
use itertools::Itertools;
use nac3parser::ast::{Cmpop, Location, StrRef};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum TypeErrorKind { pub enum TypeErrorKind {
GotMultipleValues {
name: StrRef,
},
TooManyArguments { TooManyArguments {
expected_min_count: usize, expected: usize,
expected_max_count: usize, got: usize,
got_count: usize,
},
MissingArgs {
missing_arg_names: Vec<StrRef>,
}, },
MissingArgs(String),
UnknownArgName(StrRef), UnknownArgName(StrRef),
IncorrectArgType { IncorrectArgType {
name: StrRef, name: StrRef,
expected: Type, expected: Type,
got: Type, got: Type,
}, },
UnsupportedBinaryOpTypes {
operator: Binop,
lhs_type: Type,
rhs_type: Type,
expected_rhs_type: Type,
},
UnsupportedComparsionOpTypes {
operator: Cmpop,
lhs_type: Type,
rhs_type: Type,
expected_rhs_type: Type,
},
FieldUnificationError { FieldUnificationError {
field: RecordKey, field: RecordKey,
types: (Type, Type), types: (Type, Type),
@ -56,7 +34,6 @@ pub enum TypeErrorKind {
}, },
RequiresTypeAnn, RequiresTypeAnn,
PolymorphicFunctionPointer, PolymorphicFunctionPointer,
NoSuchAttribute(RecordKey, Type),
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -66,18 +43,15 @@ pub struct TypeError {
} }
impl TypeError { impl TypeError {
#[must_use]
pub fn new(kind: TypeErrorKind, loc: Option<Location>) -> TypeError { pub fn new(kind: TypeErrorKind, loc: Option<Location>) -> TypeError {
TypeError { kind, loc } TypeError { kind, loc }
} }
#[must_use]
pub fn at(mut self, loc: Option<Location>) -> TypeError { pub fn at(mut self, loc: Option<Location>) -> TypeError {
self.loc = self.loc.or(loc); self.loc = self.loc.or(loc);
self self
} }
#[must_use]
pub fn to_display(self, unifier: &Unifier) -> DisplayTypeError { pub fn to_display(self, unifier: &Unifier) -> DisplayTypeError {
DisplayTypeError { err: self, unifier } DisplayTypeError { err: self, unifier }
} }
@ -90,8 +64,8 @@ pub struct DisplayTypeError<'a> {
fn loc_to_str(loc: Option<Location>) -> String { fn loc_to_str(loc: Option<Location>) -> String {
match loc { match loc {
Some(loc) => format!("(in {loc})"), Some(loc) => format!("(in {})", loc),
None => String::new(), None => "".to_string(),
} }
} }
@ -100,49 +74,23 @@ impl<'a> Display for DisplayTypeError<'a> {
use TypeErrorKind::*; use TypeErrorKind::*;
let mut notes = Some(HashMap::new()); let mut notes = Some(HashMap::new());
match &self.err.kind { match &self.err.kind {
GotMultipleValues { name } => { TooManyArguments { expected, got } => {
write!(f, "For multiple values for parameter {name}") write!(f, "Too many arguments. Expected {} but got {}", expected, got)
} }
TooManyArguments { expected_min_count, expected_max_count, got_count } => { MissingArgs(args) => {
debug_assert!(expected_min_count <= expected_max_count); write!(f, "Missing arguments: {}", args)
if expected_min_count == expected_max_count {
let expected_count = expected_min_count; // or expected_max_count
write!(f, "Too many arguments. Expected {expected_count} but got {got_count}")
} else {
write!(f, "Too many arguments. Expected {expected_min_count} to {expected_max_count} arguments but got {got_count}")
}
}
MissingArgs { missing_arg_names } => {
let args = missing_arg_names.iter().join(", ");
write!(f, "Missing arguments: {args}")
}
UnsupportedBinaryOpTypes { operator, lhs_type, rhs_type, expected_rhs_type } => {
let op_symbol = operator.op_info().symbol;
let lhs_type_str = self.unifier.stringify_with_notes(*lhs_type, &mut notes);
let rhs_type_str = self.unifier.stringify_with_notes(*rhs_type, &mut notes);
let expected_rhs_type_str =
self.unifier.stringify_with_notes(*expected_rhs_type, &mut notes);
write!(f, "Unsupported operand type(s) for {op_symbol}: '{lhs_type_str}' and '{rhs_type_str}' (right operand should have type {expected_rhs_type_str})")
}
UnsupportedComparsionOpTypes { operator, lhs_type, rhs_type, expected_rhs_type } => {
let op_symbol = operator.op_info().symbol;
let lhs_type_str = self.unifier.stringify_with_notes(*lhs_type, &mut notes);
let rhs_type_str = self.unifier.stringify_with_notes(*rhs_type, &mut notes);
let expected_rhs_type_str =
self.unifier.stringify_with_notes(*expected_rhs_type, &mut notes);
write!(f, "'{op_symbol}' not supported between instances of '{lhs_type_str}' and '{rhs_type_str}' (right operand should have type {expected_rhs_type_str})")
} }
UnknownArgName(name) => { UnknownArgName(name) => {
write!(f, "Unknown argument name: {name}") write!(f, "Unknown argument name: {}", name)
} }
IncorrectArgType { name, expected, got } => { IncorrectArgType { name, expected, got } => {
let expected = self.unifier.stringify_with_notes(*expected, &mut notes); let expected = self.unifier.stringify_with_notes(*expected, &mut notes);
let got = self.unifier.stringify_with_notes(*got, &mut notes); let got = self.unifier.stringify_with_notes(*got, &mut notes);
write!(f, "Incorrect argument type for parameter {name}. Expected {expected}, but got {got}") write!(
f,
"Incorrect argument type for {}. Expected {}, but got {}",
name, expected, got
)
} }
FieldUnificationError { field, types, loc } => { FieldUnificationError { field, types, loc } => {
let lhs = self.unifier.stringify_with_notes(types.0, &mut notes); let lhs = self.unifier.stringify_with_notes(types.0, &mut notes);
@ -178,7 +126,7 @@ impl<'a> Display for DisplayTypeError<'a> {
); );
if let Some(loc) = loc { if let Some(loc) = loc {
result?; result?;
write!(f, " (in {loc})")?; write!(f, " (in {})", loc)?;
return Ok(()); return Ok(());
} }
result result
@ -188,12 +136,12 @@ impl<'a> Display for DisplayTypeError<'a> {
{ {
let t1 = self.unifier.stringify_with_notes(*t1, &mut notes); let t1 = self.unifier.stringify_with_notes(*t1, &mut notes);
let t2 = self.unifier.stringify_with_notes(*t2, &mut notes); let t2 = self.unifier.stringify_with_notes(*t2, &mut notes);
write!(f, "Tuple length mismatch: got {t1} and {t2}") write!(f, "Tuple length mismatch: got {} and {}", t1, t2)
} }
_ => { _ => {
let t1 = self.unifier.stringify_with_notes(*t1, &mut notes); let t1 = self.unifier.stringify_with_notes(*t1, &mut notes);
let t2 = self.unifier.stringify_with_notes(*t2, &mut notes); let t2 = self.unifier.stringify_with_notes(*t2, &mut notes);
write!(f, "Incompatible types: {t1} and {t2}") write!(f, "Incompatible types: {} and {}", t1, t2)
} }
} }
} }
@ -202,21 +150,18 @@ impl<'a> Display for DisplayTypeError<'a> {
write!(f, "Cannot assign to an element of a tuple") write!(f, "Cannot assign to an element of a tuple")
} else { } else {
let t = self.unifier.stringify_with_notes(*t, &mut notes); let t = self.unifier.stringify_with_notes(*t, &mut notes);
write!(f, "Cannot assign to field {name} of {t}, which is immutable") write!(f, "Cannot assign to field {} of {}, which is immutable", name, t)
} }
} }
NoSuchField(name, t) => { NoSuchField(name, t) => {
let t = self.unifier.stringify_with_notes(*t, &mut notes); let t = self.unifier.stringify_with_notes(*t, &mut notes);
write!(f, "`{t}::{name}` field/method does not exist") write!(f, "`{}::{}` field/method does not exist", t, name)
}
NoSuchAttribute(name, t) => {
let t = self.unifier.stringify_with_notes(*t, &mut notes);
write!(f, "`{t}::{name}` is not a class attribute")
} }
TupleIndexOutOfBounds { index, len } => { TupleIndexOutOfBounds { index, len } => {
write!( write!(
f, f,
"Tuple index out of bounds. Got {index} but tuple has only {len} elements" "Tuple index out of bounds. Got {} but tuple has only {} elements",
index, len
) )
} }
RequiresTypeAnn => { RequiresTypeAnn => {
@ -227,13 +172,13 @@ impl<'a> Display for DisplayTypeError<'a> {
} }
}?; }?;
if let Some(loc) = self.err.loc { if let Some(loc) = self.err.loc {
write!(f, " at {loc}")?; write!(f, " at {}", loc)?;
} }
let notes = notes.unwrap(); let notes = notes.unwrap();
if !notes.is_empty() { if !notes.is_empty() {
write!(f, "\n\nNotes:")?; write!(f, "\n\nNotes:")?;
for line in notes.values() { for line in notes.values() {
write!(f, "\n {line}")?; write!(f, "\n {}", line)?;
} }
} }
Ok(()) Ok(())

File diff suppressed because it is too large Load Diff

View File

@ -3,14 +3,12 @@ use super::*;
use crate::{ use crate::{
codegen::CodeGenContext, codegen::CodeGenContext,
symbol_resolver::ValueEnum, symbol_resolver::ValueEnum,
toplevel::{helper::PrimDef, DefinitionId, TopLevelDef}, toplevel::{DefinitionId, TopLevelDef},
}; };
use indexmap::IndexMap;
use indoc::indoc; use indoc::indoc;
use nac3parser::ast::FileName; use itertools::zip;
use nac3parser::parser::parse_program; use nac3parser::parser::parse_program;
use parking_lot::RwLock; use parking_lot::RwLock;
use std::iter::zip;
use test_case::test_case; use test_case::test_case;
struct Resolver { struct Resolver {
@ -22,7 +20,7 @@ struct Resolver {
impl SymbolResolver for Resolver { impl SymbolResolver for Resolver {
fn get_default_param_value( fn get_default_param_value(
&self, &self,
_: &ast::Expr, _: &nac3parser::ast::Expr,
) -> Option<crate::symbol_resolver::SymbolValue> { ) -> Option<crate::symbol_resolver::SymbolValue> {
unimplemented!() unimplemented!()
} }
@ -34,22 +32,19 @@ impl SymbolResolver for Resolver {
_: &PrimitiveStore, _: &PrimitiveStore,
str: StrRef, str: StrRef,
) -> Result<Type, String> { ) -> Result<Type, String> {
self.id_to_type.get(&str).copied().ok_or_else(|| format!("cannot find symbol `{str}`")) self.id_to_type.get(&str).cloned().ok_or_else(|| format!("cannot find symbol `{}`", str))
} }
fn get_symbol_value<'ctx>( fn get_symbol_value<'ctx, 'a>(
&self, &self,
_: StrRef, _: StrRef,
_: &mut CodeGenContext<'ctx, '_>, _: &mut CodeGenContext<'ctx, 'a>,
) -> Option<ValueEnum<'ctx>> { ) -> Option<ValueEnum<'ctx>> {
unimplemented!() unimplemented!()
} }
fn get_identifier_def(&self, id: StrRef) -> Result<DefinitionId, HashSet<String>> { fn get_identifier_def(&self, id: StrRef) -> Result<DefinitionId, String> {
self.id_to_def self.id_to_def.get(&id).cloned().ok_or_else(|| "Unknown identifier".to_string())
.get(&id)
.copied()
.ok_or_else(|| HashSet::from(["Unknown identifier".to_string()]))
} }
fn get_string_id(&self, _: &str) -> i32 { fn get_string_id(&self, _: &str) -> i32 {
@ -67,7 +62,7 @@ struct TestEnvironment {
pub primitives: PrimitiveStore, pub primitives: PrimitiveStore,
pub id_to_name: HashMap<usize, StrRef>, pub id_to_name: HashMap<usize, StrRef>,
pub identifier_mapping: HashMap<StrRef, Type>, pub identifier_mapping: HashMap<StrRef, Type>,
pub virtual_checks: Vec<(Type, Type, Location)>, pub virtual_checks: Vec<(Type, Type, nac3parser::ast::Location)>,
pub calls: HashMap<CodeLocation, CallId>, pub calls: HashMap<CodeLocation, CallId>,
pub top_level: TopLevelContext, pub top_level: TopLevelContext,
} }
@ -77,81 +72,67 @@ impl TestEnvironment {
let mut unifier = Unifier::new(); let mut unifier = Unifier::new();
let int32 = unifier.add_ty(TypeEnum::TObj { let int32 = unifier.add_ty(TypeEnum::TObj {
obj_id: PrimDef::Int32.id(), obj_id: DefinitionId(0),
fields: HashMap::new(), fields: HashMap::new(),
params: VarMap::new(), params: HashMap::new(),
}); });
with_fields(&mut unifier, int32, |unifier, fields| { with_fields(&mut unifier, int32, |unifier, fields| {
let add_ty = unifier.add_ty(TypeEnum::TFunc(FunSignature { let add_ty = unifier.add_ty(TypeEnum::TFunc(FunSignature {
args: vec![FuncArg { name: "other".into(), ty: int32, default_value: None }], args: vec![FuncArg { name: "other".into(), ty: int32, default_value: None }],
ret: int32, ret: int32,
vars: VarMap::new(), vars: HashMap::new(),
})); }));
fields.insert("__add__".into(), (add_ty, false)); fields.insert("__add__".into(), (add_ty, false));
}); });
let int64 = unifier.add_ty(TypeEnum::TObj { let int64 = unifier.add_ty(TypeEnum::TObj {
obj_id: PrimDef::Int64.id(), obj_id: DefinitionId(1),
fields: HashMap::new(), fields: HashMap::new(),
params: VarMap::new(), params: HashMap::new(),
}); });
let float = unifier.add_ty(TypeEnum::TObj { let float = unifier.add_ty(TypeEnum::TObj {
obj_id: PrimDef::Float.id(), obj_id: DefinitionId(2),
fields: HashMap::new(), fields: HashMap::new(),
params: VarMap::new(), params: HashMap::new(),
}); });
let bool = unifier.add_ty(TypeEnum::TObj { let bool = unifier.add_ty(TypeEnum::TObj {
obj_id: PrimDef::Bool.id(), obj_id: DefinitionId(3),
fields: HashMap::new(), fields: HashMap::new(),
params: VarMap::new(), params: HashMap::new(),
}); });
let none = unifier.add_ty(TypeEnum::TObj { let none = unifier.add_ty(TypeEnum::TObj {
obj_id: PrimDef::None.id(), obj_id: DefinitionId(4),
fields: HashMap::new(), fields: HashMap::new(),
params: VarMap::new(), params: HashMap::new(),
}); });
let range = unifier.add_ty(TypeEnum::TObj { let range = unifier.add_ty(TypeEnum::TObj {
obj_id: PrimDef::Range.id(), obj_id: DefinitionId(5),
fields: HashMap::new(), fields: HashMap::new(),
params: VarMap::new(), params: HashMap::new(),
}); });
let str = unifier.add_ty(TypeEnum::TObj { let str = unifier.add_ty(TypeEnum::TObj {
obj_id: PrimDef::Str.id(), obj_id: DefinitionId(6),
fields: HashMap::new(), fields: HashMap::new(),
params: VarMap::new(), params: HashMap::new(),
}); });
let exception = unifier.add_ty(TypeEnum::TObj { let exception = unifier.add_ty(TypeEnum::TObj {
obj_id: PrimDef::Exception.id(), obj_id: DefinitionId(7),
fields: HashMap::new(), fields: HashMap::new(),
params: VarMap::new(), params: HashMap::new(),
}); });
let uint32 = unifier.add_ty(TypeEnum::TObj { let uint32 = unifier.add_ty(TypeEnum::TObj {
obj_id: PrimDef::UInt32.id(), obj_id: DefinitionId(8),
fields: HashMap::new(), fields: HashMap::new(),
params: VarMap::new(), params: HashMap::new(),
}); });
let uint64 = unifier.add_ty(TypeEnum::TObj { let uint64 = unifier.add_ty(TypeEnum::TObj {
obj_id: PrimDef::UInt64.id(), obj_id: DefinitionId(9),
fields: HashMap::new(), fields: HashMap::new(),
params: VarMap::new(), params: HashMap::new(),
}); });
let option = unifier.add_ty(TypeEnum::TObj { let option = unifier.add_ty(TypeEnum::TObj {
obj_id: PrimDef::Option.id(), obj_id: DefinitionId(10),
fields: HashMap::new(), fields: HashMap::new(),
params: VarMap::new(), params: HashMap::new(),
});
let list_elem_tvar = unifier.get_fresh_var(Some("list_elem".into()), None);
let list = unifier.add_ty(TypeEnum::TObj {
obj_id: PrimDef::List.id(),
fields: HashMap::new(),
params: into_var_map([list_elem_tvar]),
});
let ndarray_dtype_tvar = unifier.get_fresh_var(Some("ndarray_dtype".into()), None);
let ndarray_ndims_tvar =
unifier.get_fresh_const_generic_var(uint64, Some("ndarray_ndims".into()), None);
let ndarray = unifier.add_ty(TypeEnum::TObj {
obj_id: PrimDef::NDArray.id(),
fields: HashMap::new(),
params: into_var_map([ndarray_dtype_tvar, ndarray_ndims_tvar]),
}); });
let primitives = PrimitiveStore { let primitives = PrimitiveStore {
int32, int32,
@ -165,14 +146,10 @@ impl TestEnvironment {
uint32, uint32,
uint64, uint64,
option, option,
list,
ndarray,
size_t: 64,
}; };
unifier.put_primitive_store(&primitives);
set_primitives_magic_methods(&primitives, &mut unifier); set_primitives_magic_methods(&primitives, &mut unifier);
let id_to_name: HashMap<_, _> = [ let id_to_name = [
(0, "int32".into()), (0, "int32".into()),
(1, "int64".into()), (1, "int64".into()),
(2, "float".into()), (2, "float".into()),
@ -182,21 +159,23 @@ impl TestEnvironment {
(6, "str".into()), (6, "str".into()),
(7, "exception".into()), (7, "exception".into()),
] ]
.into(); .iter()
.cloned()
.collect();
let mut identifier_mapping = HashMap::new(); let mut identifier_mapping = HashMap::new();
identifier_mapping.insert("None".into(), none); identifier_mapping.insert("None".into(), none);
let resolver = Arc::new(Resolver { let resolver = Arc::new(Resolver {
id_to_type: identifier_mapping.clone(), id_to_type: identifier_mapping.clone(),
id_to_def: HashMap::default(), id_to_def: Default::default(),
class_names: HashMap::default(), class_names: Default::default(),
}) as Arc<dyn SymbolResolver + Send + Sync>; }) as Arc<dyn SymbolResolver + Send + Sync>;
TestEnvironment { TestEnvironment {
top_level: TopLevelContext { top_level: TopLevelContext {
definitions: Arc::default(), definitions: Default::default(),
unifiers: Arc::default(), unifiers: Default::default(),
personality_symbol: None, personality_symbol: None,
}, },
unifier, unifier,
@ -218,107 +197,81 @@ impl TestEnvironment {
let mut identifier_mapping = HashMap::new(); let mut identifier_mapping = HashMap::new();
let mut top_level_defs: Vec<Arc<RwLock<TopLevelDef>>> = Vec::new(); let mut top_level_defs: Vec<Arc<RwLock<TopLevelDef>>> = Vec::new();
let int32 = unifier.add_ty(TypeEnum::TObj { let int32 = unifier.add_ty(TypeEnum::TObj {
obj_id: PrimDef::Int32.id(), obj_id: DefinitionId(0),
fields: HashMap::new(), fields: HashMap::new(),
params: VarMap::new(), params: HashMap::new(),
}); });
with_fields(&mut unifier, int32, |unifier, fields| { with_fields(&mut unifier, int32, |unifier, fields| {
let add_ty = unifier.add_ty(TypeEnum::TFunc(FunSignature { let add_ty = unifier.add_ty(TypeEnum::TFunc(FunSignature {
args: vec![FuncArg { name: "other".into(), ty: int32, default_value: None }], args: vec![FuncArg { name: "other".into(), ty: int32, default_value: None }],
ret: int32, ret: int32,
vars: VarMap::new(), vars: HashMap::new(),
})); }));
fields.insert("__add__".into(), (add_ty, false)); fields.insert("__add__".into(), (add_ty, false));
}); });
let int64 = unifier.add_ty(TypeEnum::TObj { let int64 = unifier.add_ty(TypeEnum::TObj {
obj_id: PrimDef::Int64.id(), obj_id: DefinitionId(1),
fields: HashMap::new(), fields: HashMap::new(),
params: VarMap::new(), params: HashMap::new(),
}); });
let float = unifier.add_ty(TypeEnum::TObj { let float = unifier.add_ty(TypeEnum::TObj {
obj_id: PrimDef::Float.id(), obj_id: DefinitionId(2),
fields: HashMap::new(), fields: HashMap::new(),
params: VarMap::new(), params: HashMap::new(),
}); });
let bool = unifier.add_ty(TypeEnum::TObj { let bool = unifier.add_ty(TypeEnum::TObj {
obj_id: PrimDef::Bool.id(), obj_id: DefinitionId(3),
fields: HashMap::new(), fields: HashMap::new(),
params: VarMap::new(), params: HashMap::new(),
}); });
let none = unifier.add_ty(TypeEnum::TObj { let none = unifier.add_ty(TypeEnum::TObj {
obj_id: PrimDef::None.id(), obj_id: DefinitionId(4),
fields: HashMap::new(), fields: HashMap::new(),
params: VarMap::new(), params: HashMap::new(),
}); });
let range = unifier.add_ty(TypeEnum::TObj { let range = unifier.add_ty(TypeEnum::TObj {
obj_id: PrimDef::Range.id(), obj_id: DefinitionId(5),
fields: HashMap::new(), fields: HashMap::new(),
params: VarMap::new(), params: HashMap::new(),
}); });
let str = unifier.add_ty(TypeEnum::TObj { let str = unifier.add_ty(TypeEnum::TObj {
obj_id: PrimDef::Str.id(), obj_id: DefinitionId(6),
fields: HashMap::new(), fields: HashMap::new(),
params: VarMap::new(), params: HashMap::new(),
}); });
let exception = unifier.add_ty(TypeEnum::TObj { let exception = unifier.add_ty(TypeEnum::TObj {
obj_id: PrimDef::Exception.id(), obj_id: DefinitionId(7),
fields: HashMap::new(), fields: HashMap::new(),
params: VarMap::new(), params: HashMap::new(),
}); });
let uint32 = unifier.add_ty(TypeEnum::TObj { let uint32 = unifier.add_ty(TypeEnum::TObj {
obj_id: PrimDef::UInt32.id(), obj_id: DefinitionId(8),
fields: HashMap::new(), fields: HashMap::new(),
params: VarMap::new(), params: HashMap::new(),
}); });
let uint64 = unifier.add_ty(TypeEnum::TObj { let uint64 = unifier.add_ty(TypeEnum::TObj {
obj_id: PrimDef::UInt64.id(), obj_id: DefinitionId(9),
fields: HashMap::new(), fields: HashMap::new(),
params: VarMap::new(), params: HashMap::new(),
}); });
let option = unifier.add_ty(TypeEnum::TObj { let option = unifier.add_ty(TypeEnum::TObj {
obj_id: PrimDef::Option.id(), obj_id: DefinitionId(10),
fields: HashMap::new(), fields: HashMap::new(),
params: VarMap::new(), params: HashMap::new(),
});
let list_elem_tvar = unifier.get_fresh_var(Some("list_elem".into()), None);
let list = unifier.add_ty(TypeEnum::TObj {
obj_id: PrimDef::List.id(),
fields: HashMap::new(),
params: into_var_map([list_elem_tvar]),
});
let ndarray = unifier.add_ty(TypeEnum::TObj {
obj_id: PrimDef::NDArray.id(),
fields: HashMap::new(),
params: VarMap::new(),
}); });
identifier_mapping.insert("None".into(), none); identifier_mapping.insert("None".into(), none);
for (i, name) in [ for (i, name) in ["int32", "int64", "float", "bool", "none", "range", "str", "Exception"]
"int32", .iter()
"int64", .enumerate()
"float",
"bool",
"none",
"range",
"str",
"Exception",
"uint32",
"uint64",
"Option",
"list",
"ndarray",
]
.iter()
.enumerate()
{ {
top_level_defs.push( top_level_defs.push(
RwLock::new(TopLevelDef::Class { RwLock::new(TopLevelDef::Class {
name: (*name).into(), name: (*name).into(),
object_id: DefinitionId(i), object_id: DefinitionId(i),
type_vars: Vec::default(), type_vars: Default::default(),
fields: Vec::default(), fields: Default::default(),
attributes: Vec::default(), methods: Default::default(),
methods: Vec::default(), ancestors: Default::default(),
ancestors: Vec::default(),
resolver: None, resolver: None,
constructor: None, constructor: None,
loc: None, loc: None,
@ -326,7 +279,7 @@ impl TestEnvironment {
.into(), .into(),
); );
} }
let defs = 12; let defs = 7;
let primitives = PrimitiveStore { let primitives = PrimitiveStore {
int32, int32,
@ -340,29 +293,23 @@ impl TestEnvironment {
uint32, uint32,
uint64, uint64,
option, option,
list,
ndarray,
size_t: 64,
}; };
unifier.put_primitive_store(&primitives); let (v0, id) = unifier.get_dummy_var();
let tvar = unifier.get_dummy_var();
let foo_ty = unifier.add_ty(TypeEnum::TObj { let foo_ty = unifier.add_ty(TypeEnum::TObj {
obj_id: DefinitionId(defs + 1), obj_id: DefinitionId(defs + 1),
fields: [("a".into(), (tvar.ty, true))].into(), fields: [("a".into(), (v0, true))].iter().cloned().collect::<HashMap<_, _>>(),
params: into_var_map([tvar]), params: [(id, v0)].iter().cloned().collect::<HashMap<_, _>>(),
}); });
top_level_defs.push( top_level_defs.push(
RwLock::new(TopLevelDef::Class { RwLock::new(TopLevelDef::Class {
name: "Foo".into(), name: "Foo".into(),
object_id: DefinitionId(defs + 1), object_id: DefinitionId(defs + 1),
type_vars: vec![tvar.ty], type_vars: vec![v0],
fields: [("a".into(), tvar.ty, true)].into(), fields: [("a".into(), v0, true)].into(),
attributes: Vec::default(), methods: Default::default(),
methods: Vec::default(), ancestors: Default::default(),
ancestors: Vec::default(),
resolver: None, resolver: None,
constructor: None, constructor: None,
loc: None, loc: None,
@ -375,29 +322,31 @@ impl TestEnvironment {
unifier.add_ty(TypeEnum::TFunc(FunSignature { unifier.add_ty(TypeEnum::TFunc(FunSignature {
args: vec![], args: vec![],
ret: foo_ty, ret: foo_ty,
vars: into_var_map([tvar]), vars: [(id, v0)].iter().cloned().collect(),
})), })),
); );
let fun = unifier.add_ty(TypeEnum::TFunc(FunSignature { let fun = unifier.add_ty(TypeEnum::TFunc(FunSignature {
args: vec![], args: vec![],
ret: int32, ret: int32,
vars: IndexMap::default(), vars: Default::default(),
})); }));
let bar = unifier.add_ty(TypeEnum::TObj { let bar = unifier.add_ty(TypeEnum::TObj {
obj_id: DefinitionId(defs + 2), obj_id: DefinitionId(defs + 2),
fields: [("a".into(), (int32, true)), ("b".into(), (fun, true))].into(), fields: [("a".into(), (int32, true)), ("b".into(), (fun, true))]
params: IndexMap::default(), .iter()
.cloned()
.collect::<HashMap<_, _>>(),
params: Default::default(),
}); });
top_level_defs.push( top_level_defs.push(
RwLock::new(TopLevelDef::Class { RwLock::new(TopLevelDef::Class {
name: "Bar".into(), name: "Bar".into(),
object_id: DefinitionId(defs + 2), object_id: DefinitionId(defs + 2),
type_vars: Vec::default(), type_vars: Default::default(),
fields: [("a".into(), int32, true), ("b".into(), fun, true)].into(), fields: [("a".into(), int32, true), ("b".into(), fun, true)].into(),
attributes: Vec::default(), methods: Default::default(),
methods: Vec::default(), ancestors: Default::default(),
ancestors: Vec::default(),
resolver: None, resolver: None,
constructor: None, constructor: None,
loc: None, loc: None,
@ -409,24 +358,26 @@ impl TestEnvironment {
unifier.add_ty(TypeEnum::TFunc(FunSignature { unifier.add_ty(TypeEnum::TFunc(FunSignature {
args: vec![], args: vec![],
ret: bar, ret: bar,
vars: IndexMap::default(), vars: Default::default(),
})), })),
); );
let bar2 = unifier.add_ty(TypeEnum::TObj { let bar2 = unifier.add_ty(TypeEnum::TObj {
obj_id: DefinitionId(defs + 3), obj_id: DefinitionId(defs + 3),
fields: [("a".into(), (bool, true)), ("b".into(), (fun, false))].into(), fields: [("a".into(), (bool, true)), ("b".into(), (fun, false))]
params: IndexMap::default(), .iter()
.cloned()
.collect::<HashMap<_, _>>(),
params: Default::default(),
}); });
top_level_defs.push( top_level_defs.push(
RwLock::new(TopLevelDef::Class { RwLock::new(TopLevelDef::Class {
name: "Bar2".into(), name: "Bar2".into(),
object_id: DefinitionId(defs + 3), object_id: DefinitionId(defs + 3),
type_vars: Vec::default(), type_vars: Default::default(),
fields: [("a".into(), bool, true), ("b".into(), fun, false)].into(), fields: [("a".into(), bool, true), ("b".into(), fun, false)].into(),
attributes: Vec::default(), methods: Default::default(),
methods: Vec::default(), ancestors: Default::default(),
ancestors: Vec::default(),
resolver: None, resolver: None,
constructor: None, constructor: None,
loc: None, loc: None,
@ -438,10 +389,10 @@ impl TestEnvironment {
unifier.add_ty(TypeEnum::TFunc(FunSignature { unifier.add_ty(TypeEnum::TFunc(FunSignature {
args: vec![], args: vec![],
ret: bar2, ret: bar2,
vars: IndexMap::default(), vars: Default::default(),
})), })),
); );
let class_names: HashMap<_, _> = [("Bar".into(), bar), ("Bar2".into(), bar2)].into(); let class_names = [("Bar".into(), bar), ("Bar2".into(), bar2)].iter().cloned().collect();
let id_to_name = [ let id_to_name = [
"int32".into(), "int32".into(),
@ -452,22 +403,18 @@ impl TestEnvironment {
"range".into(), "range".into(),
"str".into(), "str".into(),
"exception".into(), "exception".into(),
"uint32".into(),
"uint64".into(),
"option".into(),
"list".into(),
"ndarray".into(),
"Foo".into(), "Foo".into(),
"Bar".into(), "Bar".into(),
"Bar2".into(), "Bar2".into(),
] ]
.into_iter() .iter()
.enumerate() .enumerate()
.map(|(a, b)| (a, *b))
.collect(); .collect();
let top_level = TopLevelContext { let top_level = TopLevelContext {
definitions: Arc::new(top_level_defs.into()), definitions: Arc::new(top_level_defs.into()),
unifiers: Arc::default(), unifiers: Default::default(),
personality_symbol: None, personality_symbol: None,
}; };
@ -478,7 +425,9 @@ impl TestEnvironment {
("Bar".into(), DefinitionId(defs + 2)), ("Bar".into(), DefinitionId(defs + 2)),
("Bar2".into(), DefinitionId(defs + 3)), ("Bar2".into(), DefinitionId(defs + 3)),
] ]
.into(), .iter()
.cloned()
.collect(),
class_names, class_names,
}) as Arc<dyn SymbolResolver + Send + Sync>; }) as Arc<dyn SymbolResolver + Send + Sync>;
@ -503,11 +452,11 @@ impl TestEnvironment {
top_level: &self.top_level, top_level: &self.top_level,
function_data: &mut self.function_data, function_data: &mut self.function_data,
unifier: &mut self.unifier, unifier: &mut self.unifier,
variable_mapping: HashMap::default(), variable_mapping: Default::default(),
primitives: &mut self.primitives, primitives: &mut self.primitives,
virtual_checks: &mut self.virtual_checks, virtual_checks: &mut self.virtual_checks,
calls: &mut self.calls, calls: &mut self.calls,
defined_identifiers: HashSet::default(), defined_identifiers: Default::default(),
in_handler: false, in_handler: false,
} }
} }
@ -519,7 +468,7 @@ impl TestEnvironment {
c = 1.234 c = 1.234
d = True d = True
"}, "},
&[("a", "int32"), ("b", "int64"), ("c", "float"), ("d", "bool")].into(), [("a", "int32"), ("b", "int64"), ("c", "float"), ("d", "bool")].iter().cloned().collect(),
&[] &[]
; "primitives test")] ; "primitives test")]
#[test_case(indoc! {" #[test_case(indoc! {"
@ -528,7 +477,7 @@ impl TestEnvironment {
c = 1.234 c = 1.234
d = b(c) d = b(c)
"}, "},
&[("a", "fn[[x:float, y:float], float]"), ("b", "fn[[x:float], float]"), ("c", "float"), ("d", "float")].into(), [("a", "fn[[x:float, y:float], float]"), ("b", "fn[[x:float], float]"), ("c", "float"), ("d", "float")].iter().cloned().collect(),
&[] &[]
; "lambda test")] ; "lambda test")]
#[test_case(indoc! {" #[test_case(indoc! {"
@ -537,7 +486,7 @@ impl TestEnvironment {
a = b a = b
c = b(1) c = b(1)
"}, "},
&[("a", "fn[[x:int32], int32]"), ("b", "fn[[x:int32], int32]"), ("c", "int32")].into(), [("a", "fn[[x:int32], int32]"), ("b", "fn[[x:int32], int32]"), ("c", "int32")].iter().cloned().collect(),
&[] &[]
; "lambda test 2")] ; "lambda test 2")]
#[test_case(indoc! {" #[test_case(indoc! {"
@ -553,15 +502,15 @@ impl TestEnvironment {
b(123) b(123)
"}, "},
&[("a", "fn[[x:bool], bool]"), ("b", "fn[[x:int32], int32]"), ("c", "bool"), [("a", "fn[[x:bool], bool]"), ("b", "fn[[x:int32], int32]"), ("c", "bool"),
("d", "int32"), ("foo1", "Foo[bool]"), ("foo2", "Foo[int32]")].into(), ("d", "int32"), ("foo1", "Foo[bool]"), ("foo2", "Foo[int32]")].iter().cloned().collect(),
&[] &[]
; "obj test")] ; "obj test")]
#[test_case(indoc! {" #[test_case(indoc! {"
a = [1, 2, 3] a = [1, 2, 3]
b = [x + x for x in a] b = [x + x for x in a]
"}, "},
&[("a", "list[int32]"), ("b", "list[int32]")].into(), [("a", "list[int32]"), ("b", "list[int32]")].iter().cloned().collect(),
&[] &[]
; "listcomp test")] ; "listcomp test")]
#[test_case(indoc! {" #[test_case(indoc! {"
@ -569,25 +518,25 @@ impl TestEnvironment {
b = a.b() b = a.b()
a = virtual(Bar2()) a = virtual(Bar2())
"}, "},
&[("a", "virtual[Bar]"), ("b", "int32")].into(), [("a", "virtual[Bar]"), ("b", "int32")].iter().cloned().collect(),
&[("Bar", "Bar"), ("Bar2", "Bar")] &[("Bar", "Bar"), ("Bar2", "Bar")]
; "virtual test")] ; "virtual test")]
#[test_case(indoc! {" #[test_case(indoc! {"
a = [virtual(Bar(), Bar), virtual(Bar2())] a = [virtual(Bar(), Bar), virtual(Bar2())]
b = [x.b() for x in a] b = [x.b() for x in a]
"}, "},
&[("a", "list[virtual[Bar]]"), ("b", "list[int32]")].into(), [("a", "list[virtual[Bar]]"), ("b", "list[int32]")].iter().cloned().collect(),
&[("Bar", "Bar"), ("Bar2", "Bar")] &[("Bar", "Bar"), ("Bar2", "Bar")]
; "virtual list test")] ; "virtual list test")]
fn test_basic(source: &str, mapping: &HashMap<&str, &str>, virtuals: &[(&str, &str)]) { fn test_basic(source: &str, mapping: HashMap<&str, &str>, virtuals: &[(&str, &str)]) {
println!("source:\n{source}"); println!("source:\n{}", source);
let mut env = TestEnvironment::new(); let mut env = TestEnvironment::new();
let id_to_name = std::mem::take(&mut env.id_to_name); let id_to_name = std::mem::take(&mut env.id_to_name);
let mut defined_identifiers: HashSet<_> = env.identifier_mapping.keys().copied().collect(); let mut defined_identifiers: HashSet<_> = env.identifier_mapping.keys().cloned().collect();
defined_identifiers.insert("virtual".into()); defined_identifiers.insert("virtual".into());
let mut inferencer = env.get_inferencer(); let mut inferencer = env.get_inferencer();
inferencer.defined_identifiers.clone_from(&defined_identifiers); inferencer.defined_identifiers = defined_identifiers.clone();
let statements = parse_program(source, FileName::default()).unwrap(); let statements = parse_program(source, Default::default()).unwrap();
let statements = statements let statements = statements
.into_iter() .into_iter()
.map(|v| inferencer.fold_stmt(v)) .map(|v| inferencer.fold_stmt(v))
@ -596,37 +545,37 @@ fn test_basic(source: &str, mapping: &HashMap<&str, &str>, virtuals: &[(&str, &s
inferencer.check_block(&statements, &mut defined_identifiers).unwrap(); inferencer.check_block(&statements, &mut defined_identifiers).unwrap();
for (k, v) in &inferencer.variable_mapping { for (k, v) in inferencer.variable_mapping.iter() {
let name = inferencer.unifier.internal_stringify( let name = inferencer.unifier.internal_stringify(
*v, *v,
&mut |v| (*id_to_name.get(&v).unwrap()).into(), &mut |v| (*id_to_name.get(&v).unwrap()).into(),
&mut |v| format!("v{v}"), &mut |v| format!("v{}", v),
&mut None, &mut None,
); );
println!("{k}: {name}"); println!("{}: {}", k, name);
} }
for (k, v) in mapping { for (k, v) in mapping.iter() {
let ty = inferencer.variable_mapping.get(&(*k).into()).unwrap(); let ty = inferencer.variable_mapping.get(&(*k).into()).unwrap();
let name = inferencer.unifier.internal_stringify( let name = inferencer.unifier.internal_stringify(
*ty, *ty,
&mut |v| (*id_to_name.get(&v).unwrap()).into(), &mut |v| (*id_to_name.get(&v).unwrap()).into(),
&mut |v| format!("v{v}"), &mut |v| format!("v{}", v),
&mut None, &mut None,
); );
assert_eq!(format!("{k}: {v}"), format!("{k}: {name}")); assert_eq!(format!("{}: {}", k, v), format!("{}: {}", k, name));
} }
assert_eq!(inferencer.virtual_checks.len(), virtuals.len()); assert_eq!(inferencer.virtual_checks.len(), virtuals.len());
for ((a, b, _), (x, y)) in zip(inferencer.virtual_checks.iter(), virtuals) { for ((a, b, _), (x, y)) in zip(inferencer.virtual_checks.iter(), virtuals) {
let a = inferencer.unifier.internal_stringify( let a = inferencer.unifier.internal_stringify(
*a, *a,
&mut |v| (*id_to_name.get(&v).unwrap()).into(), &mut |v| (*id_to_name.get(&v).unwrap()).into(),
&mut |v| format!("v{v}"), &mut |v| format!("v{}", v),
&mut None, &mut None,
); );
let b = inferencer.unifier.internal_stringify( let b = inferencer.unifier.internal_stringify(
*b, *b,
&mut |v| (*id_to_name.get(&v).unwrap()).into(), &mut |v| (*id_to_name.get(&v).unwrap()).into(),
&mut |v| format!("v{v}"), &mut |v| format!("v{}", v),
&mut None, &mut None,
); );
@ -645,14 +594,14 @@ fn test_basic(source: &str, mapping: &HashMap<&str, &str>, virtuals: &[(&str, &s
g = a // b g = a // b
h = a % b h = a % b
"}, "},
&[("a", "int32"), [("a", "int32"),
("b", "int32"), ("b", "int32"),
("c", "int32"), ("c", "int32"),
("d", "int32"), ("d", "int32"),
("e", "int32"), ("e", "int32"),
("f", "float"), ("f", "float"),
("g", "int32"), ("g", "int32"),
("h", "int32")].into() ("h", "int32")].iter().cloned().collect()
; "int32")] ; "int32")]
#[test_case( #[test_case(
indoc! {" indoc! {"
@ -668,7 +617,7 @@ fn test_basic(source: &str, mapping: &HashMap<&str, &str>, virtuals: &[(&str, &s
ii = 3 ii = 3
j = a ** b j = a ** b
"}, "},
&[("a", "float"), [("a", "float"),
("b", "float"), ("b", "float"),
("c", "float"), ("c", "float"),
("d", "float"), ("d", "float"),
@ -678,7 +627,7 @@ fn test_basic(source: &str, mapping: &HashMap<&str, &str>, virtuals: &[(&str, &s
("h", "float"), ("h", "float"),
("i", "float"), ("i", "float"),
("ii", "int32"), ("ii", "int32"),
("j", "float")].into() ("j", "float")].iter().cloned().collect()
; "float" ; "float"
)] )]
#[test_case( #[test_case(
@ -696,7 +645,7 @@ fn test_basic(source: &str, mapping: &HashMap<&str, &str>, virtuals: &[(&str, &s
k = a < b k = a < b
l = a != b l = a != b
"}, "},
&[("a", "int64"), [("a", "int64"),
("b", "int64"), ("b", "int64"),
("c", "int64"), ("c", "int64"),
("d", "int64"), ("d", "int64"),
@ -707,7 +656,7 @@ fn test_basic(source: &str, mapping: &HashMap<&str, &str>, virtuals: &[(&str, &s
("i", "bool"), ("i", "bool"),
("j", "bool"), ("j", "bool"),
("k", "bool"), ("k", "bool"),
("l", "bool")].into() ("l", "bool")].iter().cloned().collect()
; "int64" ; "int64"
)] )]
#[test_case( #[test_case(
@ -718,22 +667,22 @@ fn test_basic(source: &str, mapping: &HashMap<&str, &str>, virtuals: &[(&str, &s
d = not a d = not a
e = a != b e = a != b
"}, "},
&[("a", "bool"), [("a", "bool"),
("b", "bool"), ("b", "bool"),
("c", "bool"), ("c", "bool"),
("d", "bool"), ("d", "bool"),
("e", "bool")].into() ("e", "bool")].iter().cloned().collect()
; "boolean" ; "boolean"
)] )]
fn test_primitive_magic_methods(source: &str, mapping: &HashMap<&str, &str>) { fn test_primitive_magic_methods(source: &str, mapping: HashMap<&str, &str>) {
println!("source:\n{source}"); println!("source:\n{}", source);
let mut env = TestEnvironment::basic_test_env(); let mut env = TestEnvironment::basic_test_env();
let id_to_name = std::mem::take(&mut env.id_to_name); let id_to_name = std::mem::take(&mut env.id_to_name);
let mut defined_identifiers: HashSet<_> = env.identifier_mapping.keys().copied().collect(); let mut defined_identifiers: HashSet<_> = env.identifier_mapping.keys().cloned().collect();
defined_identifiers.insert("virtual".into()); defined_identifiers.insert("virtual".into());
let mut inferencer = env.get_inferencer(); let mut inferencer = env.get_inferencer();
inferencer.defined_identifiers.clone_from(&defined_identifiers); inferencer.defined_identifiers = defined_identifiers.clone();
let statements = parse_program(source, FileName::default()).unwrap(); let statements = parse_program(source, Default::default()).unwrap();
let statements = statements let statements = statements
.into_iter() .into_iter()
.map(|v| inferencer.fold_stmt(v)) .map(|v| inferencer.fold_stmt(v))
@ -742,23 +691,23 @@ fn test_primitive_magic_methods(source: &str, mapping: &HashMap<&str, &str>) {
inferencer.check_block(&statements, &mut defined_identifiers).unwrap(); inferencer.check_block(&statements, &mut defined_identifiers).unwrap();
for (k, v) in &inferencer.variable_mapping { for (k, v) in inferencer.variable_mapping.iter() {
let name = inferencer.unifier.internal_stringify( let name = inferencer.unifier.internal_stringify(
*v, *v,
&mut |v| (*id_to_name.get(&v).unwrap()).into(), &mut |v| (*id_to_name.get(&v).unwrap()).into(),
&mut |v| format!("v{v}"), &mut |v| format!("v{}", v),
&mut None, &mut None,
); );
println!("{k}: {name}"); println!("{}: {}", k, name);
} }
for (k, v) in mapping { for (k, v) in mapping.iter() {
let ty = inferencer.variable_mapping.get(&(*k).into()).unwrap(); let ty = inferencer.variable_mapping.get(&(*k).into()).unwrap();
let name = inferencer.unifier.internal_stringify( let name = inferencer.unifier.internal_stringify(
*ty, *ty,
&mut |v| (*id_to_name.get(&v).unwrap()).into(), &mut |v| (*id_to_name.get(&v).unwrap()).into(),
&mut |v| format!("v{v}"), &mut |v| format!("v{}", v),
&mut None, &mut None,
); );
assert_eq!(format!("{k}: {v}"), format!("{k}: {name}")); assert_eq!(format!("{}: {}", k, v), format!("{}: {}", k, name));
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -32,25 +32,28 @@ impl Unifier {
ty1.len() == ty2.len() ty1.len() == ty2.len()
&& ty1.iter().zip(ty2.iter()).all(|(t1, t2)| self.eq(*t1, *t2)) && ty1.iter().zip(ty2.iter()).all(|(t1, t2)| self.eq(*t1, *t2))
} }
(TypeEnum::TVirtual { ty: ty1 }, TypeEnum::TVirtual { ty: ty2 }) => self.eq(*ty1, *ty2), (TypeEnum::TList { ty: ty1 }, TypeEnum::TList { ty: ty2 })
| (TypeEnum::TVirtual { ty: ty1 }, TypeEnum::TVirtual { ty: ty2 }) => {
self.eq(*ty1, *ty2)
}
( (
TypeEnum::TObj { obj_id: id1, params: params1, .. }, TypeEnum::TObj { obj_id: id1, params: params1, .. },
TypeEnum::TObj { obj_id: id2, params: params2, .. }, TypeEnum::TObj { obj_id: id2, params: params2, .. },
) => id1 == id2 && self.map_eq(params1, params2), ) => id1 == id2 && self.map_eq(params1, params2),
// TLiteral, TCall and TFunc are not yet implemented // TCall and TFunc are not yet implemented
_ => false, _ => false,
} }
} }
fn map_eq<K>(&mut self, map1: &IndexMapping<K>, map2: &IndexMapping<K>) -> bool fn map_eq<K>(&mut self, map1: &Mapping<K>, map2: &Mapping<K>) -> bool
where where
K: std::hash::Hash + Eq + Clone, K: std::hash::Hash + std::cmp::Eq + std::clone::Clone,
{ {
if map1.len() != map2.len() { if map1.len() != map2.len() {
return false; return false;
} }
for (k, v) in map1 { for (k, v) in map1.iter() {
if !map2.get(k).is_some_and(|v1| self.eq(*v, *v1)) { if !map2.get(k).map(|v1| self.eq(*v, *v1)).unwrap_or(false) {
return false; return false;
} }
} }
@ -59,13 +62,13 @@ impl Unifier {
fn map_eq2<K>(&mut self, map1: &Mapping<K, RecordField>, map2: &Mapping<K, RecordField>) -> bool fn map_eq2<K>(&mut self, map1: &Mapping<K, RecordField>, map2: &Mapping<K, RecordField>) -> bool
where where
K: std::hash::Hash + Eq + Clone, K: std::hash::Hash + std::cmp::Eq + std::clone::Clone,
{ {
if map1.len() != map2.len() { if map1.len() != map2.len() {
return false; return false;
} }
for (k, v) in map1 { for (k, v) in map1.iter() {
if !map2.get(k).is_some_and(|v1| self.eq(v.ty, v1.ty)) { if !map2.get(k).map(|v1| self.eq(v.ty, v1.ty)).unwrap_or(false) {
return false; return false;
} }
} }
@ -88,7 +91,7 @@ impl TestEnvironment {
unifier.add_ty(TypeEnum::TObj { unifier.add_ty(TypeEnum::TObj {
obj_id: DefinitionId(0), obj_id: DefinitionId(0),
fields: HashMap::new(), fields: HashMap::new(),
params: VarMap::new(), params: HashMap::new(),
}), }),
); );
type_mapping.insert( type_mapping.insert(
@ -96,7 +99,7 @@ impl TestEnvironment {
unifier.add_ty(TypeEnum::TObj { unifier.add_ty(TypeEnum::TObj {
obj_id: DefinitionId(1), obj_id: DefinitionId(1),
fields: HashMap::new(), fields: HashMap::new(),
params: VarMap::new(), params: HashMap::new(),
}), }),
); );
type_mapping.insert( type_mapping.insert(
@ -104,25 +107,16 @@ impl TestEnvironment {
unifier.add_ty(TypeEnum::TObj { unifier.add_ty(TypeEnum::TObj {
obj_id: DefinitionId(2), obj_id: DefinitionId(2),
fields: HashMap::new(), fields: HashMap::new(),
params: VarMap::new(), params: HashMap::new(),
}), }),
); );
let tvar = unifier.get_dummy_var(); let (v0, id) = unifier.get_dummy_var();
type_mapping.insert( type_mapping.insert(
"Foo".into(), "Foo".into(),
unifier.add_ty(TypeEnum::TObj { unifier.add_ty(TypeEnum::TObj {
obj_id: DefinitionId(3), obj_id: DefinitionId(3),
fields: [("a".into(), (tvar.ty, true))].into(), fields: [("a".into(), (v0, true))].iter().cloned().collect::<HashMap<_, _>>(),
params: into_var_map([tvar]), params: [(id, v0)].iter().cloned().collect::<HashMap<_, _>>(),
}),
);
let tvar = unifier.get_dummy_var();
type_mapping.insert(
"list".into(),
unifier.add_ty(TypeEnum::TObj {
obj_id: PrimDef::List.id(),
fields: HashMap::new(),
params: into_var_map([tvar]),
}), }),
); );
@ -135,43 +129,17 @@ impl TestEnvironment {
result.0 result.0
} }
fn internal_parse<'b>(&mut self, typ: &'b str, mapping: &Mapping<String>) -> (Type, &'b str) { fn internal_parse<'a, 'b>(
&'a mut self,
typ: &'b str,
mapping: &Mapping<String>,
) -> (Type, &'b str) {
// for testing only, so we can just panic when the input is malformed // for testing only, so we can just panic when the input is malformed
let end = typ.find(|c| ['[', ',', ']', '='].contains(&c)).unwrap_or(typ.len()); let end = typ.find(|c| ['[', ',', ']', '='].contains(&c)).unwrap_or_else(|| typ.len());
match &typ[..end] { match &typ[..end] {
"list" => {
let mut s = &typ[end..];
assert_eq!(&s[0..1], "[");
let mut ty = Vec::new();
while &s[0..1] != "]" {
let result = self.internal_parse(&s[1..], mapping);
ty.push(result.0);
s = result.1;
}
assert_eq!(ty.len(), 1);
let list_elem_tvar = if let TypeEnum::TObj { params, .. } =
&*self.unifier.get_ty_immutable(self.type_mapping["list"])
{
iter_type_vars(params).next().unwrap()
} else {
unreachable!()
};
(
self.unifier
.subst(
self.type_mapping["list"],
&into_var_map([TypeVar { id: list_elem_tvar.id, ty: ty[0] }]),
)
.unwrap(),
&s[1..],
)
}
"tuple" => { "tuple" => {
let mut s = &typ[end..]; let mut s = &typ[end..];
assert_eq!(&s[0..1], "["); assert!(&s[0..1] == "[");
let mut ty = Vec::new(); let mut ty = Vec::new();
while &s[0..1] != "]" { while &s[0..1] != "]" {
let result = self.internal_parse(&s[1..], mapping); let result = self.internal_parse(&s[1..], mapping);
@ -180,9 +148,15 @@ impl TestEnvironment {
} }
(self.unifier.add_ty(TypeEnum::TTuple { ty }), &s[1..]) (self.unifier.add_ty(TypeEnum::TTuple { ty }), &s[1..])
} }
"list" => {
assert!(&typ[end..end + 1] == "[");
let (ty, s) = self.internal_parse(&typ[end + 1..], mapping);
assert!(&s[0..1] == "]");
(self.unifier.add_ty(TypeEnum::TList { ty }), &s[1..])
}
"Record" => { "Record" => {
let mut s = &typ[end..]; let mut s = &typ[end..];
assert_eq!(&s[0..1], "["); assert!(&s[0..1] == "[");
let mut fields = HashMap::new(); let mut fields = HashMap::new();
while &s[0..1] != "]" { while &s[0..1] != "]" {
let eq = s.find('=').unwrap(); let eq = s.find('=').unwrap();
@ -195,14 +169,14 @@ impl TestEnvironment {
} }
x => { x => {
let mut s = &typ[end..]; let mut s = &typ[end..];
let ty = mapping.get(x).copied().unwrap_or_else(|| { let ty = mapping.get(x).cloned().unwrap_or_else(|| {
// mapping should be type variables, type_mapping should be concrete types // mapping should be type variables, type_mapping should be concrete types
// we should not resolve the type of type variables. // we should not resolve the type of type variables.
let mut ty = *self.type_mapping.get(x).unwrap(); let mut ty = *self.type_mapping.get(x).unwrap();
let te = self.unifier.get_ty(ty); let te = self.unifier.get_ty(ty);
if let TypeEnum::TObj { params, .. } = &*te { if let TypeEnum::TObj { params, .. } = &*te.as_ref() {
if !params.is_empty() { if !params.is_empty() {
assert_eq!(&s[0..1], "["); assert!(&s[0..1] == "[");
let mut p = Vec::new(); let mut p = Vec::new();
while &s[0..1] != "]" { while &s[0..1] != "]" {
let result = self.internal_parse(&s[1..], mapping); let result = self.internal_parse(&s[1..], mapping);
@ -212,7 +186,7 @@ impl TestEnvironment {
s = &s[1..]; s = &s[1..];
ty = self ty = self
.unifier .unifier
.subst(ty, &params.keys().copied().zip(p).collect()) .subst(ty, &params.keys().cloned().zip(p.into_iter()).collect())
.unwrap_or(ty); .unwrap_or(ty);
} }
} }
@ -276,12 +250,12 @@ fn test_unify(
let mut mapping = HashMap::new(); let mut mapping = HashMap::new();
for i in 1..=variable_count { for i in 1..=variable_count {
let v = env.unifier.get_dummy_var(); let v = env.unifier.get_dummy_var();
mapping.insert(format!("v{i}"), v.ty); mapping.insert(format!("v{}", i), v.0);
} }
// unification may have side effect when we do type resolution, so freeze the types // unification may have side effect when we do type resolution, so freeze the types
// before doing unification. // before doing unification.
let mut pairs = Vec::new(); let mut pairs = Vec::new();
for (a, b) in &perm { for (a, b) in perm.iter() {
let t1 = env.parse(a, &mapping); let t1 = env.parse(a, &mapping);
let t2 = env.parse(b, &mapping); let t2 = env.parse(b, &mapping);
pairs.push((t1, t2)); pairs.push((t1, t2));
@ -289,8 +263,8 @@ fn test_unify(
for (t1, t2) in pairs { for (t1, t2) in pairs {
env.unifier.unify(t1, t2).unwrap(); env.unifier.unify(t1, t2).unwrap();
} }
for (a, b) in verify_pairs { for (a, b) in verify_pairs.iter() {
println!("{a} = {b}"); println!("{} = {}", a, b);
let t1 = env.parse(a, &mapping); let t1 = env.parse(a, &mapping);
let t2 = env.parse(b, &mapping); let t2 = env.parse(b, &mapping);
println!("a = {}, b = {}", env.unifier.stringify(t1), env.unifier.stringify(t2)); println!("a = {}, b = {}", env.unifier.stringify(t1), env.unifier.stringify(t2));
@ -304,7 +278,7 @@ fn test_unify(
("v1", "tuple[int]"), ("v1", "tuple[int]"),
("v2", "list[int]"), ("v2", "list[int]"),
], ],
(("v1", "v2"), "Incompatible types: 11[0] and tuple[0]") (("v1", "v2"), "Incompatible types: list[0] and tuple[0]")
; "type mismatch" ; "type mismatch"
)] )]
#[test_case(2, #[test_case(2,
@ -328,7 +302,7 @@ fn test_unify(
("v1", "Record[a=float,b=int]"), ("v1", "Record[a=float,b=int]"),
("v2", "Foo[v3]"), ("v2", "Foo[v3]"),
], ],
(("v1", "v2"), "`3[typevar5]::b` field/method does not exist") (("v1", "v2"), "`3[typevar4]::b` field/method does not exist")
; "record obj merge" ; "record obj merge"
)] )]
/// Test cases for invalid unifications. /// Test cases for invalid unifications.
@ -341,12 +315,12 @@ fn test_invalid_unification(
let mut mapping = HashMap::new(); let mut mapping = HashMap::new();
for i in 1..=variable_count { for i in 1..=variable_count {
let v = env.unifier.get_dummy_var(); let v = env.unifier.get_dummy_var();
mapping.insert(format!("v{i}"), v.ty); mapping.insert(format!("v{}", i), v.0);
} }
// unification may have side effect when we do type resolution, so freeze the types // unification may have side effect when we do type resolution, so freeze the types
// before doing unification. // before doing unification.
let mut pairs = Vec::new(); let mut pairs = Vec::new();
for (a, b) in unify_pairs { for (a, b) in unify_pairs.iter() {
let t1 = env.parse(a, &mapping); let t1 = env.parse(a, &mapping);
let t2 = env.parse(b, &mapping); let t2 = env.parse(b, &mapping);
pairs.push((t1, t2)); pairs.push((t1, t2));
@ -365,17 +339,23 @@ fn test_recursive_subst() {
let int = *env.type_mapping.get("int").unwrap(); let int = *env.type_mapping.get("int").unwrap();
let foo_id = *env.type_mapping.get("Foo").unwrap(); let foo_id = *env.type_mapping.get("Foo").unwrap();
let foo_ty = env.unifier.get_ty(foo_id); let foo_ty = env.unifier.get_ty(foo_id);
let mapping: HashMap<_, _>;
with_fields(&mut env.unifier, foo_id, |_unifier, fields| { with_fields(&mut env.unifier, foo_id, |_unifier, fields| {
fields.insert("rec".into(), (foo_id, true)); fields.insert("rec".into(), (foo_id, true));
}); });
let TypeEnum::TObj { params, .. } = &*foo_ty else { unreachable!() }; if let TypeEnum::TObj { params, .. } = &*foo_ty {
let mapping = params.iter().map(|(id, _)| (*id, int)).collect(); mapping = params.iter().map(|(id, _)| (*id, int)).collect();
} else {
unreachable!()
}
let instantiated = env.unifier.subst(foo_id, &mapping).unwrap(); let instantiated = env.unifier.subst(foo_id, &mapping).unwrap();
let instantiated_ty = env.unifier.get_ty(instantiated); let instantiated_ty = env.unifier.get_ty(instantiated);
if let TypeEnum::TObj { fields, .. } = &*instantiated_ty {
let TypeEnum::TObj { fields, .. } = &*instantiated_ty else { unreachable!() }; assert!(env.unifier.unioned(fields.get(&"a".into()).unwrap().0, int));
assert!(env.unifier.unioned(fields.get(&"a".into()).unwrap().0, int)); assert!(env.unifier.unioned(fields.get(&"rec".into()).unwrap().0, instantiated));
assert!(env.unifier.unioned(fields.get(&"rec".into()).unwrap().0, instantiated)); } else {
unreachable!()
}
} }
#[test] #[test]
@ -385,27 +365,36 @@ fn test_virtual() {
let fun = env.unifier.add_ty(TypeEnum::TFunc(FunSignature { let fun = env.unifier.add_ty(TypeEnum::TFunc(FunSignature {
args: vec![], args: vec![],
ret: int, ret: int,
vars: VarMap::new(), vars: HashMap::new(),
})); }));
let bar = env.unifier.add_ty(TypeEnum::TObj { let bar = env.unifier.add_ty(TypeEnum::TObj {
obj_id: DefinitionId(5), obj_id: DefinitionId(5),
fields: [("f".into(), (fun, false)), ("a".into(), (int, false))].into(), fields: [("f".into(), (fun, false)), ("a".into(), (int, false))]
params: VarMap::new(), .iter()
.cloned()
.collect::<HashMap<StrRef, _>>(),
params: HashMap::new(),
}); });
let v0 = env.unifier.get_dummy_var().ty; let v0 = env.unifier.get_dummy_var().0;
let v1 = env.unifier.get_dummy_var().ty; let v1 = env.unifier.get_dummy_var().0;
let a = env.unifier.add_ty(TypeEnum::TVirtual { ty: bar }); let a = env.unifier.add_ty(TypeEnum::TVirtual { ty: bar });
let b = env.unifier.add_ty(TypeEnum::TVirtual { ty: v0 }); let b = env.unifier.add_ty(TypeEnum::TVirtual { ty: v0 });
let c = env.unifier.add_record([("f".into(), RecordField::new(v1, false, None))].into()); let c = env
.unifier
.add_record([("f".into(), RecordField::new(v1, false, None))].iter().cloned().collect());
env.unifier.unify(a, b).unwrap(); env.unifier.unify(a, b).unwrap();
env.unifier.unify(b, c).unwrap(); env.unifier.unify(b, c).unwrap();
assert!(env.unifier.eq(v1, fun)); assert!(env.unifier.eq(v1, fun));
let d = env.unifier.add_record([("a".into(), RecordField::new(v1, true, None))].into()); let d = env
.unifier
.add_record([("a".into(), RecordField::new(v1, true, None))].iter().cloned().collect());
assert_eq!(env.unify(b, d), Err("`virtual[5]::a` field/method does not exist".to_string())); assert_eq!(env.unify(b, d), Err("`virtual[5]::a` field/method does not exist".to_string()));
let d = env.unifier.add_record([("b".into(), RecordField::new(v1, true, None))].into()); let d = env
.unifier
.add_record([("b".into(), RecordField::new(v1, true, None))].iter().cloned().collect());
assert_eq!(env.unify(b, d), Err("`virtual[5]::b` field/method does not exist".to_string())); assert_eq!(env.unify(b, d), Err("`virtual[5]::b` field/method does not exist".to_string()));
} }
@ -418,132 +407,86 @@ fn test_typevar_range() {
let int_list = env.parse("list[int]", &HashMap::new()); let int_list = env.parse("list[int]", &HashMap::new());
let float_list = env.parse("list[float]", &HashMap::new()); let float_list = env.parse("list[float]", &HashMap::new());
let list_elem_tvar = if let TypeEnum::TObj { params, .. } =
&*env.unifier.get_ty_immutable(env.type_mapping["list"])
{
iter_type_vars(params).next().unwrap()
} else {
unreachable!()
};
// unification between v and int // unification between v and int
// where v in (int, bool) // where v in (int, bool)
let v = env.unifier.get_fresh_var_with_range(&[int, boolean], None, None).ty; let v = env.unifier.get_fresh_var_with_range(&[int, boolean], None, None).0;
env.unifier.unify(int, v).unwrap(); env.unifier.unify(int, v).unwrap();
// unification between v and list[int] // unification between v and list[int]
// where v in (int, bool) // where v in (int, bool)
let v = env.unifier.get_fresh_var_with_range(&[int, boolean], None, None).ty; let v = env.unifier.get_fresh_var_with_range(&[int, boolean], None, None).0;
assert_eq!( assert_eq!(
env.unify(int_list, v), env.unify(int_list, v),
Err("Expected any one of these types: 0, 2, but got 11[0]".to_string()) Err("Expected any one of these types: 0, 2, but got list[0]".to_string())
); );
// unification between v and float // unification between v and float
// where v in (int, bool) // where v in (int, bool)
let v = env.unifier.get_fresh_var_with_range(&[int, boolean], None, None).ty; let v = env.unifier.get_fresh_var_with_range(&[int, boolean], None, None).0;
assert_eq!( assert_eq!(
env.unify(float, v), env.unify(float, v),
Err("Expected any one of these types: 0, 2, but got 1".to_string()) Err("Expected any one of these types: 0, 2, but got 1".to_string())
); );
let v1 = env.unifier.get_fresh_var_with_range(&[int, boolean], None, None).ty; let v1 = env.unifier.get_fresh_var_with_range(&[int, boolean], None, None).0;
let v1_list = env.unifier.add_ty(TypeEnum::TObj { let v1_list = env.unifier.add_ty(TypeEnum::TList { ty: v1 });
obj_id: env.type_mapping["list"].obj_id(&env.unifier).unwrap(), let v = env.unifier.get_fresh_var_with_range(&[int, v1_list], None, None).0;
fields: Mapping::default(),
params: into_var_map([TypeVar { id: list_elem_tvar.id, ty: v1 }]),
});
let v = env.unifier.get_fresh_var_with_range(&[int, v1_list], None, None).ty;
// unification between v and int // unification between v and int
// where v in (int, list[v1]), v1 in (int, bool) // where v in (int, list[v1]), v1 in (int, bool)
env.unifier.unify(int, v).unwrap(); env.unifier.unify(int, v).unwrap();
let v = env.unifier.get_fresh_var_with_range(&[int, v1_list], None, None).ty; let v = env.unifier.get_fresh_var_with_range(&[int, v1_list], None, None).0;
// unification between v and list[int] // unification between v and list[int]
// where v in (int, list[v1]), v1 in (int, bool) // where v in (int, list[v1]), v1 in (int, bool)
env.unifier.unify(int_list, v).unwrap(); env.unifier.unify(int_list, v).unwrap();
let v = env.unifier.get_fresh_var_with_range(&[int, v1_list], None, None).ty; let v = env.unifier.get_fresh_var_with_range(&[int, v1_list], None, None).0;
// unification between v and list[float] // unification between v and list[float]
// where v in (int, list[v1]), v1 in (int, bool) // where v in (int, list[v1]), v1 in (int, bool)
println!("float_list: {}, v: {}", env.unifier.stringify(float_list), env.unifier.stringify(v));
assert_eq!( assert_eq!(
env.unify(float_list, v), env.unify(float_list, v),
Err("Expected any one of these types: 0, 11[typevar6], but got 11[1]\n\nNotes:\n typevar6 ∈ {0, 2}".to_string()) Err("Expected any one of these types: 0, list[typevar5], but got list[1]\n\nNotes:\n typevar5 ∈ {0, 2}".to_string())
); );
let a = env.unifier.get_fresh_var_with_range(&[int, float], None, None).ty; let a = env.unifier.get_fresh_var_with_range(&[int, float], None, None).0;
let b = env.unifier.get_fresh_var_with_range(&[boolean, float], None, None).ty; let b = env.unifier.get_fresh_var_with_range(&[boolean, float], None, None).0;
env.unifier.unify(a, b).unwrap(); env.unifier.unify(a, b).unwrap();
env.unifier.unify(a, float).unwrap(); env.unifier.unify(a, float).unwrap();
let a = env.unifier.get_fresh_var_with_range(&[int, float], None, None).ty; let a = env.unifier.get_fresh_var_with_range(&[int, float], None, None).0;
let b = env.unifier.get_fresh_var_with_range(&[boolean, float], None, None).ty; let b = env.unifier.get_fresh_var_with_range(&[boolean, float], None, None).0;
env.unifier.unify(a, b).unwrap(); env.unifier.unify(a, b).unwrap();
assert_eq!(env.unify(a, int), Err("Expected any one of these types: 1, but got 0".into())); assert_eq!(env.unify(a, int), Err("Expected any one of these types: 1, but got 0".into()));
let a = env.unifier.get_fresh_var_with_range(&[int, float], None, None).ty; let a = env.unifier.get_fresh_var_with_range(&[int, float], None, None).0;
let b = env.unifier.get_fresh_var_with_range(&[boolean, float], None, None).ty; let b = env.unifier.get_fresh_var_with_range(&[boolean, float], None, None).0;
let a_list = env.unifier.add_ty(TypeEnum::TObj { let a_list = env.unifier.add_ty(TypeEnum::TList { ty: a });
obj_id: env.type_mapping["list"].obj_id(&env.unifier).unwrap(), let a_list = env.unifier.get_fresh_var_with_range(&[a_list], None, None).0;
fields: Mapping::default(), let b_list = env.unifier.add_ty(TypeEnum::TList { ty: b });
params: into_var_map([TypeVar { id: list_elem_tvar.id, ty: a }]), let b_list = env.unifier.get_fresh_var_with_range(&[b_list], None, None).0;
});
let a_list = env.unifier.get_fresh_var_with_range(&[a_list], None, None).ty;
let b_list = env.unifier.add_ty(TypeEnum::TObj {
obj_id: env.type_mapping["list"].obj_id(&env.unifier).unwrap(),
fields: Mapping::default(),
params: into_var_map([TypeVar { id: list_elem_tvar.id, ty: b }]),
});
let b_list = env.unifier.get_fresh_var_with_range(&[b_list], None, None).ty;
env.unifier.unify(a_list, b_list).unwrap(); env.unifier.unify(a_list, b_list).unwrap();
let float_list = env.unifier.add_ty(TypeEnum::TObj { let float_list = env.unifier.add_ty(TypeEnum::TList { ty: float });
obj_id: env.type_mapping["list"].obj_id(&env.unifier).unwrap(),
fields: Mapping::default(),
params: into_var_map([TypeVar { id: list_elem_tvar.id, ty: float }]),
});
env.unifier.unify(a_list, float_list).unwrap(); env.unifier.unify(a_list, float_list).unwrap();
// previous unifications should not affect a and b // previous unifications should not affect a and b
env.unifier.unify(a, int).unwrap(); env.unifier.unify(a, int).unwrap();
let a = env.unifier.get_fresh_var_with_range(&[int, float], None, None).ty; let a = env.unifier.get_fresh_var_with_range(&[int, float], None, None).0;
let b = env.unifier.get_fresh_var_with_range(&[boolean, float], None, None).ty; let b = env.unifier.get_fresh_var_with_range(&[boolean, float], None, None).0;
let a_list = env.unifier.add_ty(TypeEnum::TObj { let a_list = env.unifier.add_ty(TypeEnum::TList { ty: a });
obj_id: env.type_mapping["list"].obj_id(&env.unifier).unwrap(), let b_list = env.unifier.add_ty(TypeEnum::TList { ty: b });
fields: Mapping::default(),
params: into_var_map([TypeVar { id: list_elem_tvar.id, ty: a }]),
});
let b_list = env.unifier.add_ty(TypeEnum::TObj {
obj_id: env.type_mapping["list"].obj_id(&env.unifier).unwrap(),
fields: Mapping::default(),
params: into_var_map([TypeVar { id: list_elem_tvar.id, ty: b }]),
});
env.unifier.unify(a_list, b_list).unwrap(); env.unifier.unify(a_list, b_list).unwrap();
let int_list = env.unifier.add_ty(TypeEnum::TObj { let int_list = env.unifier.add_ty(TypeEnum::TList { ty: int });
obj_id: env.type_mapping["list"].obj_id(&env.unifier).unwrap(),
fields: Mapping::default(),
params: into_var_map([TypeVar { id: list_elem_tvar.id, ty: int }]),
});
assert_eq!( assert_eq!(
env.unify(a_list, int_list), env.unify(a_list, int_list),
Err("Incompatible types: 11[typevar23] and 11[0]\ Err("Incompatible types: list[typevar22] and list[0]\
\n\nNotes:\n typevar23 {1}" \n\nNotes:\n typevar22 {1}".into())
.into())
); );
let a = env.unifier.get_fresh_var_with_range(&[int, float], None, None).ty; let a = env.unifier.get_fresh_var_with_range(&[int, float], None, None).0;
let b = env.unifier.get_dummy_var().ty; let b = env.unifier.get_dummy_var().0;
let a_list = env.unifier.add_ty(TypeEnum::TObj { let a_list = env.unifier.add_ty(TypeEnum::TList { ty: a });
obj_id: env.type_mapping["list"].obj_id(&env.unifier).unwrap(), let a_list = env.unifier.get_fresh_var_with_range(&[a_list], None, None).0;
fields: Mapping::default(), let b_list = env.unifier.add_ty(TypeEnum::TList { ty: b });
params: into_var_map([TypeVar { id: list_elem_tvar.id, ty: a }]),
});
let a_list = env.unifier.get_fresh_var_with_range(&[a_list], None, None).ty;
let b_list = env.unifier.add_ty(TypeEnum::TObj {
obj_id: env.type_mapping["list"].obj_id(&env.unifier).unwrap(),
fields: Mapping::default(),
params: into_var_map([TypeVar { id: list_elem_tvar.id, ty: b }]),
});
env.unifier.unify(a_list, b_list).unwrap(); env.unifier.unify(a_list, b_list).unwrap();
assert_eq!( assert_eq!(
env.unify(b, boolean), env.unify(b, boolean),
@ -554,29 +497,17 @@ fn test_typevar_range() {
#[test] #[test]
fn test_rigid_var() { fn test_rigid_var() {
let mut env = TestEnvironment::new(); let mut env = TestEnvironment::new();
let a = env.unifier.get_fresh_rigid_var(None, None).ty; let a = env.unifier.get_fresh_rigid_var(None, None).0;
let b = env.unifier.get_fresh_rigid_var(None, None).ty; let b = env.unifier.get_fresh_rigid_var(None, None).0;
let x = env.unifier.get_dummy_var().ty; let x = env.unifier.get_dummy_var().0;
let list_elem_tvar = env.unifier.get_fresh_var(Some("list_elem".into()), None); let list_a = env.unifier.add_ty(TypeEnum::TList { ty: a });
let list_a = env.unifier.add_ty(TypeEnum::TObj { let list_x = env.unifier.add_ty(TypeEnum::TList { ty: x });
obj_id: env.type_mapping["list"].obj_id(&env.unifier).unwrap(),
fields: Mapping::default(),
params: into_var_map([TypeVar { id: list_elem_tvar.id, ty: a }]),
});
let list_x = env.unifier.add_ty(TypeEnum::TObj {
obj_id: env.type_mapping["list"].obj_id(&env.unifier).unwrap(),
fields: Mapping::default(),
params: into_var_map([TypeVar { id: list_elem_tvar.id, ty: x }]),
});
let int = env.parse("int", &HashMap::new()); let int = env.parse("int", &HashMap::new());
let list_int = env.parse("list[int]", &HashMap::new()); let list_int = env.parse("list[int]", &HashMap::new());
assert_eq!(env.unify(a, b), Err("Incompatible types: typevar4 and typevar3".to_string())); assert_eq!(env.unify(a, b), Err("Incompatible types: typevar3 and typevar2".to_string()));
env.unifier.unify(list_a, list_x).unwrap(); env.unifier.unify(list_a, list_x).unwrap();
assert_eq!( assert_eq!(env.unify(list_x, list_int), Err("Incompatible types: list[typevar2] and list[0]".to_string()));
env.unify(list_x, list_int),
Err("Incompatible types: 11[typevar3] and 11[0]".to_string())
);
env.unifier.replace_rigid_var(a, int); env.unifier.replace_rigid_var(a, int);
env.unifier.unify(list_x, list_int).unwrap(); env.unifier.unify(list_x, list_int).unwrap();
@ -590,26 +521,16 @@ fn test_instantiation() {
let float = env.parse("float", &HashMap::new()); let float = env.parse("float", &HashMap::new());
let list_int = env.parse("list[int]", &HashMap::new()); let list_int = env.parse("list[int]", &HashMap::new());
let list_elem_tvar = if let TypeEnum::TObj { params, .. } = let obj_map: HashMap<_, _> =
&*env.unifier.get_ty_immutable(env.type_mapping["list"]) [(0usize, "int"), (1, "float"), (2, "bool")].iter().cloned().collect();
{
iter_type_vars(params).next().unwrap()
} else {
unreachable!()
};
let obj_map: HashMap<_, _> = [(0usize, "int"), (1, "float"), (2, "bool"), (11, "list")].into(); let v = env.unifier.get_fresh_var_with_range(&[int, boolean], None, None).0;
let list_v = env.unifier.add_ty(TypeEnum::TList { ty: v });
let v = env.unifier.get_fresh_var_with_range(&[int, boolean], None, None).ty; let v1 = env.unifier.get_fresh_var_with_range(&[list_v, int], None, None).0;
let list_v = env let v2 = env.unifier.get_fresh_var_with_range(&[list_int, float], None, None).0;
.unifier let t = env.unifier.get_dummy_var().0;
.subst(env.type_mapping["list"], &into_var_map([TypeVar { id: list_elem_tvar.id, ty: v }]))
.unwrap();
let v1 = env.unifier.get_fresh_var_with_range(&[list_v, int], None, None).ty;
let v2 = env.unifier.get_fresh_var_with_range(&[list_int, float], None, None).ty;
let t = env.unifier.get_dummy_var().ty;
let tuple = env.unifier.add_ty(TypeEnum::TTuple { ty: vec![v, v1, v2] }); let tuple = env.unifier.add_ty(TypeEnum::TTuple { ty: vec![v, v1, v2] });
let v3 = env.unifier.get_fresh_var_with_range(&[tuple, t], None, None).ty; let v3 = env.unifier.get_fresh_var_with_range(&[tuple, t], None, None).0;
// t = TypeVar('t') // t = TypeVar('t')
// v = TypeVar('v', int, bool) // v = TypeVar('v', int, bool)
// v1 = TypeVar('v1', 'list[v]', int) // v1 = TypeVar('v1', 'list[v]', int)
@ -631,7 +552,7 @@ fn test_instantiation() {
tuple[int, list[bool], list[int]] tuple[int, list[bool], list[int]]
tuple[int, list[int], float] tuple[int, list[int], float]
tuple[int, list[int], list[int]] tuple[int, list[int], list[int]]
v6" v5"
} }
.split('\n') .split('\n')
.collect_vec(); .collect_vec();
@ -640,8 +561,8 @@ fn test_instantiation() {
.map(|ty| { .map(|ty| {
env.unifier.internal_stringify( env.unifier.internal_stringify(
*ty, *ty,
&mut |i| (*obj_map.get(&i).unwrap()).to_string(), &mut |i| obj_map.get(&i).unwrap().to_string(),
&mut |i| format!("v{i}"), &mut |i| format!("v{}", i),
&mut None, &mut None,
) )
}) })

View File

@ -16,10 +16,21 @@ pub struct UnificationTable<V> {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
enum Action<V> { enum Action<V> {
Parent { key: usize, original_parent: usize }, Parent {
Value { key: usize, original_value: Option<V> }, key: usize,
Rank { key: usize, original_rank: u32 }, original_parent: usize,
Marker { generation: u32 }, },
Value {
key: usize,
original_value: Option<V>,
},
Rank {
key: usize,
original_rank: u32,
},
Marker {
generation: u32,
}
} }
impl<V> Default for UnificationTable<V> { impl<V> Default for UnificationTable<V> {
@ -30,13 +41,7 @@ impl<V> Default for UnificationTable<V> {
impl<V> UnificationTable<V> { impl<V> UnificationTable<V> {
pub fn new() -> UnificationTable<V> { pub fn new() -> UnificationTable<V> {
UnificationTable { UnificationTable { parents: Vec::new(), ranks: Vec::new(), values: Vec::new(), log: Vec::new(), generation: 0 }
parents: Vec::new(),
ranks: Vec::new(),
values: Vec::new(),
log: Vec::new(),
generation: 0,
}
} }
pub fn new_key(&mut self, v: V) -> UnificationKey { pub fn new_key(&mut self, v: V) -> UnificationKey {
@ -120,10 +125,7 @@ impl<V> UnificationTable<V> {
pub fn restore_snapshot(&mut self, snapshot: (usize, u32)) { pub fn restore_snapshot(&mut self, snapshot: (usize, u32)) {
let (log_len, generation) = snapshot; let (log_len, generation) = snapshot;
assert!(self.log.len() >= log_len, "snapshot restoration error"); assert!(self.log.len() >= log_len, "snapshot restoration error");
assert!( assert!(matches!(self.log[log_len - 1], Action::Marker { generation: gen } if gen == generation), "snapshot restoration error");
matches!(self.log[log_len - 1], Action::Marker { generation: gen } if gen == generation),
"snapshot restoration error"
);
for action in self.log.drain(log_len - 1..).rev() { for action in self.log.drain(log_len - 1..).rev() {
match action { match action {
Action::Parent { key, original_parent } => { Action::Parent { key, original_parent } => {
@ -143,10 +145,7 @@ impl<V> UnificationTable<V> {
pub fn discard_snapshot(&mut self, snapshot: (usize, u32)) { pub fn discard_snapshot(&mut self, snapshot: (usize, u32)) {
let (log_len, generation) = snapshot; let (log_len, generation) = snapshot;
assert!(self.log.len() >= log_len, "snapshot discard error"); assert!(self.log.len() >= log_len, "snapshot discard error");
assert!( assert!(matches!(self.log[log_len - 1], Action::Marker { generation: gen } if gen == generation), "snapshot discard error");
matches!(self.log[log_len - 1], Action::Marker { generation: gen } if gen == generation),
"snapshot discard error"
);
self.log.clear(); self.log.clear();
} }
} }
@ -160,23 +159,11 @@ where
.enumerate() .enumerate()
.map(|(i, (v, p))| if *p == i { v.as_ref().map(|v| v.as_ref().clone()) } else { None }) .map(|(i, (v, p))| if *p == i { v.as_ref().map(|v| v.as_ref().clone()) } else { None })
.collect(); .collect();
UnificationTable { UnificationTable { parents: self.parents.clone(), ranks: self.ranks.clone(), values, log: Vec::new(), generation: 0 }
parents: self.parents.clone(),
ranks: self.ranks.clone(),
values,
log: Vec::new(),
generation: 0,
}
} }
pub fn from_send(table: &UnificationTable<V>) -> UnificationTable<Rc<V>> { pub fn from_send(table: &UnificationTable<V>) -> UnificationTable<Rc<V>> {
let values = table.values.iter().cloned().map(|v| v.map(Rc::new)).collect(); let values = table.values.iter().cloned().map(|v| v.map(Rc::new)).collect();
UnificationTable { UnificationTable { parents: table.parents.clone(), ranks: table.ranks.clone(), values, log: Vec::new(), generation: 0 }
parents: table.parents.clone(),
ranks: table.ranks.clone(),
values,
log: Vec::new(),
generation: 0,
}
} }
} }

View File

@ -2,7 +2,7 @@
name = "nac3ld" name = "nac3ld"
version = "0.1.0" version = "0.1.0"
authors = ["M-Labs"] authors = ["M-Labs"]
edition = "2021" edition = "2018"
[dependencies] [dependencies]
byteorder = { version = "1.5", default-features = false } byteorder = { version = "1.4", default-features = false }

View File

@ -27,39 +27,27 @@ pub const DW_EH_PE_indirect: u8 = 0x80;
pub struct DwarfReader<'a> { pub struct DwarfReader<'a> {
pub slice: &'a [u8], pub slice: &'a [u8],
pub virt_addr: u32, pub virt_addr: u32,
base_slice: &'a [u8],
base_virt_addr: u32,
} }
impl<'a> DwarfReader<'a> { impl<'a> DwarfReader<'a> {
pub fn new(slice: &[u8], virt_addr: u32) -> DwarfReader { pub fn new(slice: &[u8], virt_addr: u32) -> DwarfReader {
DwarfReader { slice, virt_addr, base_slice: slice, base_virt_addr: virt_addr } DwarfReader { slice, virt_addr }
} }
/// Creates a new instance from another instance of [DwarfReader], optionally removing any pub fn offset(&mut self, offset: i32) {
/// offsets previously applied to the other instance.
pub fn from_reader(other: &DwarfReader<'a>, reset_offset: bool) -> DwarfReader<'a> {
if reset_offset {
DwarfReader::new(other.base_slice, other.base_virt_addr)
} else {
DwarfReader::new(other.slice, other.virt_addr)
}
}
pub fn offset(&mut self, offset: u32) {
self.slice = &self.slice[offset as usize..]; self.slice = &self.slice[offset as usize..];
self.virt_addr = self.virt_addr.wrapping_add(offset); self.virt_addr = self.virt_addr.wrapping_add(offset as u32);
} }
/// ULEB128 and SLEB128 encodings are defined in Section 7.6 - "Variable Length Data" of the // ULEB128 and SLEB128 encodings are defined in Section 7.6 - "Variable
/// [DWARF-4 Manual](https://dwarfstd.org/doc/DWARF4.pdf). // Length Data".
pub fn read_uleb128(&mut self) -> u64 { pub fn read_uleb128(&mut self) -> u64 {
let mut shift: usize = 0; let mut shift: usize = 0;
let mut result: u64 = 0; let mut result: u64 = 0;
let mut byte: u8; let mut byte: u8;
loop { loop {
byte = self.read_u8(); byte = self.read_u8();
result |= u64::from(byte & 0x7F) << shift; result |= ((byte & 0x7F) as u64) << shift;
shift += 7; shift += 7;
if byte & 0x80 == 0 { if byte & 0x80 == 0 {
break; break;
@ -74,7 +62,7 @@ impl<'a> DwarfReader<'a> {
let mut byte: u8; let mut byte: u8;
loop { loop {
byte = self.read_u8(); byte = self.read_u8();
result |= u64::from(byte & 0x7F) << shift; result |= ((byte & 0x7F) as u64) << shift;
shift += 7; shift += 7;
if byte & 0x80 == 0 { if byte & 0x80 == 0 {
break; break;
@ -82,7 +70,7 @@ impl<'a> DwarfReader<'a> {
} }
// sign-extend // sign-extend
if shift < u64::BITS && (byte & 0x40) != 0 { if shift < u64::BITS && (byte & 0x40) != 0 {
result |= (!0u64) << shift; result |= (!0 as u64) << shift;
} }
result as i64 result as i64
} }
@ -156,9 +144,10 @@ fn read_encoded_pointer(reader: &mut DwarfReader, encoding: u8) -> Result<usize,
} }
match encoding & 0x0F { match encoding & 0x0F {
DW_EH_PE_absptr | DW_EH_PE_udata4 => Ok(reader.read_u32() as usize), DW_EH_PE_absptr => Ok(reader.read_u32() as usize),
DW_EH_PE_uleb128 => Ok(reader.read_uleb128() as usize), DW_EH_PE_uleb128 => Ok(reader.read_uleb128() as usize),
DW_EH_PE_udata2 => Ok(reader.read_u16() as usize), DW_EH_PE_udata2 => Ok(reader.read_u16() as usize),
DW_EH_PE_udata4 => Ok(reader.read_u32() as usize),
DW_EH_PE_udata8 => Ok(reader.read_u64() as usize), DW_EH_PE_udata8 => Ok(reader.read_u64() as usize),
DW_EH_PE_sleb128 => Ok(reader.read_sleb128() as usize), DW_EH_PE_sleb128 => Ok(reader.read_sleb128() as usize),
DW_EH_PE_sdata2 => Ok(reader.read_i16() as usize), DW_EH_PE_sdata2 => Ok(reader.read_i16() as usize),
@ -168,7 +157,10 @@ fn read_encoded_pointer(reader: &mut DwarfReader, encoding: u8) -> Result<usize,
} }
} }
fn read_encoded_pointer_with_pc(reader: &mut DwarfReader, encoding: u8) -> Result<usize, ()> { fn read_encoded_pointer_with_pc(
reader: &mut DwarfReader,
encoding: u8,
) -> Result<usize, ()> {
let entry_virt_addr = reader.virt_addr; let entry_virt_addr = reader.virt_addr;
let mut result = read_encoded_pointer(reader, encoding)?; let mut result = read_encoded_pointer(reader, encoding)?;
@ -208,64 +200,38 @@ fn round_up(unrounded: usize, align: usize) -> Result<usize, ()> {
} }
} }
/// Minimalistic structure to store everything needed for parsing FDEs to synthesize `.eh_frame_hdr` // Minimalistic structure to store everything needed for parsing FDEs to synthesize
/// section. // .eh_frame_hdr section. Since we are only linking 1 object file, there should only be 1 call
/// // frame information (CFI) record, so there should be only 1 common information entry (CIE).
/// Refer to [The Linux Standard Base Core Specification, Generic Part](https://refspecs.linuxfoundation.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/ehframechpt.html) // So the class parses the only CIE on init, cache the encoding info, then parse the FDE on
/// for more information. // iterations based on the cached encoding format.
pub struct EH_Frame<'a> { pub struct EH_Frame<'a> {
reader: DwarfReader<'a>, // It refers to the augmentation data that corresponds to 'R' in the augmentation string
pub fde_pointer_encoding: u8,
pub fde_reader: DwarfReader<'a>,
pub fde_sz: usize,
} }
impl<'a> EH_Frame<'a> { impl<'a> EH_Frame<'a> {
/// Creates an [EH_Frame] using the bytes in the `.eh_frame` section and its address in the ELF pub fn new(eh_frame_slice: &[u8], eh_frame_addr: u32) -> Result<EH_Frame, ()> {
/// file. let mut cie_reader = DwarfReader::new(eh_frame_slice, eh_frame_addr);
pub fn new(eh_frame_slice: &[u8], eh_frame_addr: u32) -> EH_Frame { let eh_frame_size = eh_frame_slice.len();
EH_Frame { reader: DwarfReader::new(eh_frame_slice, eh_frame_addr) }
}
/// Returns an [Iterator] over all Call Frame Information (CFI) records.
pub fn cfi_records(&self) -> CFI_Records<'a> {
let reader = DwarfReader::from_reader(&self.reader, true);
let len = reader.slice.len();
CFI_Records { reader, available: len }
}
}
/// A single Call Frame Information (CFI) record.
///
/// From the [specification](https://refspecs.linuxfoundation.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/ehframechpt.html):
///
/// > Each CFI record contains a Common Information Entry (CIE) record followed by 1 or more Frame
/// Description Entry (FDE) records.
pub struct CFI_Record<'a> {
// It refers to the augmentation data that corresponds to 'R' in the augmentation string
fde_pointer_encoding: u8,
fde_reader: DwarfReader<'a>,
}
impl<'a> CFI_Record<'a> {
pub fn from_reader(cie_reader: &mut DwarfReader<'a>) -> Result<CFI_Record<'a>, ()> {
let length = cie_reader.read_u32(); let length = cie_reader.read_u32();
let fde_reader = match length { let fde_reader = match length {
// eh_frame with 0 lengths means the CIE is terminated // eh_frame with 0 lengths means the CIE is terminated
0 => panic!("Cannot create an EH_Frame from a termination CIE"), // while length == u32::MAX means that the length is only representable with 64 bits,
// length == u32::MAX means that the length is only representable with 64 bits,
// which does not make sense in a system with 32-bit address. // which does not make sense in a system with 32-bit address.
0xFFFF_FFFF => unimplemented!(), 0 | 0xFFFFFFFF => unimplemented!(),
_ => { _ => {
let mut fde_reader = DwarfReader::from_reader(cie_reader, false); let mut fde_reader = DwarfReader::new(cie_reader.slice, cie_reader.virt_addr);
fde_reader.offset(length); fde_reader.offset(length as i32);
fde_reader fde_reader
} }
}; };
let fde_sz = eh_frame_size - mem::size_of::<u32>() - length as usize;
// Routine check on the .eh_frame well-formness, in terms of CIE ID & Version args. // Routine check on the .eh_frame well-formness, in terms of CIE ID & Version args.
let cie_ptr = cie_reader.read_u32(); assert_eq!(cie_reader.read_u32(), 0);
assert_eq!(cie_ptr, 0);
assert_eq!(cie_reader.read_u8(), 1); assert_eq!(cie_reader.read_u8(), 1);
// Parse augmentation string // Parse augmentation string
@ -276,7 +242,7 @@ impl<'a> CFI_Record<'a> {
// Skip code/data alignment factors & return address register along the way as well // Skip code/data alignment factors & return address register along the way as well
// We only tackle the case where 'z' and 'R' are part of the augmentation string, otherwise // We only tackle the case where 'z' and 'R' are part of the augmentation string, otherwise
// we cannot get the addresses to make .eh_frame_hdr // we cannot get the addresses to make .eh_frame_hdr
let mut aug_data_reader = DwarfReader::from_reader(cie_reader, false); let mut aug_data_reader = DwarfReader::new(cie_reader.slice, cie_reader.virt_addr);
let mut aug_str_len = 0; let mut aug_str_len = 0;
loop { loop {
if aug_data_reader.read_u8() == b'\0' { if aug_data_reader.read_u8() == b'\0' {
@ -313,114 +279,43 @@ impl<'a> CFI_Record<'a> {
} }
assert_ne!(fde_pointer_encoding, DW_EH_PE_omit); assert_ne!(fde_pointer_encoding, DW_EH_PE_omit);
Ok(CFI_Record { fde_pointer_encoding, fde_reader }) Ok(EH_Frame { fde_pointer_encoding, fde_reader, fde_sz })
} }
/// Returns a [DwarfReader] initialized to the first Frame Description Entry (FDE) of this CFI pub fn iterate_fde(&self, callback: &mut dyn FnMut(u32, u32)) -> Result<(), ()> {
/// record.
pub fn get_fde_reader(&self) -> DwarfReader<'a> {
DwarfReader::from_reader(&self.fde_reader, true)
}
/// Returns an [Iterator] over all Frame Description Entries (FDEs).
pub fn fde_records(&self) -> FDE_Records<'a> {
let reader = self.get_fde_reader();
let len = reader.slice.len();
FDE_Records { pointer_encoding: self.fde_pointer_encoding, reader, available: len }
}
}
/// [Iterator] over Call Frame Information (CFI) records in an
/// [Exception Handling (EH) frame][EH_Frame].
pub struct CFI_Records<'a> {
reader: DwarfReader<'a>,
available: usize,
}
impl<'a> Iterator for CFI_Records<'a> {
type Item = CFI_Record<'a>;
fn next(&mut self) -> Option<Self::Item> {
loop {
if self.available == 0 {
return None;
}
let mut this_reader = DwarfReader::from_reader(&self.reader, false);
// Remove the length of the header and the content from the counter
let length = self.reader.read_u32();
let length = match length {
// eh_frame with 0-length means the CIE is terminated
0 => return None,
0xFFFF_FFFF => unimplemented!("CIE entries larger than 4 bytes not supported"),
other => other,
} as usize;
// Remove the length of the header and the content from the counter
self.available -= length + mem::size_of::<u32>();
let mut next_reader = DwarfReader::from_reader(&self.reader, false);
next_reader.offset(length as u32);
let cie_ptr = self.reader.read_u32();
self.reader = next_reader;
// Skip this record if it is a FDE
if cie_ptr == 0 {
// Rewind back to the start of the CFI Record
return Some(CFI_Record::from_reader(&mut this_reader).ok().unwrap());
}
}
}
}
/// [Iterator] over Frame Description Entries (FDEs) in an
/// [Exception Handling (EH) frame][EH_Frame].
pub struct FDE_Records<'a> {
pointer_encoding: u8,
reader: DwarfReader<'a>,
available: usize,
}
impl<'a> Iterator for FDE_Records<'a> {
type Item = (u32, u32);
fn next(&mut self) -> Option<Self::Item> {
// Parse each FDE to obtain the starting address that the FDE applies to // Parse each FDE to obtain the starting address that the FDE applies to
// Send the FDE offset and the mentioned address to a callback that write up the // Send the FDE offset and the mentioned address to a callback that write up the
// .eh_frame_hdr section // .eh_frame_hdr section
let mut remaining_len = self.fde_sz;
let mut reader = DwarfReader::new(self.fde_reader.slice, self.fde_reader.virt_addr);
loop {
if remaining_len == 0 {
break;
}
if self.available == 0 { let fde_virt_addr = reader.virt_addr;
return None; let length = match reader.read_u32() {
0 | 0xFFFFFFFF => unimplemented!(),
other => other,
};
// Remove the length of the header and the content from the counter
remaining_len -= length as usize + mem::size_of::<u32>();
let mut next_fde_reader = DwarfReader::new(reader.slice, reader.virt_addr);
next_fde_reader.offset(length as i32);
// Skip CIE pointer offset
reader.read_u32();
// Parse PC Begin using the encoding scheme mentioned in the CIE
let pc_begin = read_encoded_pointer_with_pc(&mut reader, self.fde_pointer_encoding)?;
callback(pc_begin as u32, fde_virt_addr);
reader = next_fde_reader;
} }
// Remove the length of the header and the content from the counter Ok(())
let length = match self.reader.read_u32() {
// eh_frame with 0-length means the CIE is terminated
0 => return None,
0xFFFF_FFFF => unimplemented!("CIE entries larger than 4 bytes not supported"),
other => other,
} as usize;
// Remove the length of the header and the content from the counter
self.available -= length + mem::size_of::<u32>();
let mut next_fde_reader = DwarfReader::from_reader(&self.reader, false);
next_fde_reader.offset(length as u32);
let cie_ptr = self.reader.read_u32();
let next_val = if cie_ptr != 0 {
let pc_begin = read_encoded_pointer_with_pc(&mut self.reader, self.pointer_encoding)
.expect("Failed to read PC Begin");
Some((pc_begin as u32, self.reader.virt_addr))
} else {
None
};
self.reader = next_fde_reader;
next_val
} }
} }
@ -431,32 +326,29 @@ pub struct EH_Frame_Hdr<'a> {
} }
impl<'a> EH_Frame_Hdr<'a> { impl<'a> EH_Frame_Hdr<'a> {
/// Create a [EH_Frame_Hdr] object, and write out the fixed fields of `.eh_frame_hdr` to memory. // Create a EH_Frame_Hdr object, and write out the fixed fields of .eh_frame_hdr to memory
/// // eh_frame_ptr_enc will be 0x1B (PC-relative, 4 bytes)
/// Load address is not known at this point. // table_enc will be 0x3B (Relative to the start of .eh_frame_hdr, 4 bytes)
// Load address is not known at this point.
pub fn new( pub fn new(
eh_frame_hdr_slice: &mut [u8], eh_frame_hdr_slice: &mut [u8],
eh_frame_hdr_addr: u32, eh_frame_hdr_addr: u32,
eh_frame_addr: u32, eh_frame_addr: u32,
) -> EH_Frame_Hdr { ) -> EH_Frame_Hdr {
let mut writer = DwarfWriter::new(eh_frame_hdr_slice); let mut writer = DwarfWriter::new(eh_frame_hdr_slice);
writer.write_u8(1);
writer.write_u8(0x1B);
writer.write_u8(0x03);
writer.write_u8(0x3B);
writer.write_u8(1); // version let eh_frame_offset =
writer.write_u8(0x1B); // eh_frame_ptr_enc - PC-relative 4-byte signed value (eh_frame_addr).wrapping_sub(eh_frame_hdr_addr + ((mem::size_of::<u8>() as u32) * 4));
writer.write_u8(0x03); // fde_count_enc - 4-byte unsigned value writer.write_u32(eh_frame_offset);
writer.write_u8(0x3B); // table_enc - .eh_frame_hdr section-relative 4-byte signed value writer.write_u32(0);
let eh_frame_offset = eh_frame_addr.wrapping_sub(
eh_frame_hdr_addr + writer.offset as u32 + ((mem::size_of::<u8>() as u32) * 4),
);
writer.write_u32(eh_frame_offset); // eh_frame_ptr
writer.write_u32(0); // `fde_count`, will be written in finalize_fde
EH_Frame_Hdr { fde_writer: writer, eh_frame_hdr_addr, fdes: Vec::new() } EH_Frame_Hdr { fde_writer: writer, eh_frame_hdr_addr, fdes: Vec::new() }
} }
/// The offset of the `fde_count` value relative to the start of the `.eh_frame_hdr` section in
/// bytes.
fn fde_count_offset() -> usize { fn fde_count_offset() -> usize {
8 8
} }
@ -475,10 +367,7 @@ impl<'a> EH_Frame_Hdr<'a> {
self.fde_writer.write_u32(*init_loc); self.fde_writer.write_u32(*init_loc);
self.fde_writer.write_u32(*addr); self.fde_writer.write_u32(*addr);
} }
LittleEndian::write_u32( LittleEndian::write_u32(&mut self.fde_writer.slice[Self::fde_count_offset()..], self.fdes.len() as u32);
&mut self.fde_writer.slice[Self::fde_count_offset()..],
self.fdes.len() as u32,
);
} }
pub fn size_from_eh_frame(eh_frame: &[u8]) -> usize { pub fn size_from_eh_frame(eh_frame: &[u8]) -> usize {
@ -490,20 +379,14 @@ impl<'a> EH_Frame_Hdr<'a> {
// The original length field should be able to hold the entire value. // The original length field should be able to hold the entire value.
// The device memory space is limited to 32-bits addresses anyway. // The device memory space is limited to 32-bits addresses anyway.
let entry_length = reader.read_u32(); let entry_length = reader.read_u32();
if entry_length == 0 || entry_length == 0xFFFF_FFFF { if entry_length == 0 || entry_length == 0xFFFFFFFF {
unimplemented!() unimplemented!()
} }
if reader.read_u32() != 0 {
// This slot stores the CIE ID (for CIE)/CIE Pointer (for FDE).
// This value must be non-zero for FDEs.
let cie_ptr = reader.read_u32();
if cie_ptr != 0 {
fde_count += 1; fde_count += 1;
} }
reader.offset(entry_length as i32 - mem::size_of::<u32>() as i32)
reader.offset(entry_length - mem::size_of::<u32>() as u32);
} }
12 + fde_count * 8 12 + fde_count * 8
} }
} }

View File

@ -1,5 +1,5 @@
/* generated from elf.h with rust-bindgen and then manually altered */ /* generated from elf.h with rust-bindgen and then manually altered */
#![allow(non_camel_case_types, non_snake_case, non_upper_case_globals, dead_code, clippy::pedantic)] #![allow(non_camel_case_types, non_snake_case, non_upper_case_globals, dead_code)]
pub const EI_NIDENT: usize = 16; pub const EI_NIDENT: usize = 16;
pub const EI_MAG0: usize = 0; pub const EI_MAG0: usize = 0;
@ -10,7 +10,7 @@ pub const EI_MAG2: usize = 2;
pub const ELFMAG2: u8 = b'L'; pub const ELFMAG2: u8 = b'L';
pub const EI_MAG3: usize = 3; pub const EI_MAG3: usize = 3;
pub const ELFMAG3: u8 = b'F'; pub const ELFMAG3: u8 = b'F';
pub const ELFMAG: &[u8; 5usize] = b"\x7fELF\x00"; pub const ELFMAG: &'static [u8; 5usize] = b"\x7fELF\x00";
pub const SELFMAG: usize = 4; pub const SELFMAG: usize = 4;
pub const EI_CLASS: usize = 4; pub const EI_CLASS: usize = 4;
pub const ELFCLASSNONE: u8 = 0; pub const ELFCLASSNONE: u8 = 0;
@ -428,8 +428,8 @@ pub const VER_NDX_ELIMINATE: usize = 65281;
pub const VER_NEED_NONE: usize = 0; pub const VER_NEED_NONE: usize = 0;
pub const VER_NEED_CURRENT: usize = 1; pub const VER_NEED_CURRENT: usize = 1;
pub const VER_NEED_NUM: usize = 2; pub const VER_NEED_NUM: usize = 2;
pub const ELF_NOTE_SOLARIS: &[u8; 13usize] = b"SUNW Solaris\x00"; pub const ELF_NOTE_SOLARIS: &'static [u8; 13usize] = b"SUNW Solaris\x00";
pub const ELF_NOTE_GNU: &[u8; 4usize] = b"GNU\x00"; pub const ELF_NOTE_GNU: &'static [u8; 4usize] = b"GNU\x00";
pub const ELF_NOTE_PAGESIZE_HINT: usize = 1; pub const ELF_NOTE_PAGESIZE_HINT: usize = 1;
pub const NT_GNU_ABI_TAG: usize = 1; pub const NT_GNU_ABI_TAG: usize = 1;
pub const ELF_NOTE_ABI: usize = 1; pub const ELF_NOTE_ABI: usize = 1;

View File

@ -1,30 +1,7 @@
#![deny(
future_incompatible,
let_underscore,
nonstandard_style,
rust_2024_compatibility,
clippy::all
)]
#![warn(clippy::pedantic)]
#![allow(
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_sign_loss,
clippy::doc_markdown,
clippy::enum_glob_use,
clippy::missing_errors_doc,
clippy::missing_panics_doc,
clippy::module_name_repetitions,
clippy::similar_names,
clippy::struct_field_names,
clippy::too_many_lines,
clippy::wildcard_imports
)]
use dwarf::*; use dwarf::*;
use elf::*; use elf::*;
use std::collections::HashMap; use std::collections::HashMap;
use std::{mem, ptr, slice, str}; use std::{convert, mem, ptr, slice, str};
extern crate byteorder; extern crate byteorder;
use byteorder::{ByteOrder, LittleEndian}; use byteorder::{ByteOrder, LittleEndian};
@ -44,7 +21,7 @@ pub enum Error {
Lookup(&'static str), Lookup(&'static str),
} }
impl From<&'static str> for Error { impl convert::From<&'static str> for Error {
fn from(desc: &'static str) -> Error { fn from(desc: &'static str) -> Error {
Error::Parsing(desc) Error::Parsing(desc)
} }
@ -93,45 +70,45 @@ struct SectionRecord<'a> {
data: Vec<u8>, data: Vec<u8>,
} }
fn read_unaligned<T: Copy>(data: &[u8], offset: usize) -> Option<T> { fn read_unaligned<T: Copy>(data: &[u8], offset: usize) -> Result<T, ()> {
if data.len() < offset + mem::size_of::<T>() { if data.len() < offset + mem::size_of::<T>() {
None Err(())
} else { } else {
let ptr = data.as_ptr().wrapping_add(offset).cast(); let ptr = data.as_ptr().wrapping_offset(offset as isize) as *const T;
Some(unsafe { ptr::read_unaligned(ptr) }) Ok(unsafe { ptr::read_unaligned(ptr) })
} }
} }
#[must_use] pub fn get_ref_slice<T: Copy>(data: &[u8], offset: usize, len: usize) -> Result<&[T], ()> {
pub fn get_ref_slice<T: Copy>(data: &[u8], offset: usize, len: usize) -> Option<&[T]> {
if data.len() < offset + mem::size_of::<T>() * len { if data.len() < offset + mem::size_of::<T>() * len {
None Err(())
} else { } else {
let ptr = data.as_ptr().wrapping_add(offset).cast(); let ptr = data.as_ptr().wrapping_offset(offset as isize) as *const T;
Some(unsafe { slice::from_raw_parts(ptr, len) }) Ok(unsafe { slice::from_raw_parts(ptr, len) })
} }
} }
fn from_struct_slice<T>(struct_vec: &[T]) -> Vec<u8> { fn from_struct_vec<T>(struct_vec: Vec<T>) -> Vec<u8> {
let ptr = struct_vec.as_ptr(); let ptr = struct_vec.as_ptr();
unsafe { slice::from_raw_parts(ptr.cast(), mem::size_of_val(struct_vec)) }.to_vec() unsafe { slice::from_raw_parts(ptr as *const u8, struct_vec.len() * mem::size_of::<T>()) }
.to_vec()
} }
fn to_struct_slice<T>(bytes: &[u8]) -> &[T] { fn to_struct_slice<T>(bytes: &[u8]) -> &[T] {
unsafe { slice::from_raw_parts(bytes.as_ptr().cast(), bytes.len() / mem::size_of::<T>()) } unsafe { slice::from_raw_parts(bytes.as_ptr() as *const T, bytes.len() / mem::size_of::<T>()) }
} }
fn to_struct_mut_slice<T>(bytes: &mut [u8]) -> &mut [T] { fn to_struct_mut_slice<T>(bytes: &mut [u8]) -> &mut [T] {
unsafe { unsafe {
slice::from_raw_parts_mut(bytes.as_mut_ptr().cast(), bytes.len() / mem::size_of::<T>()) slice::from_raw_parts_mut(bytes.as_mut_ptr() as *mut T, bytes.len() / mem::size_of::<T>())
} }
} }
fn elf_hash(name: &[u8]) -> u32 { fn elf_hash(name: &[u8]) -> u32 {
let mut h: u32 = 0; let mut h: u32 = 0;
for c in name { for c in name {
h = (h << 4) + u32::from(*c); h = (h << 4) + *c as u32;
let g = h & 0xf000_0000; let g = h & 0xf0000000;
if g != 0 { if g != 0 {
h ^= g >> 24; h ^= g >> 24;
h &= !g; h &= !g;
@ -200,7 +177,7 @@ impl<'a> Linker<'a> {
} }
fn load_section(&mut self, shdr: &Elf32_Shdr, sh_name_str: &'a str, data: Vec<u8>) -> usize { fn load_section(&mut self, shdr: &Elf32_Shdr, sh_name_str: &'a str, data: Vec<u8>) -> usize {
let mut elf_shdr = *shdr; let mut elf_shdr = shdr.clone();
// Maintain alignment requirement specified in sh_addralign // Maintain alignment requirement specified in sh_addralign
let align = shdr.sh_addralign; let align = shdr.sh_addralign;
@ -225,26 +202,22 @@ impl<'a> Linker<'a> {
relocs: &[R], relocs: &[R],
target_section: Elf32_Word, target_section: Elf32_Word,
) -> Result<(), Error> { ) -> Result<(), Error> {
type RelocateFn = dyn Fn(&mut [u8], Elf32_Word);
struct RelocInfo<'a, R> {
pub defined_val: bool,
pub indirect_reloc: Option<&'a R>,
pub pc_relative: bool,
pub relocate: Option<Box<RelocateFn>>,
}
for reloc in relocs { for reloc in relocs {
let sym = match reloc.sym_info() as usize { let sym = match reloc.sym_info() as usize {
STN_UNDEF => None, STN_UNDEF => None,
sym_index => { sym_index => Some(
Some(self.symtab.get(sym_index).ok_or("symbol out of bounds of symbol table")?) self.symtab
} .get(sym_index as usize)
.ok_or("symbol out of bounds of symbol table")?,
),
}; };
let resolve_symbol_addr = let resolve_symbol_addr =
|sym_option: Option<&Elf32_Sym>| -> Result<Elf32_Word, Error> { |sym_option: Option<&Elf32_Sym>| -> Result<Elf32_Word, Error> {
let Some(sym) = sym_option else { return Ok(0) }; let sym = match sym_option {
Some(sym) => sym,
None => return Ok(0),
};
match sym.st_shndx { match sym.st_shndx {
SHN_UNDEF => Err(Error::Lookup("undefined symbol")), SHN_UNDEF => Err(Error::Lookup("undefined symbol")),
@ -267,10 +240,17 @@ impl<'a> Linker<'a> {
let get_target_section_index = || -> Result<usize, Error> { let get_target_section_index = || -> Result<usize, Error> {
self.section_map self.section_map
.get(&(target_section as usize)) .get(&(target_section as usize))
.copied() .map(|&index| index)
.ok_or(Error::Parsing("Cannot find section with matching sh_index")) .ok_or(Error::Parsing("Cannot find section with matching sh_index"))
}; };
struct RelocInfo<'a, R> {
pub defined_val: bool,
pub indirect_reloc: Option<&'a R>,
pub pc_relative: bool,
pub relocate: Option<Box<dyn Fn(&mut [u8], Elf32_Word)>>,
}
let classify = |reloc: &R, sym_option: Option<&Elf32_Sym>| -> Option<RelocInfo<R>> { let classify = |reloc: &R, sym_option: Option<&Elf32_Sym>| -> Option<RelocInfo<R>> {
let defined_val = sym_option.map_or(true, |sym| { let defined_val = sym_option.map_or(true, |sym| {
sym.st_shndx != SHN_UNDEF || ELF32_ST_BIND(sym.st_info) == STB_LOCAL sym.st_shndx != SHN_UNDEF || ELF32_ST_BIND(sym.st_info) == STB_LOCAL
@ -282,7 +262,7 @@ impl<'a> Linker<'a> {
indirect_reloc: None, indirect_reloc: None,
pc_relative: true, pc_relative: true,
relocate: Some(Box::new(|target_word, value| { relocate: Some(Box::new(|target_word, value| {
LittleEndian::write_u32(target_word, value); LittleEndian::write_u32(target_word, value)
})), })),
}), }),
@ -293,9 +273,9 @@ impl<'a> Linker<'a> {
relocate: Some(Box::new(|target_word, value| { relocate: Some(Box::new(|target_word, value| {
LittleEndian::write_u32( LittleEndian::write_u32(
target_word, target_word,
(LittleEndian::read_u32(target_word) & 0x8000_0000) (LittleEndian::read_u32(target_word) & 0x80000000)
| value & 0x7FFF_FFFF, | value & 0x7FFFFFFF,
); )
})), })),
}), }),
@ -317,8 +297,8 @@ impl<'a> Linker<'a> {
relocate: Some(Box::new(|target_word, value| { relocate: Some(Box::new(|target_word, value| {
let auipc_raw = LittleEndian::read_u32(target_word); let auipc_raw = LittleEndian::read_u32(target_word);
let auipc_insn = let auipc_insn =
(auipc_raw & 0xFFF) | ((value + 0x800) & 0xFFFF_F000); (auipc_raw & 0xFFF) | ((value + 0x800) & 0xFFFFF000);
LittleEndian::write_u32(target_word, auipc_insn); LittleEndian::write_u32(target_word, auipc_insn)
})), })),
}) })
} }
@ -328,14 +308,19 @@ impl<'a> Linker<'a> {
indirect_reloc: None, indirect_reloc: None,
pc_relative: true, pc_relative: true,
relocate: Some(Box::new(|target_word, value| { relocate: Some(Box::new(|target_word, value| {
LittleEndian::write_u32(target_word, value); LittleEndian::write_u32(target_word, value)
})), })),
}), }),
R_RISCV_PCREL_LO12_I => { R_RISCV_PCREL_LO12_I => {
let expected_offset = sym_option.map_or(0, |sym| sym.st_value); let expected_offset = sym_option.map_or(0, |sym| sym.st_value);
let indirect_reloc = let indirect_reloc = if let Some(reloc) =
relocs.iter().find(|reloc| reloc.offset() == expected_offset)?; relocs.iter().find(|reloc| reloc.offset() == expected_offset)
{
reloc
} else {
return None;
};
Some(RelocInfo { Some(RelocInfo {
defined_val: { defined_val: {
let indirect_sym = let indirect_sym =
@ -349,14 +334,14 @@ impl<'a> Linker<'a> {
// Here, we convert to direct addressing // Here, we convert to direct addressing
// GOT reloc (indirect) -> lw + addi // GOT reloc (indirect) -> lw + addi
// PCREL reloc (direct) -> addi // PCREL reloc (direct) -> addi
let (lo_opcode, lo_funct3) = (0b001_0011, 0b000); let (lo_opcode, lo_funct3) = (0b0010011, 0b000);
let addi_lw_raw = LittleEndian::read_u32(target_word); let addi_lw_raw = LittleEndian::read_u32(target_word);
let addi_insn = lo_opcode let addi_insn = lo_opcode
| (addi_lw_raw & 0xF8F80) | (addi_lw_raw & 0xF8F80)
| (lo_funct3 << 12) | (lo_funct3 << 12)
| ((value & 0xFFF) << 20); | ((value & 0xFFF) << 20);
LittleEndian::write_u32(target_word, addi_insn); LittleEndian::write_u32(target_word, addi_insn)
})), })),
}) })
} }
@ -373,7 +358,10 @@ impl<'a> Linker<'a> {
indirect_reloc: None, indirect_reloc: None,
pc_relative: false, pc_relative: false,
relocate: Some(Box::new(|target_word, value| { relocate: Some(Box::new(|target_word, value| {
LittleEndian::write_u32(target_word, value); LittleEndian::write_u32(
target_word,
value,
)
})), })),
}), }),
@ -383,7 +371,7 @@ impl<'a> Linker<'a> {
pc_relative: false, pc_relative: false,
relocate: Some(Box::new(|target_word, value| { relocate: Some(Box::new(|target_word, value| {
let old_value = LittleEndian::read_u32(target_word); let old_value = LittleEndian::read_u32(target_word);
LittleEndian::write_u32(target_word, old_value.wrapping_add(value)); LittleEndian::write_u32(target_word, old_value.wrapping_add(value))
})), })),
}), }),
@ -393,7 +381,7 @@ impl<'a> Linker<'a> {
pc_relative: false, pc_relative: false,
relocate: Some(Box::new(|target_word, value| { relocate: Some(Box::new(|target_word, value| {
let old_value = LittleEndian::read_u32(target_word); let old_value = LittleEndian::read_u32(target_word);
LittleEndian::write_u32(target_word, old_value.wrapping_sub(value)); LittleEndian::write_u32(target_word, old_value.wrapping_sub(value))
})), })),
}), }),
@ -402,7 +390,10 @@ impl<'a> Linker<'a> {
indirect_reloc: None, indirect_reloc: None,
pc_relative: false, pc_relative: false,
relocate: Some(Box::new(|target_word, value| { relocate: Some(Box::new(|target_word, value| {
LittleEndian::write_u16(target_word, value as u16); LittleEndian::write_u16(
target_word,
value as u16,
)
})), })),
}), }),
@ -415,7 +406,7 @@ impl<'a> Linker<'a> {
LittleEndian::write_u16( LittleEndian::write_u16(
target_word, target_word,
old_value.wrapping_add(value as u16), old_value.wrapping_add(value as u16),
); )
})), })),
}), }),
@ -428,7 +419,7 @@ impl<'a> Linker<'a> {
LittleEndian::write_u16( LittleEndian::write_u16(
target_word, target_word,
old_value.wrapping_sub(value as u16), old_value.wrapping_sub(value as u16),
); )
})), })),
}), }),
@ -510,7 +501,7 @@ impl<'a> Linker<'a> {
if let Some(relocate) = reloc_info.relocate { if let Some(relocate) = reloc_info.relocate {
let target_word = &mut target_sec_image[reloc.offset() as usize..]; let target_word = &mut target_sec_image[reloc.offset() as usize..];
relocate(target_word, value); relocate(target_word, value)
} else { } else {
self.rela_dyn_relas.push(Elf32_Rela { self.rela_dyn_relas.push(Elf32_Rela {
r_offset: rela_off, r_offset: rela_off,
@ -558,18 +549,17 @@ impl<'a> Linker<'a> {
let eh_frame_slice = eh_frame_rec.data.as_slice(); let eh_frame_slice = eh_frame_rec.data.as_slice();
// Prepare a new buffer to dodge borrow check // Prepare a new buffer to dodge borrow check
let mut eh_frame_hdr_vec: Vec<u8> = vec![0; eh_frame_hdr_rec.shdr.sh_size as usize]; let mut eh_frame_hdr_vec: Vec<u8> = vec![0; eh_frame_hdr_rec.shdr.sh_size as usize];
let eh_frame = EH_Frame::new(eh_frame_slice, eh_frame_rec.shdr.sh_offset); let eh_frame = EH_Frame::new(eh_frame_slice, eh_frame_rec.shdr.sh_offset)
.map_err(|()| "cannot read EH frame")?;
let mut eh_frame_hdr = EH_Frame_Hdr::new( let mut eh_frame_hdr = EH_Frame_Hdr::new(
eh_frame_hdr_vec.as_mut_slice(), eh_frame_hdr_vec.as_mut_slice(),
eh_frame_hdr_rec.shdr.sh_offset, eh_frame_hdr_rec.shdr.sh_offset,
eh_frame_rec.shdr.sh_offset, eh_frame_rec.shdr.sh_offset,
); );
eh_frame.cfi_records().flat_map(|cfi| cfi.fde_records()).for_each(&mut |( let mut fde_callback = |init_pos, virt_addr| eh_frame_hdr.add_fde(init_pos, virt_addr);
init_pos, eh_frame
virt_addr, .iterate_fde(&mut fde_callback)
)| { .map_err(|()| "failed to add FDE to .eh_frame_hdr while iterating .eh_frame")?;
eh_frame_hdr.add_fde(init_pos, virt_addr);
});
// Sort FDE entries in .eh_frame_hdr // Sort FDE entries in .eh_frame_hdr
eh_frame_hdr.finalize_fde(); eh_frame_hdr.finalize_fde();
@ -583,114 +573,39 @@ impl<'a> Linker<'a> {
} }
pub fn ld(data: &'a [u8]) -> Result<Vec<u8>, Error> { pub fn ld(data: &'a [u8]) -> Result<Vec<u8>, Error> {
fn allocate_rela_dyn<R: Relocatable>( let ehdr = read_unaligned::<Elf32_Ehdr>(data, 0).map_err(|()| "cannot read ELF header")?;
linker: &Linker,
relocs: &[R],
) -> Result<(usize, Vec<u32>), Error> {
let mut alloc_size = 0;
let mut rela_dyn_sym_indices = Vec::new();
for reloc in relocs {
if reloc.sym_info() as usize == STN_UNDEF {
continue;
}
let sym: &Elf32_Sym = linker
.symtab
.get(reloc.sym_info() as usize)
.ok_or("symbol out of bounds of symbol table")?;
match (linker.isa, reloc.type_info()) {
// Absolute address relocations
// A runtime relocation is needed to find the loading address
(Isa::CortexA9, R_ARM_ABS32) | (Isa::RiscV32, R_RISCV_32) => {
alloc_size += mem::size_of::<Elf32_Rela>(); // FIXME: RELA vs REL
if ELF32_ST_BIND(sym.st_info) == STB_GLOBAL && sym.st_shndx == SHN_UNDEF {
rela_dyn_sym_indices.push(reloc.sym_info());
}
}
// Relative address relocations
// Relay the relocation to the runtime linker only if the symbol is not defined
(Isa::CortexA9, R_ARM_REL32 | R_ARM_PREL31 | R_ARM_TARGET2)
| (
Isa::RiscV32,
R_RISCV_CALL_PLT | R_RISCV_PCREL_HI20 | R_RISCV_GOT_HI20 | R_RISCV_32_PCREL
| R_RISCV_SET32 | R_RISCV_ADD32 | R_RISCV_SUB32 | R_RISCV_SET16
| R_RISCV_ADD16 | R_RISCV_SUB16 | R_RISCV_SET8 | R_RISCV_ADD8
| R_RISCV_SUB8 | R_RISCV_SET6 | R_RISCV_SUB6,
) => {
if ELF32_ST_BIND(sym.st_info) == STB_GLOBAL && sym.st_shndx == SHN_UNDEF {
alloc_size += mem::size_of::<Elf32_Rela>(); // FIXME: RELA vs REL
rela_dyn_sym_indices.push(reloc.sym_info());
}
}
// RISC-V: Lower 12-bits relocations
// If the upper 20-bits relocation cannot be resolved,
// this relocation will be relayed to the runtime linker.
(Isa::RiscV32, R_RISCV_PCREL_LO12_I) => {
// Find the HI20 relocation
let indirect_reloc = relocs
.iter()
.find(|reloc| reloc.offset() == sym.st_value)
.ok_or("malformatted LO12 relocation")?;
let indirect_sym = linker.symtab[indirect_reloc.sym_info() as usize];
if ELF32_ST_BIND(indirect_sym.st_info) == STB_GLOBAL
&& indirect_sym.st_shndx == SHN_UNDEF
{
alloc_size += mem::size_of::<Elf32_Rela>(); // FIXME: RELA vs REL
rela_dyn_sym_indices.push(reloc.sym_info());
}
}
_ => {
println!("Relocation type 0x{:X?} is not supported", reloc.type_info());
unimplemented!()
}
}
}
Ok((alloc_size, rela_dyn_sym_indices))
}
let Some(ehdr) = read_unaligned::<Elf32_Ehdr>(data, 0) else {
Err("cannot read ELF header")?
};
let isa = match ehdr.e_machine { let isa = match ehdr.e_machine {
EM_ARM => Isa::CortexA9, EM_ARM => Isa::CortexA9,
EM_RISCV => Isa::RiscV32, EM_RISCV => Isa::RiscV32,
_ => return Err(Error::Parsing("unsupported architecture")), _ => return Err(Error::Parsing("unsupported architecture")),
}; };
let Some(shdrs) = let shdrs = get_ref_slice::<Elf32_Shdr>(data, ehdr.e_shoff as usize, ehdr.e_shnum as usize)
get_ref_slice::<Elf32_Shdr>(data, ehdr.e_shoff as usize, ehdr.e_shnum as usize) .map_err(|()| "cannot read section header table")?;
else {
Err("cannot read section header table")?
};
// Read .strtab // Read .strtab
let strtab_shdr = shdrs[ehdr.e_shstrndx as usize]; let strtab_shdr = shdrs[ehdr.e_shstrndx as usize];
let Some(strtab) = let strtab =
get_ref_slice::<u8>(data, strtab_shdr.sh_offset as usize, strtab_shdr.sh_size as usize) get_ref_slice::<u8>(data, strtab_shdr.sh_offset as usize, strtab_shdr.sh_size as usize)
else { .map_err(|()| "cannot read the string table from data")?;
Err("cannot read the string table from data")?
};
// Read .symtab // Read .symtab
let symtab_shdr = shdrs let symtab_shdr = shdrs
.iter() .iter()
.find(|shdr| shdr.sh_type as usize == SHT_SYMTAB) .find(|shdr| shdr.sh_type as usize == SHT_SYMTAB)
.ok_or(Error::Parsing("cannot find the symbol table"))?; .ok_or(Error::Parsing("cannot find the symbol table"))?;
let Some(symtab) = get_ref_slice::<Elf32_Sym>( let symtab = get_ref_slice::<Elf32_Sym>(
data, data,
symtab_shdr.sh_offset as usize, symtab_shdr.sh_offset as usize,
symtab_shdr.sh_size as usize / mem::size_of::<Elf32_Sym>(), symtab_shdr.sh_size as usize / mem::size_of::<Elf32_Sym>(),
) else { )
Err("cannot read the symbol table from data")? .map_err(|()| "cannot read the symbol table from data")?;
};
// Section table for the .elf paired with the section name // Section table for the .elf paired with the section name
// To be formalized incrementally // To be formalized incrementally
// Very hashmap-like structure, but the order matters, so it is a vector // Very hashmap-like structure, but the order matters, so it is a vector
let elf_shdrs = vec![SectionRecord { let mut elf_shdrs = Vec::new();
elf_shdrs.push(SectionRecord {
shdr: Elf32_Shdr { shdr: Elf32_Shdr {
sh_name: 0, sh_name: 0,
sh_type: 0, sh_type: 0,
@ -705,7 +620,7 @@ impl<'a> Linker<'a> {
}, },
name: "", name: "",
data: vec![0; 0], data: vec![0; 0],
}]; });
let elf_sh_data_off = mem::size_of::<Elf32_Ehdr>() + mem::size_of::<Elf32_Phdr>() * 5; let elf_sh_data_off = mem::size_of::<Elf32_Ehdr>() + mem::size_of::<Elf32_Phdr>() * 5;
// Image of the linked dynamic library, to be formalized incrementally // Image of the linked dynamic library, to be formalized incrementally
@ -745,8 +660,8 @@ impl<'a> Linker<'a> {
linker.load_section( linker.load_section(
&text_shdr, &text_shdr,
".text", ".text",
data[text_shdr.sh_offset as usize (&data[text_shdr.sh_offset as usize
..text_shdr.sh_offset as usize + text_shdr.sh_size as usize] ..text_shdr.sh_offset as usize + text_shdr.sh_size as usize])
.to_vec(), .to_vec(),
); );
linker.section_map.insert(text_shdr_index, 1); linker.section_map.insert(text_shdr_index, 1);
@ -764,8 +679,8 @@ impl<'a> Linker<'a> {
let loaded_index = linker.load_section( let loaded_index = linker.load_section(
&arm_exidx_shdr, &arm_exidx_shdr,
".ARM.exidx", ".ARM.exidx",
data[arm_exidx_shdr.sh_offset as usize (&data[arm_exidx_shdr.sh_offset as usize
..arm_exidx_shdr.sh_offset as usize + arm_exidx_shdr.sh_size as usize] ..arm_exidx_shdr.sh_offset as usize + arm_exidx_shdr.sh_size as usize])
.to_vec(), .to_vec(),
); );
linker.section_map.insert(arm_exidx_shdr_index, loaded_index); linker.section_map.insert(arm_exidx_shdr_index, loaded_index);
@ -784,7 +699,7 @@ impl<'a> Linker<'a> {
let elf_shdrs_index = linker.load_section( let elf_shdrs_index = linker.load_section(
shdr, shdr,
str::from_utf8(section_name).unwrap(), str::from_utf8(section_name).unwrap(),
data[shdr.sh_offset as usize..(shdr.sh_offset + shdr.sh_size) as usize].to_vec(), (&data[shdr.sh_offset as usize..(shdr.sh_offset + shdr.sh_size) as usize]).to_vec(),
); );
linker.section_map.insert(i, elf_shdrs_index); linker.section_map.insert(i, elf_shdrs_index);
} }
@ -841,27 +756,21 @@ impl<'a> Linker<'a> {
($shdr: expr, $stmt: expr) => { ($shdr: expr, $stmt: expr) => {
match $shdr.sh_type as usize { match $shdr.sh_type as usize {
SHT_RELA => { SHT_RELA => {
let Some(relocs) = get_ref_slice::<Elf32_Rela>( let relocs = get_ref_slice::<Elf32_Rela>(
data, data,
$shdr.sh_offset as usize, $shdr.sh_offset as usize,
$shdr.sh_size as usize / mem::size_of::<Elf32_Rela>(), $shdr.sh_size as usize / mem::size_of::<Elf32_Rela>(),
) else { )
Err("cannot parse relocations")? .map_err(|()| "cannot parse relocations")?;
};
#[allow(clippy::redundant_closure_call)]
$stmt(relocs) $stmt(relocs)
} }
SHT_REL => { SHT_REL => {
let Some(relocs) = get_ref_slice::<Elf32_Rel>( let relocs = get_ref_slice::<Elf32_Rel>(
data, data,
$shdr.sh_offset as usize, $shdr.sh_offset as usize,
$shdr.sh_size as usize / mem::size_of::<Elf32_Rel>(), $shdr.sh_size as usize / mem::size_of::<Elf32_Rel>(),
) else { )
Err("cannot parse relocations")? .map_err(|()| "cannot parse relocations")?;
};
#[allow(clippy::redundant_closure_call)]
$stmt(relocs) $stmt(relocs)
} }
_ => unreachable!(), _ => unreachable!(),
@ -869,6 +778,84 @@ impl<'a> Linker<'a> {
}; };
} }
fn allocate_rela_dyn<R: Relocatable>(
linker: &Linker,
relocs: &[R],
) -> Result<(usize, Vec<u32>), Error> {
let mut alloc_size = 0;
let mut rela_dyn_sym_indices = Vec::new();
for reloc in relocs {
if reloc.sym_info() as usize == STN_UNDEF {
continue;
}
let sym: &Elf32_Sym = linker
.symtab
.get(reloc.sym_info() as usize)
.ok_or("symbol out of bounds of symbol table")?;
match (linker.isa, reloc.type_info()) {
// Absolute address relocations
// A runtime relocation is needed to find the loading address
(Isa::CortexA9, R_ARM_ABS32) | (Isa::RiscV32, R_RISCV_32) => {
alloc_size += mem::size_of::<Elf32_Rela>(); // FIXME: RELA vs REL
if ELF32_ST_BIND(sym.st_info) == STB_GLOBAL && sym.st_shndx == SHN_UNDEF {
rela_dyn_sym_indices.push(reloc.sym_info());
}
}
// Relative address relocations
// Relay the relocation to the runtime linker only if the symbol is not defined
(Isa::CortexA9, R_ARM_REL32)
| (Isa::CortexA9, R_ARM_PREL31)
| (Isa::CortexA9, R_ARM_TARGET2)
| (Isa::RiscV32, R_RISCV_CALL_PLT)
| (Isa::RiscV32, R_RISCV_PCREL_HI20)
| (Isa::RiscV32, R_RISCV_GOT_HI20)
| (Isa::RiscV32, R_RISCV_32_PCREL)
| (Isa::RiscV32, R_RISCV_SET32)
| (Isa::RiscV32, R_RISCV_ADD32)
| (Isa::RiscV32, R_RISCV_SUB32)
| (Isa::RiscV32, R_RISCV_SET16)
| (Isa::RiscV32, R_RISCV_ADD16)
| (Isa::RiscV32, R_RISCV_SUB16)
| (Isa::RiscV32, R_RISCV_SET8)
| (Isa::RiscV32, R_RISCV_ADD8)
| (Isa::RiscV32, R_RISCV_SUB8)
| (Isa::RiscV32, R_RISCV_SET6)
| (Isa::RiscV32, R_RISCV_SUB6) => {
if ELF32_ST_BIND(sym.st_info) == STB_GLOBAL && sym.st_shndx == SHN_UNDEF {
alloc_size += mem::size_of::<Elf32_Rela>(); // FIXME: RELA vs REL
rela_dyn_sym_indices.push(reloc.sym_info());
}
}
// RISC-V: Lower 12-bits relocations
// If the upper 20-bits relocation cannot be resolved,
// this relocation will be relayed to the runtime linker.
(Isa::RiscV32, R_RISCV_PCREL_LO12_I) => {
// Find the HI20 relocation
let indirect_reloc = relocs
.iter()
.find(|reloc| reloc.offset() == sym.st_value)
.ok_or("malformatted LO12 relocation")?;
let indirect_sym = linker.symtab[indirect_reloc.sym_info() as usize];
if ELF32_ST_BIND(indirect_sym.st_info) == STB_GLOBAL
&& indirect_sym.st_shndx == SHN_UNDEF
{
alloc_size += mem::size_of::<Elf32_Rela>(); // FIXME: RELA vs REL
rela_dyn_sym_indices.push(reloc.sym_info());
}
}
_ => {
println!("Relocation type 0x{:X?} is not supported", reloc.type_info());
unimplemented!()
}
}
}
Ok((alloc_size, rela_dyn_sym_indices))
}
for shdr in shdrs for shdr in shdrs
.iter() .iter()
.filter(|shdr| shdr.sh_type as usize == SHT_REL || shdr.sh_type as usize == SHT_RELA) .filter(|shdr| shdr.sh_type as usize == SHT_REL || shdr.sh_type as usize == SHT_RELA)
@ -896,7 +883,7 @@ impl<'a> Linker<'a> {
} }
// Avoid symbol duplication // Avoid symbol duplication
rela_dyn_sym_indices.sort_unstable(); rela_dyn_sym_indices.sort();
rela_dyn_sym_indices.dedup(); rela_dyn_sym_indices.dedup();
if rela_dyn_size != 0 { if rela_dyn_size != 0 {
@ -932,7 +919,7 @@ impl<'a> Linker<'a> {
dynsym_names.push((0, 0)); dynsym_names.push((0, 0));
for rela_dyn_sym_index in rela_dyn_sym_indices { for rela_dyn_sym_index in rela_dyn_sym_indices {
let mut sym = linker.symtab[rela_dyn_sym_index as usize]; let mut sym = linker.symtab[rela_dyn_sym_index as usize].clone();
let sym_name = name_starting_at_slice(strtab, sym.st_name as usize) let sym_name = name_starting_at_slice(strtab, sym.st_name as usize)
.map_err(|_| "cannot read symbol name from the original .strtab")?; .map_err(|_| "cannot read symbol name from the original .strtab")?;
let dynstr_start_index = dynstr.len(); let dynstr_start_index = dynstr.len();
@ -942,7 +929,7 @@ impl<'a> Linker<'a> {
let elf_shdr_index = linker let elf_shdr_index = linker
.section_map .section_map
.get(&(sym.st_shndx as usize)) .get(&(sym.st_shndx as usize))
.copied() .map(|&index| index)
.ok_or(Error::Parsing("Cannot find section with matching sh_index"))?; .ok_or(Error::Parsing("Cannot find section with matching sh_index"))?;
let elf_shdr_offset = linker.elf_shdrs[elf_shdr_index].shdr.sh_offset; let elf_shdr_offset = linker.elf_shdrs[elf_shdr_index].shdr.sh_offset;
sym.st_value += elf_shdr_offset; sym.st_value += elf_shdr_offset;
@ -969,7 +956,7 @@ impl<'a> Linker<'a> {
let modinit_shdr_index = linker let modinit_shdr_index = linker
.section_map .section_map
.get(&(modinit_sym.st_shndx as usize)) .get(&(modinit_sym.st_shndx as usize))
.copied() .map(|&index| index)
.ok_or(Error::Parsing("Cannot find section with matching sh_index"))?; .ok_or(Error::Parsing("Cannot find section with matching sh_index"))?;
let modinit_shdr = linker.elf_shdrs[modinit_shdr_index].shdr; let modinit_shdr = linker.elf_shdrs[modinit_shdr_index].shdr;
@ -1027,10 +1014,9 @@ impl<'a> Linker<'a> {
let mut hash_bucket: Vec<u32> = vec![0; dynsym.len()]; let mut hash_bucket: Vec<u32> = vec![0; dynsym.len()];
let mut hash_chain: Vec<u32> = vec![0; dynsym.len()]; let mut hash_chain: Vec<u32> = vec![0; dynsym.len()];
for (sym_index, (str_start, str_end)) in for sym_index in 1..dynsym.len() {
dynsym_names.iter().enumerate().take(dynsym.len()).skip(1) let (str_start, str_end) = dynsym_names[sym_index];
{ let hash = elf_hash(&dynstr[str_start..str_end]);
let hash = elf_hash(&dynstr[*str_start..*str_end]);
let mut hash_index = hash as usize % hash_bucket.len(); let mut hash_index = hash as usize % hash_bucket.len();
if hash_bucket[hash_index] == 0 { if hash_bucket[hash_index] == 0 {
@ -1081,7 +1067,7 @@ impl<'a> Linker<'a> {
sh_entsize: mem::size_of::<Elf32_Sym>() as Elf32_Word, sh_entsize: mem::size_of::<Elf32_Sym>() as Elf32_Word,
}, },
".dynsym", ".dynsym",
from_struct_slice(&dynsym), from_struct_vec(dynsym),
); );
let hash_elf_index = linker.load_section( let hash_elf_index = linker.load_section(
&Elf32_Shdr { &Elf32_Shdr {
@ -1097,7 +1083,7 @@ impl<'a> Linker<'a> {
sh_entsize: 4, sh_entsize: 4,
}, },
".hash", ".hash",
from_struct_slice(&hash), from_struct_vec(hash),
); );
// Link .rela.dyn header to the .dynsym header // Link .rela.dyn header to the .dynsym header
@ -1119,7 +1105,7 @@ impl<'a> Linker<'a> {
let elf_shdrs_index = linker.load_section( let elf_shdrs_index = linker.load_section(
shdr, shdr,
str::from_utf8(section_name).unwrap(), str::from_utf8(section_name).unwrap(),
data[shdr.sh_offset as usize..(shdr.sh_offset + shdr.sh_size) as usize] (&data[shdr.sh_offset as usize..(shdr.sh_offset + shdr.sh_size) as usize])
.to_vec(), .to_vec(),
); );
linker.section_map.insert(i, elf_shdrs_index); linker.section_map.insert(i, elf_shdrs_index);
@ -1196,7 +1182,7 @@ impl<'a> Linker<'a> {
}; };
let dynamic_elf_index = let dynamic_elf_index =
linker.load_section(&dynamic_shdr, ".dynamic", from_struct_slice(&dyn_entries)); linker.load_section(&dynamic_shdr, ".dynamic", from_struct_vec(dyn_entries));
let last_w_sec_elf_index = linker.elf_shdrs.len() - 1; let last_w_sec_elf_index = linker.elf_shdrs.len() - 1;
@ -1223,7 +1209,7 @@ impl<'a> Linker<'a> {
let elf_shdrs_index = linker.load_section( let elf_shdrs_index = linker.load_section(
shdr, shdr,
section_name, section_name,
data[shdr.sh_offset as usize..(shdr.sh_offset + shdr.sh_size) as usize] (&data[shdr.sh_offset as usize..(shdr.sh_offset + shdr.sh_size) as usize])
.to_vec(), .to_vec(),
); );
linker.section_map.insert(i, elf_shdrs_index); linker.section_map.insert(i, elf_shdrs_index);
@ -1272,14 +1258,12 @@ impl<'a> Linker<'a> {
update_dynsym_record!(b"__bss_start", bss_offset, bss_elf_index as Elf32_Section); update_dynsym_record!(b"__bss_start", bss_offset, bss_elf_index as Elf32_Section);
update_dynsym_record!(b"_end", bss_offset, bss_elf_index as Elf32_Section); update_dynsym_record!(b"_end", bss_offset, bss_elf_index as Elf32_Section);
} else { } else {
for (bss_iter_index, &(bss_section_index, section_name)) in for (bss_iter_index, &(bss_section_index, section_name)) in bss_index_vec.iter().enumerate() {
bss_index_vec.iter().enumerate()
{
let shdr = &shdrs[bss_section_index]; let shdr = &shdrs[bss_section_index];
let bss_elf_index = linker.load_section( let bss_elf_index = linker.load_section(
shdr, shdr,
section_name, section_name,
data[shdr.sh_offset as usize..(shdr.sh_offset + shdr.sh_size) as usize] (&data[shdr.sh_offset as usize..(shdr.sh_offset + shdr.sh_size) as usize])
.to_vec(), .to_vec(),
); );
linker.section_map.insert(bss_section_index, bss_elf_index); linker.section_map.insert(bss_section_index, bss_elf_index);
@ -1347,7 +1331,7 @@ impl<'a> Linker<'a> {
// Prepare a STRTAB to hold the names of section headers // Prepare a STRTAB to hold the names of section headers
// Fix the sh_name field of the section headers // Fix the sh_name field of the section headers
let mut shstrtab = Vec::new(); let mut shstrtab = Vec::new();
for shdr_rec in &mut linker.elf_shdrs { for shdr_rec in linker.elf_shdrs.iter_mut() {
let shstrtab_index = shstrtab.len(); let shstrtab_index = shstrtab.len();
shstrtab.extend(shdr_rec.name.as_bytes()); shstrtab.extend(shdr_rec.name.as_bytes());
shstrtab.push(0); shstrtab.push(0);
@ -1388,17 +1372,20 @@ impl<'a> Linker<'a> {
let alignment = (4 - (linker.image.len() % 4)) % 4; let alignment = (4 - (linker.image.len() % 4)) % 4;
let sec_headers_offset = linker.image.len() + alignment; let sec_headers_offset = linker.image.len() + alignment;
linker.image.extend(vec![0; alignment]); linker.image.extend(vec![0; alignment]);
for rec in &linker.elf_shdrs { for rec in linker.elf_shdrs.iter() {
let shdr = rec.shdr; let shdr = rec.shdr;
linker.image.extend(unsafe { linker.image.extend(unsafe {
slice::from_raw_parts(ptr::addr_of!(shdr).cast(), mem::size_of::<Elf32_Shdr>()) slice::from_raw_parts(
&shdr as *const Elf32_Shdr as *const u8,
mem::size_of::<Elf32_Shdr>(),
)
}); });
} }
// Update the PHDRs // Update the PHDRs
let phdr_offset = mem::size_of::<Elf32_Ehdr>(); let phdr_offset = mem::size_of::<Elf32_Ehdr>();
unsafe { unsafe {
let phdr_ptr = linker.image.as_mut_ptr().add(phdr_offset).cast(); let phdr_ptr = linker.image.as_mut_ptr().add(phdr_offset) as *mut Elf32_Phdr;
let phdr_slice = slice::from_raw_parts_mut(phdr_ptr, 5); let phdr_slice = slice::from_raw_parts_mut(phdr_ptr, 5);
// List of program headers: // List of program headers:
// 1. ELF headers & program headers // 1. ELF headers & program headers
@ -1475,9 +1462,9 @@ impl<'a> Linker<'a> {
} }
// Update the EHDR // Update the EHDR
let ehdr_ptr = linker.image.as_mut_ptr().cast(); let ehdr_ptr = linker.image.as_mut_ptr() as *mut Elf32_Ehdr;
unsafe { unsafe {
*ehdr_ptr = Elf32_Ehdr { (*ehdr_ptr) = Elf32_Ehdr {
e_ident: ehdr.e_ident, e_ident: ehdr.e_ident,
e_type: ET_DYN, e_type: ET_DYN,
e_machine: ehdr.e_machine, e_machine: ehdr.e_machine,

View File

@ -5,20 +5,20 @@ description = "Parser for python code."
authors = [ "RustPython Team", "M-Labs" ] authors = [ "RustPython Team", "M-Labs" ]
build = "build.rs" build = "build.rs"
license = "MIT" license = "MIT"
edition = "2021" edition = "2018"
[build-dependencies] [build-dependencies]
lalrpop = "0.20" lalrpop = "0.19"
[dependencies] [dependencies]
nac3ast = { path = "../nac3ast" } nac3ast = { path = "../nac3ast" }
lalrpop-util = "0.20" lalrpop-util = "0.19"
log = "0.4" log = "0.4"
unic-emoji-char = "0.9" unic-emoji-char = "0.9"
unic-ucd-ident = "0.9" unic-ucd-ident = "0.9"
unicode_names2 = "1.2" unicode_names2 = "0.5"
phf = { version = "0.11", features = ["macros"] } phf = { version = "0.11", features = ["macros"] }
ahash = "0.8" ahash = "0.7"
[dev-dependencies] [dev-dependencies]
insta = "=1.11.0" insta = "=1.11.0"

View File

@ -1,15 +1,15 @@
use crate::ast::Ident;
use crate::ast::Location;
use crate::error::*;
use crate::token::Tok;
use lalrpop_util::ParseError; use lalrpop_util::ParseError;
use nac3ast::*; use nac3ast::*;
use crate::ast::Ident;
use crate::ast::Location;
use crate::token::Tok;
use crate::error::*;
pub fn make_config_comment( pub fn make_config_comment(
com_loc: Location, com_loc: Location,
stmt_loc: Location, stmt_loc: Location,
nac3com_above: Vec<(Ident, Tok)>, nac3com_above: Vec<(Ident, Tok)>,
nac3com_end: Option<Ident>, nac3com_end: Option<Ident>
) -> Result<Vec<Ident>, ParseError<Location, Tok, LexicalError>> { ) -> Result<Vec<Ident>, ParseError<Location, Tok, LexicalError>> {
if com_loc.column() != stmt_loc.column() && !nac3com_above.is_empty() { if com_loc.column() != stmt_loc.column() && !nac3com_above.is_empty() {
return Err(ParseError::User { return Err(ParseError::User {
@ -17,25 +17,24 @@ pub fn make_config_comment(
location: com_loc, location: com_loc,
error: LexicalErrorType::OtherError( error: LexicalErrorType::OtherError(
format!( format!(
"config comment at top must have the same indentation with what it applies (comment at {com_loc}, statement at {stmt_loc})", "config comment at top must have the same indentation with what it applies (comment at {}, statement at {})",
com_loc,
stmt_loc,
) )
) )
} }
}); })
}; };
Ok(nac3com_above Ok(
.into_iter() nac3com_above
.map(|(com, _)| com) .into_iter()
.chain(nac3com_end.map_or_else(|| vec![].into_iter(), |com| vec![com].into_iter())) .map(|(com, _)| com)
.collect()) .chain(nac3com_end.map_or_else(|| vec![].into_iter(), |com| vec![com].into_iter()))
.collect()
)
} }
pub fn handle_small_stmt<U>( pub fn handle_small_stmt<U>(stmts: &mut [Stmt<U>], nac3com_above: Vec<(Ident, Tok)>, nac3com_end: Option<Ident>, com_above_loc: Location) -> Result<(), ParseError<Location, Tok, LexicalError>> {
stmts: &mut [Stmt<U>],
nac3com_above: Vec<(Ident, Tok)>,
nac3com_end: Option<Ident>,
com_above_loc: Location,
) -> Result<(), ParseError<Location, Tok, LexicalError>> {
if com_above_loc.column() != stmts[0].location.column() && !nac3com_above.is_empty() { if com_above_loc.column() != stmts[0].location.column() && !nac3com_above.is_empty() {
return Err(ParseError::User { return Err(ParseError::User {
error: LexicalError { error: LexicalError {
@ -48,12 +47,17 @@ pub fn handle_small_stmt<U>(
) )
) )
} }
}); })
} }
apply_config_comments(&mut stmts[0], nac3com_above.into_iter().map(|(com, _)| com).collect()); apply_config_comments(
&mut stmts[0],
nac3com_above
.into_iter()
.map(|(com, _)| com).collect()
);
apply_config_comments( apply_config_comments(
stmts.last_mut().unwrap(), stmts.last_mut().unwrap(),
nac3com_end.map_or_else(Vec::new, |com| vec![com]), nac3com_end.map_or_else(Vec::new, |com| vec![com])
); );
Ok(()) Ok(())
} }
@ -76,8 +80,6 @@ fn apply_config_comments<U>(stmt: &mut Stmt<U>, comments: Vec<Ident>) {
| StmtKind::Nonlocal { config_comment, .. } | StmtKind::Nonlocal { config_comment, .. }
| StmtKind::Assert { config_comment, .. } => config_comment.extend(comments), | StmtKind::Assert { config_comment, .. } => config_comment.extend(comments),
_ => { _ => { unreachable!("only small statements should call this function") }
unreachable!("only small statements should call this function")
}
} }
} }

View File

@ -37,7 +37,7 @@ impl fmt::Display for LexicalErrorType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { match self {
LexicalErrorType::StringError => write!(f, "Got unexpected string"), LexicalErrorType::StringError => write!(f, "Got unexpected string"),
LexicalErrorType::FStringError(error) => write!(f, "Got error in f-string: {error}"), LexicalErrorType::FStringError(error) => write!(f, "Got error in f-string: {}", error),
LexicalErrorType::UnicodeError => write!(f, "Got unexpected unicode"), LexicalErrorType::UnicodeError => write!(f, "Got unexpected unicode"),
LexicalErrorType::NestingError => write!(f, "Got unexpected nesting"), LexicalErrorType::NestingError => write!(f, "Got unexpected nesting"),
LexicalErrorType::IndentationError => { LexicalErrorType::IndentationError => {
@ -59,13 +59,13 @@ impl fmt::Display for LexicalErrorType {
write!(f, "positional argument follows keyword argument") write!(f, "positional argument follows keyword argument")
} }
LexicalErrorType::UnrecognizedToken { tok } => { LexicalErrorType::UnrecognizedToken { tok } => {
write!(f, "Got unexpected token {tok}") write!(f, "Got unexpected token {}", tok)
} }
LexicalErrorType::LineContinuationError => { LexicalErrorType::LineContinuationError => {
write!(f, "unexpected character after line continuation character") write!(f, "unexpected character after line continuation character")
} }
LexicalErrorType::Eof => write!(f, "unexpected EOF while parsing"), LexicalErrorType::Eof => write!(f, "unexpected EOF while parsing"),
LexicalErrorType::OtherError(msg) => write!(f, "{msg}"), LexicalErrorType::OtherError(msg) => write!(f, "{}", msg),
} }
} }
} }
@ -96,7 +96,7 @@ impl fmt::Display for FStringErrorType {
FStringErrorType::UnopenedRbrace => write!(f, "Unopened '}}'"), FStringErrorType::UnopenedRbrace => write!(f, "Unopened '}}'"),
FStringErrorType::ExpectedRbrace => write!(f, "Expected '}}' after conversion flag."), FStringErrorType::ExpectedRbrace => write!(f, "Expected '}}' after conversion flag."),
FStringErrorType::InvalidExpression(error) => { FStringErrorType::InvalidExpression(error) => {
write!(f, "Invalid expression: {error}") write!(f, "Invalid expression: {}", error)
} }
FStringErrorType::InvalidConversionFlag => write!(f, "Invalid conversion flag"), FStringErrorType::InvalidConversionFlag => write!(f, "Invalid conversion flag"),
FStringErrorType::EmptyExpression => write!(f, "Empty expression"), FStringErrorType::EmptyExpression => write!(f, "Empty expression"),
@ -144,27 +144,36 @@ pub enum ParseErrorType {
impl From<LalrpopError<Location, Tok, LexicalError>> for ParseError { impl From<LalrpopError<Location, Tok, LexicalError>> for ParseError {
fn from(err: LalrpopError<Location, Tok, LexicalError>) -> Self { fn from(err: LalrpopError<Location, Tok, LexicalError>) -> Self {
match err { match err {
LalrpopError::ExtraToken { token } => { // TODO: Are there cases where this isn't an EOF?
ParseError { error: ParseErrorType::ExtraToken(token.1), location: token.0 } LalrpopError::InvalidToken { location } => ParseError {
} error: ParseErrorType::Eof,
LalrpopError::User { error } => { location,
ParseError { error: ParseErrorType::Lexical(error.error), location: error.location } },
} LalrpopError::ExtraToken { token } => ParseError {
error: ParseErrorType::ExtraToken(token.1),
location: token.0,
},
LalrpopError::User { error } => ParseError {
error: ParseErrorType::Lexical(error.error),
location: error.location,
},
LalrpopError::UnrecognizedToken { token, expected } => { LalrpopError::UnrecognizedToken { token, expected } => {
// Hacky, but it's how CPython does it. See PyParser_AddToken, // Hacky, but it's how CPython does it. See PyParser_AddToken,
// in particular "Only one possible expected token" comment. // in particular "Only one possible expected token" comment.
let expected = if expected.len() == 1 { Some(expected[0].clone()) } else { None }; let expected = if expected.len() == 1 {
Some(expected[0].clone())
} else {
None
};
ParseError { ParseError {
error: ParseErrorType::UnrecognizedToken(token.1, expected), error: ParseErrorType::UnrecognizedToken(token.1, expected),
location: token.0, location: token.0,
} }
} }
LalrpopError::UnrecognizedEOF { location, .. } => ParseError {
LalrpopError::UnrecognizedEof { location, .. } error: ParseErrorType::Eof,
// TODO: Are there cases where this isn't an EOF? location,
| LalrpopError::InvalidToken { location } => { },
ParseError { error: ParseErrorType::Eof, location }
}
} }
} }
} }
@ -179,7 +188,7 @@ impl fmt::Display for ParseErrorType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self { match *self {
ParseErrorType::Eof => write!(f, "Got unexpected EOF"), ParseErrorType::Eof => write!(f, "Got unexpected EOF"),
ParseErrorType::ExtraToken(ref tok) => write!(f, "Got extraneous token: {tok:?}"), ParseErrorType::ExtraToken(ref tok) => write!(f, "Got extraneous token: {:?}", tok),
ParseErrorType::InvalidToken => write!(f, "Got invalid token"), ParseErrorType::InvalidToken => write!(f, "Got invalid token"),
ParseErrorType::UnrecognizedToken(ref tok, ref expected) => { ParseErrorType::UnrecognizedToken(ref tok, ref expected) => {
if *tok == Tok::Indent { if *tok == Tok::Indent {
@ -187,10 +196,10 @@ impl fmt::Display for ParseErrorType {
} else if expected.as_deref() == Some("Indent") { } else if expected.as_deref() == Some("Indent") {
write!(f, "expected an indented block") write!(f, "expected an indented block")
} else { } else {
write!(f, "Got unexpected token {tok}") write!(f, "Got unexpected token {}", tok)
} }
} }
ParseErrorType::Lexical(ref error) => write!(f, "{error}"), ParseErrorType::Lexical(ref error) => write!(f, "{}", error),
} }
} }
} }
@ -198,7 +207,6 @@ impl fmt::Display for ParseErrorType {
impl Error for ParseErrorType {} impl Error for ParseErrorType {}
impl ParseErrorType { impl ParseErrorType {
#[must_use]
pub fn is_indentation_error(&self) -> bool { pub fn is_indentation_error(&self) -> bool {
match self { match self {
ParseErrorType::Lexical(LexicalErrorType::IndentationError) => true, ParseErrorType::Lexical(LexicalErrorType::IndentationError) => true,
@ -208,11 +216,11 @@ impl ParseErrorType {
_ => false, _ => false,
} }
} }
#[must_use]
pub fn is_tab_error(&self) -> bool { pub fn is_tab_error(&self) -> bool {
matches!( matches!(
self, self,
ParseErrorType::Lexical(LexicalErrorType::TabError | LexicalErrorType::TabsAfterSpaces) ParseErrorType::Lexical(LexicalErrorType::TabError)
| ParseErrorType::Lexical(LexicalErrorType::TabsAfterSpaces)
) )
} }
} }

View File

@ -15,7 +15,10 @@ struct FStringParser<'a> {
impl<'a> FStringParser<'a> { impl<'a> FStringParser<'a> {
fn new(source: &'a str, str_location: Location) -> Self { fn new(source: &'a str, str_location: Location) -> Self {
Self { chars: source.chars().peekable(), str_location } Self {
chars: source.chars().peekable(),
str_location,
}
} }
#[inline] #[inline]
@ -130,10 +133,10 @@ impl<'a> FStringParser<'a> {
) )
} else { } else {
Box::new(self.expr(ExprKind::Constant { Box::new(self.expr(ExprKind::Constant {
value: spec_expression.clone().into(), value: spec_expression.to_owned().into(),
kind: None, kind: None,
})) }))
}); })
} }
'(' | '{' | '[' => { '(' | '{' | '[' => {
expression.push(ch); expression.push(ch);
@ -248,11 +251,17 @@ impl<'a> FStringParser<'a> {
} }
if !content.is_empty() { if !content.is_empty() {
values.push(self.expr(ExprKind::Constant { value: content.into(), kind: None })); values.push(self.expr(ExprKind::Constant {
value: content.into(),
kind: None,
}))
} }
let s = match values.len() { let s = match values.len() {
0 => self.expr(ExprKind::Constant { value: String::new().into(), kind: None }), 0 => self.expr(ExprKind::Constant {
value: String::new().into(),
kind: None,
}),
1 => values.into_iter().next().unwrap(), 1 => values.into_iter().next().unwrap(),
_ => self.expr(ExprKind::JoinedStr { values }), _ => self.expr(ExprKind::JoinedStr { values }),
}; };
@ -261,14 +270,16 @@ impl<'a> FStringParser<'a> {
} }
fn parse_fstring_expr(source: &str) -> Result<Expr, ParseError> { fn parse_fstring_expr(source: &str) -> Result<Expr, ParseError> {
let fstring_body = format!("({source})"); let fstring_body = format!("({})", source);
parse_expression(&fstring_body) parse_expression(&fstring_body)
} }
/// Parse an fstring from a string, located at a certain position in the sourcecode. /// Parse an fstring from a string, located at a certain position in the sourcecode.
/// In case of errors, we will get the location and the error returned. /// In case of errors, we will get the location and the error returned.
pub fn parse_located_fstring(source: &str, location: Location) -> Result<Expr, FStringError> { pub fn parse_located_fstring(source: &str, location: Location) -> Result<Expr, FStringError> {
FStringParser::new(source, location).parse().map_err(|error| FStringError { error, location }) FStringParser::new(source, location)
.parse()
.map_err(|error| FStringError { error, location })
} }
#[cfg(test)] #[cfg(test)]
@ -282,7 +293,7 @@ mod tests {
#[test] #[test]
fn test_parse_fstring() { fn test_parse_fstring() {
let source = "{a}{ b }{{foo}}"; let source = "{a}{ b }{{foo}}";
let parse_ast = parse_fstring(source).unwrap(); let parse_ast = parse_fstring(&source).unwrap();
insta::assert_debug_snapshot!(parse_ast); insta::assert_debug_snapshot!(parse_ast);
} }
@ -290,7 +301,7 @@ mod tests {
#[test] #[test]
fn test_parse_fstring_nested_spec() { fn test_parse_fstring_nested_spec() {
let source = "{foo:{spec}}"; let source = "{foo:{spec}}";
let parse_ast = parse_fstring(source).unwrap(); let parse_ast = parse_fstring(&source).unwrap();
insta::assert_debug_snapshot!(parse_ast); insta::assert_debug_snapshot!(parse_ast);
} }
@ -298,7 +309,7 @@ mod tests {
#[test] #[test]
fn test_parse_fstring_not_nested_spec() { fn test_parse_fstring_not_nested_spec() {
let source = "{foo:spec}"; let source = "{foo:spec}";
let parse_ast = parse_fstring(source).unwrap(); let parse_ast = parse_fstring(&source).unwrap();
insta::assert_debug_snapshot!(parse_ast); insta::assert_debug_snapshot!(parse_ast);
} }
@ -311,7 +322,7 @@ mod tests {
#[test] #[test]
fn test_fstring_parse_selfdocumenting_base() { fn test_fstring_parse_selfdocumenting_base() {
let src = "{user=}"; let src = "{user=}";
let parse_ast = parse_fstring(src).unwrap(); let parse_ast = parse_fstring(&src).unwrap();
insta::assert_debug_snapshot!(parse_ast); insta::assert_debug_snapshot!(parse_ast);
} }
@ -319,7 +330,7 @@ mod tests {
#[test] #[test]
fn test_fstring_parse_selfdocumenting_base_more() { fn test_fstring_parse_selfdocumenting_base_more() {
let src = "mix {user=} with text and {second=}"; let src = "mix {user=} with text and {second=}";
let parse_ast = parse_fstring(src).unwrap(); let parse_ast = parse_fstring(&src).unwrap();
insta::assert_debug_snapshot!(parse_ast); insta::assert_debug_snapshot!(parse_ast);
} }
@ -327,7 +338,7 @@ mod tests {
#[test] #[test]
fn test_fstring_parse_selfdocumenting_format() { fn test_fstring_parse_selfdocumenting_format() {
let src = "{user=:>10}"; let src = "{user=:>10}";
let parse_ast = parse_fstring(src).unwrap(); let parse_ast = parse_fstring(&src).unwrap();
insta::assert_debug_snapshot!(parse_ast); insta::assert_debug_snapshot!(parse_ast);
} }
@ -360,35 +371,35 @@ mod tests {
#[test] #[test]
fn test_parse_fstring_not_equals() { fn test_parse_fstring_not_equals() {
let source = "{1 != 2}"; let source = "{1 != 2}";
let parse_ast = parse_fstring(source).unwrap(); let parse_ast = parse_fstring(&source).unwrap();
insta::assert_debug_snapshot!(parse_ast); insta::assert_debug_snapshot!(parse_ast);
} }
#[test] #[test]
fn test_parse_fstring_equals() { fn test_parse_fstring_equals() {
let source = "{42 == 42}"; let source = "{42 == 42}";
let parse_ast = parse_fstring(source).unwrap(); let parse_ast = parse_fstring(&source).unwrap();
insta::assert_debug_snapshot!(parse_ast); insta::assert_debug_snapshot!(parse_ast);
} }
#[test] #[test]
fn test_parse_fstring_selfdoc_prec_space() { fn test_parse_fstring_selfdoc_prec_space() {
let source = "{x =}"; let source = "{x =}";
let parse_ast = parse_fstring(source).unwrap(); let parse_ast = parse_fstring(&source).unwrap();
insta::assert_debug_snapshot!(parse_ast); insta::assert_debug_snapshot!(parse_ast);
} }
#[test] #[test]
fn test_parse_fstring_selfdoc_trailing_space() { fn test_parse_fstring_selfdoc_trailing_space() {
let source = "{x= }"; let source = "{x= }";
let parse_ast = parse_fstring(source).unwrap(); let parse_ast = parse_fstring(&source).unwrap();
insta::assert_debug_snapshot!(parse_ast); insta::assert_debug_snapshot!(parse_ast);
} }
#[test] #[test]
fn test_parse_fstring_yield_expr() { fn test_parse_fstring_yield_expr() {
let source = "{yield}"; let source = "{yield}";
let parse_ast = parse_fstring(source).unwrap(); let parse_ast = parse_fstring(&source).unwrap();
insta::assert_debug_snapshot!(parse_ast); insta::assert_debug_snapshot!(parse_ast);
} }
} }

View File

@ -54,32 +54,38 @@ pub fn parse_args(func_args: Vec<FunctionArgument>) -> Result<ArgumentList, Lexi
let mut keyword_names = HashSet::with_capacity_and_hasher(func_args.len(), RandomState::new()); let mut keyword_names = HashSet::with_capacity_and_hasher(func_args.len(), RandomState::new());
for (name, value) in func_args { for (name, value) in func_args {
if let Some((location, name)) = name { match name {
if let Some(keyword_name) = &name { Some((location, name)) => {
if keyword_names.contains(keyword_name) { if let Some(keyword_name) = &name {
if keyword_names.contains(keyword_name) {
return Err(LexicalError {
error: LexicalErrorType::DuplicateKeywordArgumentError,
location,
});
}
keyword_names.insert(keyword_name.clone());
}
keywords.push(ast::Keyword::new(
location,
ast::KeywordData {
arg: name.map(|name| name.into()),
value: Box::new(value),
},
));
}
None => {
// Allow starred args after keyword arguments.
if !keywords.is_empty() && !is_starred(&value) {
return Err(LexicalError { return Err(LexicalError {
error: LexicalErrorType::DuplicateKeywordArgumentError, error: LexicalErrorType::PositionalArgumentError,
location, location: value.location,
}); });
} }
keyword_names.insert(keyword_name.clone()); args.push(value);
} }
keywords.push(ast::Keyword::new(
location,
ast::KeywordData { arg: name.map(String::into), value: Box::new(value) },
));
} else {
// Allow starred args after keyword arguments.
if !keywords.is_empty() && !is_starred(&value) {
return Err(LexicalError {
error: LexicalErrorType::PositionalArgumentError,
location: value.location,
});
}
args.push(value);
} }
} }
Ok(ArgumentList { args, keywords }) Ok(ArgumentList { args, keywords })

View File

@ -3,12 +3,12 @@
//! This means source code is translated into separate tokens. //! This means source code is translated into separate tokens.
pub use super::token::Tok; pub use super::token::Tok;
use crate::ast::{FileName, Location}; use crate::ast::{Location, FileName};
use crate::error::{LexicalError, LexicalErrorType}; use crate::error::{LexicalError, LexicalErrorType};
use std::char; use std::char;
use std::cmp::Ordering; use std::cmp::Ordering;
use std::num::IntErrorKind;
use std::str::FromStr; use std::str::FromStr;
use std::num::IntErrorKind;
use unic_emoji_char::is_emoji_presentation; use unic_emoji_char::is_emoji_presentation;
use unic_ucd_ident::{is_xid_continue, is_xid_start}; use unic_ucd_ident::{is_xid_continue, is_xid_start};
@ -32,14 +32,20 @@ impl IndentationLevel {
if self.spaces <= other.spaces { if self.spaces <= other.spaces {
Ok(Ordering::Less) Ok(Ordering::Less)
} else { } else {
Err(LexicalError { location, error: LexicalErrorType::TabError }) Err(LexicalError {
location,
error: LexicalErrorType::TabError,
})
} }
} }
Ordering::Greater => { Ordering::Greater => {
if self.spaces >= other.spaces { if self.spaces >= other.spaces {
Ok(Ordering::Greater) Ok(Ordering::Greater)
} else { } else {
Err(LexicalError { location, error: LexicalErrorType::TabError }) Err(LexicalError {
location,
error: LexicalErrorType::TabError,
})
} }
} }
Ordering::Equal => Ok(self.spaces.cmp(&other.spaces)), Ordering::Equal => Ok(self.spaces.cmp(&other.spaces)),
@ -57,7 +63,7 @@ pub struct Lexer<T: Iterator<Item = char>> {
chr1: Option<char>, chr1: Option<char>,
chr2: Option<char>, chr2: Option<char>,
location: Location, location: Location,
config_comment_prefix: Option<&'static str>, config_comment_prefix: Option<&'static str>
} }
pub static KEYWORDS: phf::Map<&'static str, Tok> = phf::phf_map! { pub static KEYWORDS: phf::Map<&'static str, Tok> = phf::phf_map! {
@ -130,7 +136,11 @@ where
T: Iterator<Item = char>, T: Iterator<Item = char>,
{ {
pub fn new(source: T) -> Self { pub fn new(source: T) -> Self {
let mut nlh = NewlineHandler { source, chr0: None, chr1: None }; let mut nlh = NewlineHandler {
source,
chr0: None,
chr1: None,
};
nlh.shift(); nlh.shift();
nlh.shift(); nlh.shift();
nlh nlh
@ -159,7 +169,7 @@ where
self.shift(); self.shift();
} else { } else {
// Transform MAC EOL into \n // Transform MAC EOL into \n
self.chr0 = Some('\n'); self.chr0 = Some('\n')
} }
} else { } else {
break; break;
@ -179,13 +189,13 @@ where
chars: input, chars: input,
at_begin_of_line: true, at_begin_of_line: true,
nesting: 0, nesting: 0,
indentation_stack: vec![IndentationLevel::default()], indentation_stack: vec![Default::default()],
pending: Vec::new(), pending: Vec::new(),
chr0: None, chr0: None,
location: start, location: start,
chr1: None, chr1: None,
chr2: None, chr2: None,
config_comment_prefix: Some(" nac3:"), config_comment_prefix: Some(" nac3:")
}; };
lxr.next_char(); lxr.next_char();
lxr.next_char(); lxr.next_char();
@ -207,9 +217,11 @@ where
let mut saw_f = false; let mut saw_f = false;
loop { loop {
// Detect r"", f"", b"" and u"" // Detect r"", f"", b"" and u""
if !(saw_b || saw_u || saw_f) && matches!(self.chr0, Some('b' | 'B')) { if !(saw_b || saw_u || saw_f) && matches!(self.chr0, Some('b') | Some('B')) {
saw_b = true; saw_b = true;
} else if !(saw_b || saw_r || saw_u || saw_f) && matches!(self.chr0, Some('u' | 'U')) { } else if !(saw_b || saw_r || saw_u || saw_f)
&& matches!(self.chr0, Some('u') | Some('U'))
{
saw_u = true; saw_u = true;
} else if !(saw_r || saw_u) && (self.chr0 == Some('r') || self.chr0 == Some('R')) { } else if !(saw_r || saw_u) && (self.chr0 == Some('r') || self.chr0 == Some('R')) {
saw_r = true; saw_r = true;
@ -275,15 +287,15 @@ where
let end_pos = self.get_pos(); let end_pos = self.get_pos();
let value = match i128::from_str_radix(&value_text, radix) { let value = match i128::from_str_radix(&value_text, radix) {
Ok(value) => value, Ok(value) => value,
Err(e) => match e.kind() { Err(e) => {
IntErrorKind::PosOverflow | IntErrorKind::NegOverflow => i128::MAX, match e.kind() {
_ => { IntErrorKind::PosOverflow | IntErrorKind::NegOverflow => i128::MAX,
return Err(LexicalError { _ => return Err(LexicalError {
error: LexicalErrorType::OtherError(format!("{e:?}")), error: LexicalErrorType::OtherError(format!("{:?}", e)),
location: start_pos, location: start_pos,
}) }),
} }
}, }
}; };
Ok((start_pos, Tok::Int { value }, end_pos)) Ok((start_pos, Tok::Int { value }, end_pos))
} }
@ -326,7 +338,14 @@ where
if self.chr0 == Some('j') || self.chr0 == Some('J') { if self.chr0 == Some('j') || self.chr0 == Some('J') {
self.next_char(); self.next_char();
let end_pos = self.get_pos(); let end_pos = self.get_pos();
Ok((start_pos, Tok::Complex { real: 0.0, imag: value }, end_pos)) Ok((
start_pos,
Tok::Complex {
real: 0.0,
imag: value,
},
end_pos,
))
} else { } else {
let end_pos = self.get_pos(); let end_pos = self.get_pos();
Ok((start_pos, Tok::Float { value }, end_pos)) Ok((start_pos, Tok::Float { value }, end_pos))
@ -345,7 +364,7 @@ where
let value = value_text.parse::<i128>().ok(); let value = value_text.parse::<i128>().ok();
let nonzero = match value { let nonzero = match value {
Some(value) => value != 0i128, Some(value) => value != 0i128,
None => true, None => true
}; };
if start_is_zero && nonzero { if start_is_zero && nonzero {
return Err(LexicalError { return Err(LexicalError {
@ -360,7 +379,7 @@ where
/// Consume a sequence of numbers with the given radix, /// Consume a sequence of numbers with the given radix,
/// the digits can be decorated with underscores /// the digits can be decorated with underscores
/// like this: `'1_2_3_4'` == `'1234'` /// like this: '1_2_3_4' == '1234'
fn radix_run(&mut self, radix: u32) -> String { fn radix_run(&mut self, radix: u32) -> String {
let mut value_text = String::new(); let mut value_text = String::new();
@ -393,7 +412,7 @@ where
2 => matches!(c, Some('0'..='1')), 2 => matches!(c, Some('0'..='1')),
8 => matches!(c, Some('0'..='7')), 8 => matches!(c, Some('0'..='7')),
10 => matches!(c, Some('0'..='9')), 10 => matches!(c, Some('0'..='9')),
16 => matches!(c, Some('0'..='9' | 'a'..='f' | 'A'..='F')), 16 => matches!(c, Some('0'..='9') | Some('a'..='f') | Some('A'..='F')),
other => unimplemented!("Radix not implemented: {}", other), other => unimplemented!("Radix not implemented: {}", other),
} }
} }
@ -401,8 +420,8 @@ where
/// Test if we face '[eE][-+]?[0-9]+' /// Test if we face '[eE][-+]?[0-9]+'
fn at_exponent(&self) -> bool { fn at_exponent(&self) -> bool {
match self.chr0 { match self.chr0 {
Some('e' | 'E') => match self.chr1 { Some('e') | Some('E') => match self.chr1 {
Some('+' | '-') => matches!(self.chr2, Some('0'..='9')), Some('+') | Some('-') => matches!(self.chr2, Some('0'..='9')),
Some('0'..='9') => true, Some('0'..='9') => true,
_ => false, _ => false,
}, },
@ -414,17 +433,19 @@ where
fn lex_comment(&mut self) -> Option<Spanned> { fn lex_comment(&mut self) -> Option<Spanned> {
self.next_char(); self.next_char();
// if possibly nac3 pseudocomment, special handling for `# nac3:` // if possibly nac3 pseudocomment, special handling for `# nac3:`
let (mut prefix, mut is_comment) = let (mut prefix, mut is_comment) = self
self.config_comment_prefix.map_or_else(|| ("".chars(), false), |v| (v.chars(), true)); .config_comment_prefix
.map_or_else(|| ("".chars(), false), |v| (v.chars(), true));
// for the correct location of config comment // for the correct location of config comment
let mut start_loc = self.location; let mut start_loc = self.location;
start_loc.go_left(); start_loc.go_left();
loop { loop {
match self.chr0 { match self.chr0 {
Some('\n') | None => return None, Some('\n') => return None,
None => return None,
Some(c) => { Some(c) => {
if let (true, Some(p)) = (is_comment, prefix.next()) { if let (true, Some(p)) = (is_comment, prefix.next()) {
is_comment = is_comment && c == p; is_comment = is_comment && c == p
} else { } else {
// done checking prefix, if is comment then return the spanned // done checking prefix, if is comment then return the spanned
if is_comment { if is_comment {
@ -439,20 +460,22 @@ where
return Some(( return Some((
start_loc, start_loc,
Tok::ConfigComment { content: content.trim().into() }, Tok::ConfigComment { content: content.trim().into() },
self.location, self.location
)); ));
} }
} }
} }
} }
self.next_char(); self.next_char();
} };
} }
fn unicode_literal(&mut self, literal_number: usize) -> Result<char, LexicalError> { fn unicode_literal(&mut self, literal_number: usize) -> Result<char, LexicalError> {
let mut p: u32 = 0u32; let mut p: u32 = 0u32;
let unicode_error = let unicode_error = LexicalError {
LexicalError { error: LexicalErrorType::UnicodeError, location: self.get_pos() }; error: LexicalErrorType::UnicodeError,
location: self.get_pos(),
};
for i in 1..=literal_number { for i in 1..=literal_number {
match self.next_char() { match self.next_char() {
Some(c) => match c.to_digit(16) { Some(c) => match c.to_digit(16) {
@ -463,8 +486,8 @@ where
} }
} }
match p { match p {
0xD800..=0xDFFF => Ok(char::REPLACEMENT_CHARACTER), 0xD800..=0xDFFF => Ok(std::char::REPLACEMENT_CHARACTER),
_ => char::from_u32(p).ok_or(unicode_error), _ => std::char::from_u32(p).ok_or(unicode_error),
} }
} }
@ -473,7 +496,7 @@ where
octet_content.push(first); octet_content.push(first);
while octet_content.len() < 3 { while octet_content.len() < 3 {
if let Some('0'..='7') = self.chr0 { if let Some('0'..='7') = self.chr0 {
octet_content.push(self.next_char().unwrap()); octet_content.push(self.next_char().unwrap())
} else { } else {
break; break;
} }
@ -507,8 +530,10 @@ where
} }
} }
} }
unicode_names2::character(&name) unicode_names2::character(&name).ok_or(LexicalError {
.ok_or(LexicalError { error: LexicalErrorType::UnicodeError, location: start_pos }) error: LexicalErrorType::UnicodeError,
location: start_pos,
})
} }
fn lex_string( fn lex_string(
@ -541,7 +566,7 @@ where
} else if is_raw { } else if is_raw {
string_content.push('\\'); string_content.push('\\');
if let Some(c) = self.next_char() { if let Some(c) = self.next_char() {
string_content.push(c); string_content.push(c)
} else { } else {
return Err(LexicalError { return Err(LexicalError {
error: LexicalErrorType::StringError, error: LexicalErrorType::StringError,
@ -574,7 +599,7 @@ where
Some('u') if !is_bytes => string_content.push(self.unicode_literal(4)?), Some('u') if !is_bytes => string_content.push(self.unicode_literal(4)?),
Some('U') if !is_bytes => string_content.push(self.unicode_literal(8)?), Some('U') if !is_bytes => string_content.push(self.unicode_literal(8)?),
Some('N') if !is_bytes => { Some('N') if !is_bytes => {
string_content.push(self.parse_unicode_name()?); string_content.push(self.parse_unicode_name()?)
} }
Some(c) => { Some(c) => {
string_content.push('\\'); string_content.push('\\');
@ -625,15 +650,20 @@ where
let end_pos = self.get_pos(); let end_pos = self.get_pos();
let tok = if is_bytes { let tok = if is_bytes {
Tok::Bytes { value: string_content.chars().map(|c| c as u8).collect() } Tok::Bytes {
value: string_content.chars().map(|c| c as u8).collect(),
}
} else { } else {
Tok::String { value: string_content, is_fstring } Tok::String {
value: string_content,
is_fstring,
}
}; };
Ok((start_pos, tok, end_pos)) Ok((start_pos, tok, end_pos))
} }
fn is_identifier_start(c: char) -> bool { fn is_identifier_start(&self, c: char) -> bool {
match c { match c {
'_' | 'a'..='z' | 'A'..='Z' => true, '_' | 'a'..='z' | 'A'..='Z' => true,
'+' | '-' | '*' | '/' | '=' | ' ' | '<' | '>' => false, '+' | '-' | '*' | '/' | '=' | ' ' | '<' | '>' => false,
@ -805,14 +835,18 @@ where
// Check if we have some character: // Check if we have some character:
if let Some(c) = self.chr0 { if let Some(c) = self.chr0 {
// First check identifier: // First check identifier:
if Self::is_identifier_start(c) { if self.is_identifier_start(c) {
let identifier = self.lex_identifier()?; let identifier = self.lex_identifier()?;
self.emit(identifier); self.emit(identifier);
} else if is_emoji_presentation(c) { } else if is_emoji_presentation(c) {
let tok_start = self.get_pos(); let tok_start = self.get_pos();
self.next_char(); self.next_char();
let tok_end = self.get_pos(); let tok_end = self.get_pos();
self.emit((tok_start, Tok::Name { name: c.to_string().into() }, tok_end)); self.emit((
tok_start,
Tok::Name { name: c.to_string().into() },
tok_end,
));
} else { } else {
self.consume_character(c)?; self.consume_character(c)?;
} }
@ -865,13 +899,16 @@ where
'=' => { '=' => {
let tok_start = self.get_pos(); let tok_start = self.get_pos();
self.next_char(); self.next_char();
if let Some('=') = self.chr0 { match self.chr0 {
self.next_char(); Some('=') => {
let tok_end = self.get_pos(); self.next_char();
self.emit((tok_start, Tok::EqEqual, tok_end)); let tok_end = self.get_pos();
} else { self.emit((tok_start, Tok::EqEqual, tok_end));
let tok_end = self.get_pos(); }
self.emit((tok_start, Tok::Equal, tok_end)); _ => {
let tok_end = self.get_pos();
self.emit((tok_start, Tok::Equal, tok_end));
}
} }
} }
'+' => { '+' => {
@ -897,13 +934,16 @@ where
} }
Some('*') => { Some('*') => {
self.next_char(); self.next_char();
if let Some('=') = self.chr0 { match self.chr0 {
self.next_char(); Some('=') => {
let tok_end = self.get_pos(); self.next_char();
self.emit((tok_start, Tok::DoubleStarEqual, tok_end)); let tok_end = self.get_pos();
} else { self.emit((tok_start, Tok::DoubleStarEqual, tok_end));
let tok_end = self.get_pos(); }
self.emit((tok_start, Tok::DoubleStar, tok_end)); _ => {
let tok_end = self.get_pos();
self.emit((tok_start, Tok::DoubleStar, tok_end));
}
} }
} }
_ => { _ => {
@ -923,13 +963,16 @@ where
} }
Some('/') => { Some('/') => {
self.next_char(); self.next_char();
if let Some('=') = self.chr0 { match self.chr0 {
self.next_char(); Some('=') => {
let tok_end = self.get_pos(); self.next_char();
self.emit((tok_start, Tok::DoubleSlashEqual, tok_end)); let tok_end = self.get_pos();
} else { self.emit((tok_start, Tok::DoubleSlashEqual, tok_end));
let tok_end = self.get_pos(); }
self.emit((tok_start, Tok::DoubleSlash, tok_end)); _ => {
let tok_end = self.get_pos();
self.emit((tok_start, Tok::DoubleSlash, tok_end));
}
} }
} }
_ => { _ => {
@ -1098,13 +1141,16 @@ where
match self.chr0 { match self.chr0 {
Some('<') => { Some('<') => {
self.next_char(); self.next_char();
if let Some('=') = self.chr0 { match self.chr0 {
self.next_char(); Some('=') => {
let tok_end = self.get_pos(); self.next_char();
self.emit((tok_start, Tok::LeftShiftEqual, tok_end)); let tok_end = self.get_pos();
} else { self.emit((tok_start, Tok::LeftShiftEqual, tok_end));
let tok_end = self.get_pos(); }
self.emit((tok_start, Tok::LeftShift, tok_end)); _ => {
let tok_end = self.get_pos();
self.emit((tok_start, Tok::LeftShift, tok_end));
}
} }
} }
Some('=') => { Some('=') => {
@ -1124,13 +1170,16 @@ where
match self.chr0 { match self.chr0 {
Some('>') => { Some('>') => {
self.next_char(); self.next_char();
if let Some('=') = self.chr0 { match self.chr0 {
self.next_char(); Some('=') => {
let tok_end = self.get_pos(); self.next_char();
self.emit((tok_start, Tok::RightShiftEqual, tok_end)); let tok_end = self.get_pos();
} else { self.emit((tok_start, Tok::RightShiftEqual, tok_end));
let tok_end = self.get_pos(); }
self.emit((tok_start, Tok::RightShift, tok_end)); _ => {
let tok_end = self.get_pos();
self.emit((tok_start, Tok::RightShift, tok_end));
}
} }
} }
Some('=') => { Some('=') => {
@ -1284,14 +1333,13 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{make_tokenizer, NewlineHandler, Tok}; use super::{make_tokenizer, NewlineHandler, Tok};
use nac3ast::FileName;
const WINDOWS_EOL: &str = "\r\n"; const WINDOWS_EOL: &str = "\r\n";
const MAC_EOL: &str = "\r"; const MAC_EOL: &str = "\r";
const UNIX_EOL: &str = "\n"; const UNIX_EOL: &str = "\n";
pub fn lex_source(source: &str) -> Vec<Tok> { pub fn lex_source(source: &str) -> Vec<Tok> {
let lexer = make_tokenizer(source, FileName::default()); let lexer = make_tokenizer(source, Default::default());
lexer.map(|x| x.unwrap().1).collect() lexer.map(|x| x.unwrap().1).collect()
} }
@ -1371,7 +1419,7 @@ class Foo(A, B):
Dedent, Dedent,
Dedent Dedent
] ]
); )
} }
#[test] #[test]
@ -1391,8 +1439,14 @@ class Foo(A, B):
assert_eq!( assert_eq!(
tokens, tokens,
vec![ vec![
Tok::String { value: "\\\\".to_owned(), is_fstring: false }, Tok::String {
Tok::String { value: "\\".to_owned(), is_fstring: false }, value: "\\\\".to_owned(),
is_fstring: false,
},
Tok::String {
value: "\\".to_owned(),
is_fstring: false,
},
Tok::Newline, Tok::Newline,
] ]
); );
@ -1405,13 +1459,27 @@ class Foo(A, B):
assert_eq!( assert_eq!(
tokens, tokens,
vec![ vec![
Tok::Int { value: 47i128 }, Tok::Int {
Tok::Int { value: 13i128 }, value: 47i128,
Tok::Int { value: 0i128 }, },
Tok::Int { value: 123i128 }, Tok::Int {
value: 13i128,
},
Tok::Int {
value: 0i128,
},
Tok::Int {
value: 123i128,
},
Tok::Float { value: 0.2 }, Tok::Float { value: 0.2 },
Tok::Complex { real: 0.0, imag: 2.0 }, Tok::Complex {
Tok::Complex { real: 0.0, imag: 2.2 }, real: 0.0,
imag: 2.0,
},
Tok::Complex {
real: 0.0,
imag: 2.2,
},
Tok::Newline, Tok::Newline,
] ]
); );
@ -1471,13 +1539,21 @@ class Foo(A, B):
assert_eq!( assert_eq!(
tokens, tokens,
vec![ vec![
Tok::Name { name: String::from("avariable").into() }, Tok::Name {
name: String::from("avariable").into(),
},
Tok::Equal, Tok::Equal,
Tok::Int { value: 99i128 }, Tok::Int {
value: 99i128
},
Tok::Plus, Tok::Plus,
Tok::Int { value: 2i128 }, Tok::Int {
value: 2i128
},
Tok::Minus, Tok::Minus,
Tok::Int { value: 0i128 }, Tok::Int {
value: 0i128
},
Tok::Newline, Tok::Newline,
] ]
); );
@ -1664,15 +1740,42 @@ class Foo(A, B):
assert_eq!( assert_eq!(
tokens, tokens,
vec![ vec![
Tok::String { value: String::from("double"), is_fstring: false }, Tok::String {
Tok::String { value: String::from("single"), is_fstring: false }, value: String::from("double"),
Tok::String { value: String::from("can't"), is_fstring: false }, is_fstring: false,
Tok::String { value: String::from("\\\""), is_fstring: false }, },
Tok::String { value: String::from("\t\r\n"), is_fstring: false }, Tok::String {
Tok::String { value: String::from("\\g"), is_fstring: false }, value: String::from("single"),
Tok::String { value: String::from("raw\\'"), is_fstring: false }, is_fstring: false,
Tok::String { value: String::from("Đ"), is_fstring: false }, },
Tok::String { value: String::from("\u{80}\u{0}a"), is_fstring: false }, Tok::String {
value: String::from("can't"),
is_fstring: false,
},
Tok::String {
value: String::from("\\\""),
is_fstring: false,
},
Tok::String {
value: String::from("\t\r\n"),
is_fstring: false,
},
Tok::String {
value: String::from("\\g"),
is_fstring: false,
},
Tok::String {
value: String::from("raw\\'"),
is_fstring: false,
},
Tok::String {
value: String::from("Đ"),
is_fstring: false,
},
Tok::String {
value: String::from("\u{80}\u{0}a"),
is_fstring: false,
},
Tok::Newline, Tok::Newline,
] ]
); );
@ -1727,7 +1830,7 @@ class Foo(A, B):
#[test] #[test]
fn test_escape_char_in_byte_literal() { fn test_escape_char_in_byte_literal() {
// backslash does not escape // backslash does not escape
let source = r#"b"omkmok\Xaa""#; let source = r##"b"omkmok\Xaa""##;
let tokens = lex_source(source); let tokens = lex_source(source);
let res = vec![111, 109, 107, 109, 111, 107, 92, 88, 97, 97]; let res = vec![111, 109, 107, 109, 111, 107, 92, 88, 97, 97];
assert_eq!(tokens, vec![Tok::Bytes { value: res }, Tok::Newline]); assert_eq!(tokens, vec![Tok::Bytes { value: res }, Tok::Newline]);
@ -1737,17 +1840,41 @@ class Foo(A, B):
fn test_raw_byte_literal() { fn test_raw_byte_literal() {
let source = r"rb'\x1z'"; let source = r"rb'\x1z'";
let tokens = lex_source(source); let tokens = lex_source(source);
assert_eq!(tokens, vec![Tok::Bytes { value: b"\\x1z".to_vec() }, Tok::Newline]); assert_eq!(
tokens,
vec![
Tok::Bytes {
value: b"\\x1z".to_vec()
},
Tok::Newline
]
);
let source = r"rb'\\'"; let source = r"rb'\\'";
let tokens = lex_source(source); let tokens = lex_source(source);
assert_eq!(tokens, vec![Tok::Bytes { value: b"\\\\".to_vec() }, Tok::Newline]); assert_eq!(
tokens,
vec![
Tok::Bytes {
value: b"\\\\".to_vec()
},
Tok::Newline
]
)
} }
#[test] #[test]
fn test_escape_octet() { fn test_escape_octet() {
let source = r"b'\43a\4\1234'"; let source = r##"b'\43a\4\1234'"##;
let tokens = lex_source(source); let tokens = lex_source(source);
assert_eq!(tokens, vec![Tok::Bytes { value: b"#a\x04S4".to_vec() }, Tok::Newline]); assert_eq!(
tokens,
vec![
Tok::Bytes {
value: b"#a\x04S4".to_vec()
},
Tok::Newline
]
)
} }
#[test] #[test]
@ -1756,7 +1883,13 @@ class Foo(A, B):
let tokens = lex_source(source); let tokens = lex_source(source);
assert_eq!( assert_eq!(
tokens, tokens,
vec![Tok::String { value: "\u{2002}".to_owned(), is_fstring: false }, Tok::Newline] vec![
); Tok::String {
value: "\u{2002}".to_owned(),
is_fstring: false,
},
Tok::Newline
]
)
} }
} }

View File

@ -15,24 +15,6 @@
//! //!
//! ``` //! ```
#![deny(
future_incompatible,
let_underscore,
nonstandard_style,
rust_2024_compatibility,
clippy::all
)]
#![warn(clippy::pedantic)]
#![allow(
clippy::enum_glob_use,
clippy::fn_params_excessive_bools,
clippy::missing_errors_doc,
clippy::missing_panics_doc,
clippy::module_name_repetitions,
clippy::too_many_lines,
clippy::wildcard_imports
)]
#[macro_use] #[macro_use]
extern crate log; extern crate log;
use lalrpop_util::lalrpop_mod; use lalrpop_util::lalrpop_mod;
@ -45,16 +27,9 @@ pub mod lexer;
pub mod mode; pub mod mode;
pub mod parser; pub mod parser;
lalrpop_mod!( lalrpop_mod!(
#[allow( #[allow(clippy::all)]
future_incompatible, #[allow(unused)]
let_underscore,
nonstandard_style,
rust_2024_compatibility,
unused,
clippy::all,
clippy::pedantic
)]
python python
); );
pub mod config_comment_helper;
pub mod token; pub mod token;
pub mod config_comment_helper;

View File

@ -5,7 +5,6 @@
//! parse a whole program, a single statement, or a single //! parse a whole program, a single statement, or a single
//! expression. //! expression.
use nac3ast::Location;
use std::iter; use std::iter;
use crate::ast::{self, FileName}; use crate::ast::{self, FileName};
@ -64,7 +63,7 @@ pub fn parse_program(source: &str, file: FileName) -> Result<ast::Suite, ParseEr
/// ///
/// ``` /// ```
pub fn parse_expression(source: &str) -> Result<ast::Expr, ParseError> { pub fn parse_expression(source: &str) -> Result<ast::Expr, ParseError> {
parse(source, Mode::Expression, FileName::default()).map(|top| match top { parse(source, Mode::Expression, Default::default()).map(|top| match top {
ast::Mod::Expression { body } => *body, ast::Mod::Expression { body } => *body,
_ => unreachable!(), _ => unreachable!(),
}) })
@ -73,10 +72,12 @@ pub fn parse_expression(source: &str) -> Result<ast::Expr, ParseError> {
// Parse a given source code // Parse a given source code
pub fn parse(source: &str, mode: Mode, file: FileName) -> Result<ast::Mod, ParseError> { pub fn parse(source: &str, mode: Mode, file: FileName) -> Result<ast::Mod, ParseError> {
let lxr = lexer::make_tokenizer(source, file); let lxr = lexer::make_tokenizer(source, file);
let marker_token = (Location::default(), mode.to_marker(), Location::default()); let marker_token = (Default::default(), mode.to_marker(), Default::default());
let tokenizer = iter::once(Ok(marker_token)).chain(lxr); let tokenizer = iter::once(Ok(marker_token)).chain(lxr);
python::TopParser::new().parse(tokenizer).map_err(ParseError::from) python::TopParser::new()
.parse(tokenizer)
.map_err(ParseError::from)
} }
#[cfg(test)] #[cfg(test)]
@ -85,42 +86,42 @@ mod tests {
#[test] #[test]
fn test_parse_empty() { fn test_parse_empty() {
let parse_ast = parse_program("", FileName::default()).unwrap(); let parse_ast = parse_program("", Default::default()).unwrap();
insta::assert_debug_snapshot!(parse_ast); insta::assert_debug_snapshot!(parse_ast);
} }
#[test] #[test]
fn test_parse_print_hello() { fn test_parse_print_hello() {
let source = String::from("print('Hello world')"); let source = String::from("print('Hello world')");
let parse_ast = parse_program(&source, FileName::default()).unwrap(); let parse_ast = parse_program(&source, Default::default()).unwrap();
insta::assert_debug_snapshot!(parse_ast); insta::assert_debug_snapshot!(parse_ast);
} }
#[test] #[test]
fn test_parse_print_2() { fn test_parse_print_2() {
let source = String::from("print('Hello world', 2)"); let source = String::from("print('Hello world', 2)");
let parse_ast = parse_program(&source, FileName::default()).unwrap(); let parse_ast = parse_program(&source, Default::default()).unwrap();
insta::assert_debug_snapshot!(parse_ast); insta::assert_debug_snapshot!(parse_ast);
} }
#[test] #[test]
fn test_parse_kwargs() { fn test_parse_kwargs() {
let source = String::from("my_func('positional', keyword=2)"); let source = String::from("my_func('positional', keyword=2)");
let parse_ast = parse_program(&source, FileName::default()).unwrap(); let parse_ast = parse_program(&source, Default::default()).unwrap();
insta::assert_debug_snapshot!(parse_ast); insta::assert_debug_snapshot!(parse_ast);
} }
#[test] #[test]
fn test_parse_if_elif_else() { fn test_parse_if_elif_else() {
let source = String::from("if 1: 10\nelif 2: 20\nelse: 30"); let source = String::from("if 1: 10\nelif 2: 20\nelse: 30");
let parse_ast = parse_program(&source, FileName::default()).unwrap(); let parse_ast = parse_program(&source, Default::default()).unwrap();
insta::assert_debug_snapshot!(parse_ast); insta::assert_debug_snapshot!(parse_ast);
} }
#[test] #[test]
fn test_parse_lambda() { fn test_parse_lambda() {
let source = "lambda x, y: x * y"; // lambda(x, y): x * y"; let source = "lambda x, y: x * y"; // lambda(x, y): x * y";
let parse_ast = parse_program(source, FileName::default()).unwrap(); let parse_ast = parse_program(source, Default::default()).unwrap();
insta::assert_debug_snapshot!(parse_ast); insta::assert_debug_snapshot!(parse_ast);
} }
@ -128,7 +129,7 @@ mod tests {
fn test_parse_tuples() { fn test_parse_tuples() {
let source = "a, b = 4, 5"; let source = "a, b = 4, 5";
insta::assert_debug_snapshot!(parse_program(source, FileName::default()).unwrap()); insta::assert_debug_snapshot!(parse_program(source, Default::default()).unwrap());
} }
#[test] #[test]
@ -139,7 +140,7 @@ class Foo(A, B):
pass pass
def method_with_default(self, arg='default'): def method_with_default(self, arg='default'):
pass"; pass";
insta::assert_debug_snapshot!(parse_program(source, FileName::default()).unwrap()); insta::assert_debug_snapshot!(parse_program(source, Default::default()).unwrap());
} }
#[test] #[test]
@ -182,7 +183,7 @@ while i < 2: # nac3: 4
# nac3: if1 # nac3: if1
if 1: # nac3: if2 if 1: # nac3: if2
3"; 3";
insta::assert_debug_snapshot!(parse_program(source, FileName::default()).unwrap()); insta::assert_debug_snapshot!(parse_program(source, Default::default()).unwrap());
} }
#[test] #[test]
@ -195,7 +196,7 @@ while test: # nac3: while3
# nac3: simple assign0 # nac3: simple assign0
a = 3 # nac3: simple assign1 a = 3 # nac3: simple assign1
"; ";
insta::assert_debug_snapshot!(parse_program(source, FileName::default()).unwrap()); insta::assert_debug_snapshot!(parse_program(source, Default::default()).unwrap());
} }
#[test] #[test]
@ -214,7 +215,7 @@ if a: # nac3: small2
for i in a: # nac3: for1 for i in a: # nac3: for1
pass pass
"; ";
insta::assert_debug_snapshot!(parse_program(source, FileName::default()).unwrap()); insta::assert_debug_snapshot!(parse_program(source, Default::default()).unwrap());
} }
#[test] #[test]
@ -223,6 +224,6 @@ for i in a: # nac3: for1
if a: # nac3: something if a: # nac3: something
a = 3 a = 3
"; ";
assert!(parse_program(source, FileName::default()).is_err()); assert!(parse_program(source, Default::default()).is_err());
} }
} }

View File

@ -1,7 +1,7 @@
//! Different token definitions. //! Different token definitions.
//! Loosely based on token.h from CPython source: //! Loosely based on token.h from CPython source:
use crate::ast;
use std::fmt::{self, Write}; use std::fmt::{self, Write};
use crate::ast;
/// Python source code can be tokenized in a sequence of these tokens. /// Python source code can be tokenized in a sequence of these tokens.
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
@ -111,23 +111,15 @@ impl fmt::Display for Tok {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use Tok::*; use Tok::*;
match self { match self {
Name { name } => { Name { name } => write!(f, "'{}'", ast::get_str_from_ref(&ast::get_str_ref_lock(), *name)),
write!(f, "'{}'", ast::get_str_from_ref(&ast::get_str_ref_lock(), *name)) Int { value } => if *value != i128::MAX { write!(f, "'{}'", value) } else { write!(f, "'#OFL#'") },
} Float { value } => write!(f, "'{}'", value),
Int { value } => { Complex { real, imag } => write!(f, "{}j{}", real, imag),
if *value == i128::MAX {
write!(f, "'#OFL#'")
} else {
write!(f, "'{value}'")
}
}
Float { value } => write!(f, "'{value}'"),
Complex { real, imag } => write!(f, "{real}j{imag}"),
String { value, is_fstring } => { String { value, is_fstring } => {
if *is_fstring { if *is_fstring {
write!(f, "f")?; write!(f, "f")?
} }
write!(f, "{value:?}") write!(f, "{:?}", value)
} }
Bytes { value } => { Bytes { value } => {
write!(f, "b\"")?; write!(f, "b\"")?;
@ -137,16 +129,12 @@ impl fmt::Display for Tok {
10 => f.write_str("\\n")?, 10 => f.write_str("\\n")?,
13 => f.write_str("\\r")?, 13 => f.write_str("\\r")?,
32..=126 => f.write_char(*i as char)?, 32..=126 => f.write_char(*i as char)?,
_ => write!(f, "\\x{i:02x}")?, _ => write!(f, "\\x{:02x}", i)?,
} }
} }
f.write_str("\"") f.write_str("\"")
} }
ConfigComment { content } => write!( ConfigComment { content } => write!(f, "ConfigComment: '{}'", ast::get_str_from_ref(&ast::get_str_ref_lock(), *content)),
f,
"ConfigComment: '{}'",
ast::get_str_from_ref(&ast::get_str_ref_lock(), *content)
),
Newline => f.write_str("Newline"), Newline => f.write_str("Newline"),
Indent => f.write_str("Indent"), Indent => f.write_str("Indent"),
Dedent => f.write_str("Dedent"), Dedent => f.write_str("Dedent"),

View File

@ -2,18 +2,14 @@
name = "nac3standalone" name = "nac3standalone"
version = "0.1.0" version = "0.1.0"
authors = ["M-Labs"] authors = ["M-Labs"]
edition = "2021" edition = "2018"
[dependencies] [dependencies]
parking_lot = "0.12" parking_lot = "0.12"
nac3parser = { path = "../nac3parser" } nac3parser = { path = "../nac3parser" }
nac3core = { path = "../nac3core" } nac3core = { path = "../nac3core" }
[dependencies.clap]
version = "4.5"
features = ["derive"]
[dependencies.inkwell] [dependencies.inkwell]
version = "0.4" git = "https://github.com/TheDan64/inkwell.git"
default-features = false default-features = false
features = ["llvm14-0", "target-x86", "target-arm", "target-riscv", "no-libffi-linking"] features = ["llvm14-0", "target-x86", "target-arm", "target-riscv", "no-libffi-linking"]

View File

@ -1,4 +0,0 @@
*.bc
*.ll
*.o
/demo

View File

@ -1,25 +0,0 @@
#!/usr/bin/env bash
set -e
if [ -z "$1" ]; then
echo "Requires at least one argument"
exit 1
fi
declare -a nac3args
while [ $# -gt 1 ]; do
nac3args+=("$1")
shift
done
demo="$1"
echo -n "Checking $demo... "
./interpret_demo.py "$demo" > interpreted.log
./run_demo.sh --out run.log "${nac3args[@]}" "$demo"
./run_demo.sh --lli --out run_lli.log "${nac3args[@]}" "$demo"
diff -Nau interpreted.log run.log
diff -Nau interpreted.log run_lli.log
echo "ok"
rm -f interpreted.log run.log run_lli.log

View File

@ -4,8 +4,12 @@ set -e
count=0 count=0
for demo in src/*.py; do for demo in src/*.py; do
./check_demo.sh "$@" "$demo" echo -n "checking $demo... "
((count += 1)) ./interpret_demo.py $demo > interpreted.log
./run_demo.sh $demo > run.log
diff -Nau interpreted.log run.log
echo "ok"
let "count+=1"
done done
echo "Ran $count demo checks - PASSED" echo "Ran $count demo checks - PASSED"

View File

@ -1,122 +0,0 @@
#include <inttypes.h>
#include <math.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define usize size_t
double dbl_nan(void) {
return NAN;
}
double dbl_inf(void) {
return INFINITY;
}
void output_bool(bool x) {
puts(x ? "True" : "False");
}
void output_int32(int32_t x) {
printf("%"PRId32"\n", x);
}
void output_int64(int64_t x) {
printf("%"PRId64"\n", x);
}
void output_uint32(uint32_t x) {
printf("%"PRIu32"\n", x);
}
void output_uint64(uint64_t x) {
printf("%"PRIu64"\n", x);
}
void output_float64(double x) {
if (isnan(x)) {
puts("nan");
} else {
printf("%f\n", x);
}
}
void output_range(int32_t range[3]) {
printf("range(");
printf("%d, %d", range[0], range[1]);
if (range[2] != 1) {
printf(", %d", range[2]);
}
puts(")");
}
void output_asciiart(int32_t x) {
static const char *chars = " .,-:;i+hHM$*#@ ";
if (x < 0) {
putchar('\n');
} else {
putchar(chars[x]);
}
}
struct cslice {
void *data;
usize len;
};
void output_int32_list(struct cslice *slice) {
const int32_t *data = (int32_t *) slice->data;
putchar('[');
for (usize i = 0; i < slice->len; ++i) {
if (i == slice->len - 1) {
printf("%d", data[i]);
} else {
printf("%d, ", data[i]);
}
}
putchar(']');
putchar('\n');
}
void output_str(struct cslice *slice) {
const char *data = (const char *) slice->data;
for (usize i = 0; i < slice->len; ++i) {
putchar(data[i]);
}
}
void output_strln(struct cslice *slice) {
output_str(slice);
putchar('\n');
}
uint64_t dbg_stack_address(__attribute__((unused)) struct cslice *slice) {
int i;
void *ptr = (void *) &i;
return (uintptr_t) ptr;
}
uint32_t __nac3_personality(uint32_t state, uint32_t exception_object, uint32_t context) {
printf("__nac3_personality(state: %u, exception_object: %u, context: %u)\n", state, exception_object, context);
exit(101);
__builtin_unreachable();
}
uint32_t __nac3_raise(uint32_t state, uint32_t exception_object, uint32_t context) {
printf("__nac3_raise(state: %u, exception_object: %u, context: %u)\n", state, exception_object, context);
exit(101);
__builtin_unreachable();
}
void __nac3_end_catch(void) {}
extern int32_t run(void);
int main(void) {
run();
}

View File

@ -0,0 +1,90 @@
mod cslice {
// copied from https://github.com/dherman/cslice
use std::marker::PhantomData;
use std::slice;
#[repr(C)]
#[derive(Clone, Copy)]
pub struct CSlice<'a, T> {
base: *const T,
len: usize,
marker: PhantomData<&'a ()>,
}
impl<'a, T> AsRef<[T]> for CSlice<'a, T> {
fn as_ref(&self) -> &[T] {
unsafe { slice::from_raw_parts(self.base, self.len) }
}
}
}
#[no_mangle]
pub extern "C" fn output_int32(x: i32) {
println!("{}", x);
}
#[no_mangle]
pub extern "C" fn output_int64(x: i64) {
println!("{}", x);
}
#[no_mangle]
pub extern "C" fn output_uint32(x: u32) {
println!("{}", x);
}
#[no_mangle]
pub extern "C" fn output_uint64(x: u64) {
println!("{}", x);
}
#[no_mangle]
pub extern "C" fn output_float64(x: f64) {
// debug output to preserve the digits after the decimal points
// to match python `print` function
println!("{:?}", x);
}
#[no_mangle]
pub extern "C" fn output_asciiart(x: i32) {
let chars = " .,-:;i+hHM$*#@ ";
if x < 0 {
println!("");
} else {
print!("{}", chars.chars().nth(x as usize).unwrap());
}
}
#[no_mangle]
pub extern "C" fn output_int32_list(x: &cslice::CSlice<i32>) {
print!("[");
let mut it = x.as_ref().iter().peekable();
while let Some(e) = it.next() {
if it.peek().is_none() {
print!("{}", e);
} else {
print!("{}, ", e);
}
}
println!("]");
}
#[no_mangle]
pub extern "C" fn __nac3_personality(_state: u32, _exception_object: u32, _context: u32) -> u32 {
unimplemented!();
}
#[no_mangle]
pub extern "C" fn __nac3_raise(_state: u32, _exception_object: u32, _context: u32) -> u32 {
unimplemented!();
}
extern "C" {
fn run() -> i32;
}
fn main() {
unsafe {
run();
}
}

View File

@ -3,14 +3,10 @@
import sys import sys
import importlib.util import importlib.util
import importlib.machinery import importlib.machinery
import math
import numpy as np
import numpy.typing as npt
import pathlib import pathlib
from numpy import int32, int64, uint32, uint64 from numpy import int32, int64, uint32, uint64
from scipy import special from typing import TypeVar, Generic
from typing import TypeVar, Generic, Literal, Union
T = TypeVar('T') T = TypeVar('T')
class Option(Generic[T]): class Option(Generic[T]):
@ -45,99 +41,26 @@ def Some(v: T) -> Option[T]:
none = Option(None) none = Option(None)
class _ConstGenericMarker:
pass
def ConstGeneric(name, constraint):
return TypeVar(name, _ConstGenericMarker, constraint)
N = TypeVar("N", bound=np.uint64)
class _NDArrayDummy(Generic[T, N]):
pass
# https://stackoverflow.com/questions/67803260/how-to-create-a-type-alias-with-a-throw-away-generic
NDArray = Union[npt.NDArray[T], _NDArrayDummy[T, N]]
def _bool(x):
if isinstance(x, np.ndarray):
return np.bool_(x)
else:
return bool(x)
def _float(x):
if isinstance(x, np.ndarray):
return np.float_(x)
else:
return float(x)
def round_away_zero(x):
if isinstance(x, np.ndarray):
return np.vectorize(round_away_zero)(x)
else:
if x >= 0.0:
return math.floor(x + 0.5)
else:
return math.ceil(x - 0.5)
def _floor(x):
if isinstance(x, np.ndarray):
return np.vectorize(_floor)(x)
else:
return math.floor(x)
def _ceil(x):
if isinstance(x, np.ndarray):
return np.vectorize(_ceil)(x)
else:
return math.ceil(x)
def patch(module): def patch(module):
def dbl_nan():
return np.nan
def dbl_inf():
return np.inf
def output_asciiart(x): def output_asciiart(x):
if x < 0: if x < 0:
sys.stdout.write("\n") sys.stdout.write("\n")
else: else:
sys.stdout.write(" .,-:;i+hHM$*#@ "[x]) sys.stdout.write(" .,-:;i+hHM$*#@ "[x])
def output_float(x):
print("%f" % x)
def output_strln(x):
print(x, end='')
def dbg_stack_address(_):
return 0
def extern(fun): def extern(fun):
name = fun.__name__ name = fun.__name__
if name == "dbl_nan": if name == "output_asciiart":
return dbl_nan
elif name == "dbl_inf":
return dbl_inf
elif name == "output_asciiart":
return output_asciiart return output_asciiart
elif name == "output_float64":
return output_float
elif name == "output_str":
return output_strln
elif name in { elif name in {
"output_bool",
"output_int32", "output_int32",
"output_int64", "output_int64",
"output_int32_list", "output_int32_list",
"output_uint32", "output_uint32",
"output_uint64", "output_uint64",
"output_strln", "output_float64"
"output_range",
}: }:
return print return print
elif name == "dbg_stack_address":
return dbg_stack_address
else: else:
raise NotImplementedError raise NotImplementedError
@ -145,93 +68,13 @@ def patch(module):
module.int64 = int64 module.int64 = int64
module.uint32 = uint32 module.uint32 = uint32
module.uint64 = uint64 module.uint64 = uint64
module.bool = _bool
module.float = _float
module.TypeVar = TypeVar module.TypeVar = TypeVar
module.ConstGeneric = ConstGeneric
module.Generic = Generic module.Generic = Generic
module.Literal = Literal
module.extern = extern module.extern = extern
module.Option = Option module.Option = Option
module.Some = Some module.Some = Some
module.none = none module.none = none
# Builtin Math functions
module.round = round_away_zero
module.round64 = round_away_zero
module.np_round = np.round
module.floor = _floor
module.floor64 = _floor
module.np_floor = np.floor
module.ceil = _ceil
module.ceil64 = _ceil
module.np_ceil = np.ceil
# NumPy ndarray functions
module.ndarray = NDArray
module.np_ndarray = np.ndarray
module.np_empty = np.empty
module.np_zeros = np.zeros
module.np_ones = np.ones
module.np_full = np.full
module.np_eye = np.eye
module.np_identity = np.identity
module.np_array = np.array
# NumPy Math functions
module.np_isnan = np.isnan
module.np_isinf = np.isinf
module.np_min = np.min
module.np_minimum = np.minimum
module.np_max = np.max
module.np_maximum = np.maximum
module.np_sin = np.sin
module.np_cos = np.cos
module.np_exp = np.exp
module.np_exp2 = np.exp2
module.np_log = np.log
module.np_log10 = np.log10
module.np_log2 = np.log2
module.np_fabs = np.fabs
module.np_trunc = np.trunc
module.np_sqrt = np.sqrt
module.np_rint = np.rint
module.np_tan = np.tan
module.np_arcsin = np.arcsin
module.np_arccos = np.arccos
module.np_arctan = np.arctan
module.np_sinh = np.sinh
module.np_cosh = np.cosh
module.np_tanh = np.tanh
module.np_arcsinh = np.arcsinh
module.np_arccosh = np.arccosh
module.np_arctanh = np.arctanh
module.np_expm1 = np.expm1
module.np_cbrt = np.cbrt
module.np_arctan2 = np.arctan2
module.np_copysign = np.copysign
module.np_fmax = np.fmax
module.np_fmin = np.fmin
module.np_ldexp = np.ldexp
module.np_hypot = np.hypot
module.np_nextafter = np.nextafter
# SciPy Math Functions
module.sp_spec_erf = special.erf
module.sp_spec_erfc = special.erfc
module.sp_spec_gamma = special.gamma
module.sp_spec_gammaln = special.gammaln
module.sp_spec_j0 = special.j0
module.sp_spec_j1 = special.j1
# NumPy NDArray Functions
module.np_ndarray = np.ndarray
module.np_empty = np.empty
module.np_zeros = np.zeros
module.np_ones = np.ones
module.np_full = np.full
module.np_eye = np.eye
module.np_identity = np.identity
def file_import(filename, prefix="file_import_"): def file_import(filename, prefix="file_import_"):
filename = pathlib.Path(filename) filename = pathlib.Path(filename)

View File

@ -7,72 +7,14 @@ if [ -z "$1" ]; then
exit 1 exit 1
fi fi
declare -a nac3args if [ -e ../../target/release/nac3standalone ]; then
while [ $# -ge 1 ]; do
case "$1" in
--help)
echo "Usage: run_demo.sh [--help] [--out OUTFILE] [--lli] [--debug] -- [NAC3ARGS...]"
exit
;;
--out)
shift
outfile="$1"
;;
--lli)
use_lli=1
;;
--debug)
debug=1
;;
--)
shift
break
;;
*)
break
;;
esac
shift
done
while [ $# -ge 1 ]; do
nac3args+=("$1")
shift
done
if [ -n "$debug" ] && [ -e ../../target/debug/nac3standalone ]; then
nac3standalone=../../target/debug/nac3standalone
elif [ -e ../../target/release/nac3standalone ]; then
nac3standalone=../../target/release/nac3standalone nac3standalone=../../target/release/nac3standalone
else else
# used by Nix builds # used by Nix builds
nac3standalone=../../target/x86_64-unknown-linux-gnu/release/nac3standalone nac3standalone=../../target/x86_64-unknown-linux-gnu/release/nac3standalone
fi fi
rm -f ./*.o ./*.bc demo rm -f *.o
if [ -z "$use_lli" ]; then $nac3standalone $1
$nac3standalone "${nac3args[@]}" rustc -o demo demo.rs -Crelocation-model=static -Clink-arg=./module.o
./demo
clang -c -std=gnu11 -Wall -Wextra -O3 -o demo.o demo.c
clang -lm -o demo module.o demo.o
if [ -z "$outfile" ]; then
./demo
else
./demo > "$outfile"
fi
else
$nac3standalone --emit-llvm "${nac3args[@]}"
clang -c -std=gnu11 -Wall -Wextra -O3 -emit-llvm -o demo.bc demo.c
shopt -s nullglob
llvm-link -o nac3out.bc module*.bc main.bc
shopt -u nullglob
if [ -z "$outfile" ]; then
lli --extra-module demo.bc --extra-module irrt.bc nac3out.bc
else
lli --extra-module demo.bc --extra-module irrt.bc nac3out.bc > "$outfile"
fi
fi

View File

@ -1,30 +0,0 @@
# Different cases for using boolean variables in boolean contexts.
# Tests whether all boolean variables (expressed as i8s) are lowered into i1s before used in branching instruction (`br`)
def bfunc(b: bool) -> bool:
return not b
def run() -> int32:
b1 = True
b2 = False
if b1:
pass
if not b2:
pass
while b2:
pass
l = [i for i in range(10) if b2]
b_and = True and False
b_or = True or False
b_and = b1 and b2
b_or = b1 or b2
bfunc(b1)
return 0

View File

@ -6,10 +6,6 @@ def output_int32(x: int32):
def output_int64(x: int64): def output_int64(x: int64):
... ...
@extern
def output_strln(x: str):
...
class B: class B:
b: int32 b: int32
@ -27,14 +23,10 @@ class A:
def get_a(self) -> int32: def get_a(self) -> int32:
return self.a return self.a
# def get_b(self) -> B: def get_b(self) -> B:
# return self.b return self.b
class Initless:
def foo(self):
output_strln("hello")
def run() -> int32: def run() -> int32:
a = A(10) a = A(10)
output_int32(a.a) output_int32(a.a)
@ -43,8 +35,4 @@ def run() -> int32:
output_int32(a.a) output_int32(a.a)
output_int32(a.get_a()) output_int32(a.get_a())
# output_int32(a.get_b().b) FIXME: NAC3 prints garbage # output_int32(a.get_b().b) FIXME: NAC3 prints garbage
initless = Initless()
initless.foo()
return 0 return 0

View File

@ -1,50 +0,0 @@
A = ConstGeneric("A", int32)
B = ConstGeneric("B", uint32)
T = TypeVar("T")
class ConstGenericClass(Generic[A]):
def __init__(self):
pass
class ConstGeneric2Class(Generic[A, B]):
def __init__(self):
pass
class HybridGenericClass2(Generic[A, T]):
pass
class HybridGenericClass3(Generic[T, A, B]):
pass
def make_generic_2() -> ConstGenericClass[Literal[2]]:
return ...
def make_generic2_1_2() -> ConstGeneric2Class[Literal[1], Literal[2]]:
return ...
def make_hybrid_class_2_int32() -> HybridGenericClass2[Literal[2], int32]:
return ...
def make_hybrid_class_i32_0_1() -> HybridGenericClass3[int32, Literal[0], Literal[1]]:
return ...
def consume_generic_2(instance: ConstGenericClass[Literal[2]]):
pass
def consume_generic2_1_2(instance: ConstGeneric2Class[Literal[1], Literal[2]]):
pass
def consume_hybrid_class_2_i32(instance: HybridGenericClass2[Literal[2], int32]):
pass
def consume_hybrid_class_i32_0_1(instance: HybridGenericClass3[int32, Literal[0], Literal[1]]):
pass
def f():
consume_generic_2(make_generic_2())
consume_generic2_1_2(make_generic2_1_2())
consume_hybrid_class_2_i32(make_hybrid_class_2_int32())
consume_hybrid_class_i32_0_1(make_hybrid_class_i32_0_1())
def run() -> int32:
return 0

View File

@ -1,8 +0,0 @@
def f():
return
return
def run() -> int32:
f()
return 0

View File

@ -1,102 +0,0 @@
@extern
def output_bool(x: bool):
...
@extern
def output_int32(x: int32):
...
@extern
def output_int64(x: int64):
...
@extern
def output_uint32(x: uint32):
...
@extern
def output_uint64(x: uint64):
...
@extern
def output_float64(x: float):
...
@extern
def output_range(x: range):
...
@extern
def output_int32_list(x: list[int32]):
...
@extern
def output_asciiart(x: int32):
...
@extern
def output_str(x: str):
...
@extern
def output_strln(x: str):
...
def test_output_bool():
output_bool(True)
output_bool(False)
def test_output_int32():
output_int32(-128)
def test_output_int64():
output_int64(int64(-256))
def test_output_uint32():
output_uint32(uint32(128))
def test_output_uint64():
output_uint64(uint64(256))
def test_output_float64():
output_float64(0.0)
output_float64(1.0)
output_float64(-1.0)
output_float64(128.0)
output_float64(-128.0)
output_float64(16.25)
output_float64(-16.25)
def test_output_range():
r = range(1, 100, 5)
output_int32(r.start)
output_int32(r.stop)
output_int32(r.step)
output_range(range(10))
output_range(range(1, 10))
output_range(range(1, 10, 2))
def test_output_asciiart():
for i in range(17):
output_asciiart(i)
output_asciiart(0)
def test_output_int32_list():
output_int32_list([0, 1, 3, 5, 10])
def test_output_str_family():
output_str("hello")
output_strln(" world")
def run() -> int32:
test_output_bool()
test_output_int32()
test_output_int64()
test_output_uint32()
test_output_uint64()
test_output_float64()
test_output_range()
test_output_asciiart()
test_output_int32_list()
test_output_str_family()
return 0

View File

@ -1,17 +0,0 @@
@extern
def output_int32(x: int32):
...
@extern
def output_int32_list(x: list[int32]):
...
def run() -> int32:
bl = [True, False]
bl1 = bl[:]
bl1[1:] = [True]
output_int32_list([int32(b) for b in bl1])
output_int32_list([int32(b) for b in bl1])
return 0

View File

@ -1,7 +1,3 @@
@extern
def output_bool(x: bool):
...
@extern @extern
def output_int32_list(x: list[int32]): def output_int32_list(x: list[int32]):
... ...
@ -34,32 +30,6 @@ def run() -> int32:
get_list_slice() get_list_slice()
list_slice_assignment() list_slice_assignment()
output_int32_list([1, 2, 3] + [4, 5, 6])
output_int32_list([1, 2, 3] * 3)
output_bool([] == [])
output_bool([0] == [])
output_bool([0] == [0])
output_bool([0, 1] == [0])
output_bool([0, 1] == [0, 1])
output_bool([] != [])
output_bool([0] != [])
output_bool([0] != [0])
output_bool([0] != [0, 1])
output_bool([0, 1] != [0, 1])
output_bool([] == [] == [])
output_bool([0] == [0] == [0])
output_bool([0, 1] == [0] == [0, 1])
output_bool([0, 1] == [0, 1] == [0])
output_bool([0] == [0, 1] == [0, 1])
output_bool([0, 1] == [0, 1] == [0, 1])
output_bool([] != [] != [])
output_bool([0] != [0] != [0])
output_bool([0, 1] != [0] != [0, 1])
output_bool([0, 1] != [0, 1] != [0])
output_bool([0] != [0, 1] != [0, 1])
output_bool([0, 1] != [0, 1] != [0, 1])
return 0 return 0
def get_list_slice(): def get_list_slice():

View File

@ -1,12 +1,9 @@
# For Loop using an increasing range() expression as its iterable
@extern @extern
def output_int32(x: int32): def output_int32(x: int32):
... ...
def run() -> int32: def run() -> int32:
i = 0 for _ in range(10):
for i in range(10): output_int32(_)
output_int32(i) _ = 0
output_int32(i)
return 0 return 0

Some files were not shown because too many files have changed in this diff Show More