aboutsummaryrefslogtreecommitdiffstats
path: root/bytecode
diff options
context:
space:
mode:
Diffstat (limited to 'bytecode')
-rw-r--r--bytecode/Cargo.nix742
-rw-r--r--bytecode/src/bytecode.rs721
-rw-r--r--bytecode/src/collector.rs1
-rw-r--r--bytecode/src/data.rs115
-rw-r--r--bytecode/src/encoding.rs279
-rw-r--r--bytecode/src/heap.rs68
-rw-r--r--bytecode/src/main.rs6
-rw-r--r--bytecode/src/stack.rs33
8 files changed, 1169 insertions, 796 deletions
diff --git a/bytecode/Cargo.nix b/bytecode/Cargo.nix
new file mode 100644
index 0000000..136e315
--- /dev/null
+++ b/bytecode/Cargo.nix
@@ -0,0 +1,742 @@
+
+# This file was @generated by crate2nix 0.10.0 with the command:
+# "generate"
+# See https://github.com/kolloch/crate2nix for more info.
+
+{ nixpkgs ? <nixpkgs>
+, pkgs ? import nixpkgs { config = {}; }
+, lib ? pkgs.lib
+, stdenv ? pkgs.stdenv
+, buildRustCrateForPkgs ? if buildRustCrate != null
+ then lib.warn "crate2nix: Passing `buildRustCrate` as argument to Cargo.nix is deprecated. If you don't customize `buildRustCrate`, replace `callPackage ./Cargo.nix {}` by `import ./Cargo.nix { inherit pkgs; }`, and if you need to customize `buildRustCrate`, use `buildRustCrateForPkgs` instead." (_: buildRustCrate)
+ else pkgs: pkgs.buildRustCrate
+ # Deprecated
+, buildRustCrate ? null
+ # This is used as the `crateOverrides` argument for `buildRustCrate`.
+, defaultCrateOverrides ? pkgs.defaultCrateOverrides
+ # The features to enable for the root_crate or the workspace_members.
+, rootFeatures ? [ "default" ]
+ # If true, throw errors instead of issueing deprecation warnings.
+, strictDeprecation ? false
+ # Used for conditional compilation based on CPU feature detection.
+, targetFeatures ? []
+ # Whether to perform release builds: longer compile times, faster binaries.
+, release ? true
+ # Additional crate2nix configuration if it exists.
+, crateConfig
+ ? if builtins.pathExists ./crate-config.nix
+ then pkgs.callPackage ./crate-config.nix {}
+ else {}
+}:
+
+rec {
+ #
+ # "public" attributes that we attempt to keep stable with new versions of crate2nix.
+ #
+
+ rootCrate = rec {
+ packageId = "bytecode";
+
+ # Use this attribute to refer to the derivation building your root crate package.
+ # You can override the features with rootCrate.build.override { features = [ "default" "feature1" ... ]; }.
+ build = internal.buildRustCrateWithFeatures {
+ inherit packageId;
+ };
+
+ # Debug support which might change between releases.
+ # File a bug if you depend on any for non-debug work!
+ debug = internal.debugCrate { inherit packageId; };
+ };
+ # Refer your crate build derivation by name here.
+ # You can override the features with
+ # workspaceMembers."${crateName}".build.override { features = [ "default" "feature1" ... ]; }.
+ workspaceMembers = {
+ "bytecode" = rec {
+ packageId = "bytecode";
+ build = internal.buildRustCrateWithFeatures {
+ packageId = "bytecode";
+ };
+
+ # Debug support which might change between releases.
+ # File a bug if you depend on any for non-debug work!
+ debug = internal.debugCrate { inherit packageId; };
+ };
+ };
+
+ # A derivation that joins the outputs of all workspace members together.
+ allWorkspaceMembers = pkgs.symlinkJoin {
+ name = "all-workspace-members";
+ paths =
+ let members = builtins.attrValues workspaceMembers;
+ in builtins.map (m: m.build) members;
+ };
+
+ #
+ # "internal" ("private") attributes that may change in every new version of crate2nix.
+ #
+
+ internal = rec {
+ # Build and dependency information for crates.
+ # Many of the fields are passed one-to-one to buildRustCrate.
+ #
+ # Noteworthy:
+ # * `dependencies`/`buildDependencies`: similar to the corresponding fields for buildRustCrate.
+ # but with additional information which is used during dependency/feature resolution.
+ # * `resolvedDependencies`: the selected default features reported by cargo - only included for debugging.
+ # * `devDependencies` as of now not used by `buildRustCrate` but used to
+ # inject test dependencies into the build
+
+ crates = {
+ "bytecode" = rec {
+ crateName = "bytecode";
+ version = "0.1.0";
+ edition = "2021";
+ crateBin = [
+ { name = "bytecode"; path = "src/main.rs"; }
+ ];
+ src = lib.cleanSourceWith { filter = sourceFilter; src = ./.; };
+
+ };
+ };
+
+ #
+# crate2nix/default.nix (excerpt start)
+#
+
+ /* Target (platform) data for conditional dependencies.
+ This corresponds roughly to what buildRustCrate is setting.
+ */
+ defaultTarget = {
+ unix = true;
+ windows = false;
+ fuchsia = true;
+ test = false;
+
+ # This doesn't appear to be officially documented anywhere yet.
+ # See https://github.com/rust-lang-nursery/rust-forge/issues/101.
+ os =
+ if stdenv.hostPlatform.isDarwin
+ then "macos"
+ else stdenv.hostPlatform.parsed.kernel.name;
+ arch = stdenv.hostPlatform.parsed.cpu.name;
+ family = "unix";
+ env = "gnu";
+ endian =
+ if stdenv.hostPlatform.parsed.cpu.significantByte.name == "littleEndian"
+ then "little" else "big";
+ pointer_width = toString stdenv.hostPlatform.parsed.cpu.bits;
+ vendor = stdenv.hostPlatform.parsed.vendor.name;
+ debug_assertions = false;
+ };
+
+ /* Filters common temp files and build files. */
+ # TODO(pkolloch): Substitute with gitignore filter
+ sourceFilter = name: type:
+ let
+ baseName = builtins.baseNameOf (builtins.toString name);
+ in
+ ! (
+ # Filter out git
+ baseName == ".gitignore"
+ || (type == "directory" && baseName == ".git")
+
+ # Filter out build results
+ || (
+ type == "directory" && (
+ baseName == "target"
+ || baseName == "_site"
+ || baseName == ".sass-cache"
+ || baseName == ".jekyll-metadata"
+ || baseName == "build-artifacts"
+ )
+ )
+
+ # Filter out nix-build result symlinks
+ || (
+ type == "symlink" && lib.hasPrefix "result" baseName
+ )
+
+ # Filter out IDE config
+ || (
+ type == "directory" && (
+ baseName == ".idea" || baseName == ".vscode"
+ )
+ ) || lib.hasSuffix ".iml" baseName
+
+ # Filter out nix build files
+ || baseName == "Cargo.nix"
+
+ # Filter out editor backup / swap files.
+ || lib.hasSuffix "~" baseName
+ || builtins.match "^\\.sw[a-z]$$" baseName != null
+ || builtins.match "^\\..*\\.sw[a-z]$$" baseName != null
+ || lib.hasSuffix ".tmp" baseName
+ || lib.hasSuffix ".bak" baseName
+ || baseName == "tests.nix"
+ );
+
+ /* Returns a crate which depends on successful test execution
+ of crate given as the second argument.
+
+ testCrateFlags: list of flags to pass to the test exectuable
+ testInputs: list of packages that should be available during test execution
+ */
+ crateWithTest = { crate, testCrate, testCrateFlags, testInputs, testPreRun, testPostRun }:
+ assert builtins.typeOf testCrateFlags == "list";
+ assert builtins.typeOf testInputs == "list";
+ assert builtins.typeOf testPreRun == "string";
+ assert builtins.typeOf testPostRun == "string";
+ let
+ # override the `crate` so that it will build and execute tests instead of
+ # building the actual lib and bin targets We just have to pass `--test`
+ # to rustc and it will do the right thing. We execute the tests and copy
+ # their log and the test executables to $out for later inspection.
+ test =
+ let
+ drv = testCrate.override
+ (
+ _: {
+ buildTests = true;
+ }
+ );
+ # If the user hasn't set any pre/post commands, we don't want to
+ # insert empty lines. This means that any existing users of crate2nix
+ # don't get a spurious rebuild unless they set these explicitly.
+ testCommand = pkgs.lib.concatStringsSep "\n"
+ (pkgs.lib.filter (s: s != "") [
+ testPreRun
+ "$f $testCrateFlags 2>&1 | tee -a $out"
+ testPostRun
+ ]);
+ in
+ pkgs.runCommand "run-tests-${testCrate.name}"
+ {
+ inherit testCrateFlags;
+ buildInputs = testInputs;
+ } ''
+ set -ex
+
+ export RUST_BACKTRACE=1
+
+ # recreate a file hierarchy as when running tests with cargo
+
+ # the source for test data
+ ${pkgs.xorg.lndir}/bin/lndir ${crate.src}
+
+ # build outputs
+ testRoot=target/debug
+ mkdir -p $testRoot
+
+ # executables of the crate
+ # we copy to prevent std::env::current_exe() to resolve to a store location
+ for i in ${crate}/bin/*; do
+ cp "$i" "$testRoot"
+ done
+ chmod +w -R .
+
+ # test harness executables are suffixed with a hash, like cargo does
+ # this allows to prevent name collision with the main
+ # executables of the crate
+ hash=$(basename $out)
+ for file in ${drv}/tests/*; do
+ f=$testRoot/$(basename $file)-$hash
+ cp $file $f
+ ${testCommand}
+ done
+ '';
+ in
+ pkgs.runCommand "${crate.name}-linked"
+ {
+ inherit (crate) outputs crateName;
+ passthru = (crate.passthru or { }) // {
+ inherit test;
+ };
+ } ''
+ echo tested by ${test}
+ ${lib.concatMapStringsSep "\n" (output: "ln -s ${crate.${output}} ${"$"}${output}") crate.outputs}
+ '';
+
+ /* A restricted overridable version of builtRustCratesWithFeatures. */
+ buildRustCrateWithFeatures =
+ { packageId
+ , features ? rootFeatures
+ , crateOverrides ? defaultCrateOverrides
+ , buildRustCrateForPkgsFunc ? null
+ , runTests ? false
+ , testCrateFlags ? [ ]
+ , testInputs ? [ ]
+ # Any command to run immediatelly before a test is executed.
+ , testPreRun ? ""
+ # Any command run immediatelly after a test is executed.
+ , testPostRun ? ""
+ }:
+ lib.makeOverridable
+ (
+ { features
+ , crateOverrides
+ , runTests
+ , testCrateFlags
+ , testInputs
+ , testPreRun
+ , testPostRun
+ }:
+ let
+ buildRustCrateForPkgsFuncOverriden =
+ if buildRustCrateForPkgsFunc != null
+ then buildRustCrateForPkgsFunc
+ else
+ (
+ if crateOverrides == pkgs.defaultCrateOverrides
+ then buildRustCrateForPkgs
+ else
+ pkgs: (buildRustCrateForPkgs pkgs).override {
+ defaultCrateOverrides = crateOverrides;
+ }
+ );
+ builtRustCrates = builtRustCratesWithFeatures {
+ inherit packageId features;
+ buildRustCrateForPkgsFunc = buildRustCrateForPkgsFuncOverriden;
+ runTests = false;
+ };
+ builtTestRustCrates = builtRustCratesWithFeatures {
+ inherit packageId features;
+ buildRustCrateForPkgsFunc = buildRustCrateForPkgsFuncOverriden;
+ runTests = true;
+ };
+ drv = builtRustCrates.crates.${packageId};
+ testDrv = builtTestRustCrates.crates.${packageId};
+ derivation =
+ if runTests then
+ crateWithTest
+ {
+ crate = drv;
+ testCrate = testDrv;
+ inherit testCrateFlags testInputs testPreRun testPostRun;
+ }
+ else drv;
+ in
+ derivation
+ )
+ { inherit features crateOverrides runTests testCrateFlags testInputs testPreRun testPostRun; };
+
+ /* Returns an attr set with packageId mapped to the result of buildRustCrateForPkgsFunc
+ for the corresponding crate.
+ */
+ builtRustCratesWithFeatures =
+ { packageId
+ , features
+ , crateConfigs ? crates
+ , buildRustCrateForPkgsFunc
+ , runTests
+ , target ? defaultTarget
+ } @ args:
+ assert (builtins.isAttrs crateConfigs);
+ assert (builtins.isString packageId);
+ assert (builtins.isList features);
+ assert (builtins.isAttrs target);
+ assert (builtins.isBool runTests);
+ let
+ rootPackageId = packageId;
+ mergedFeatures = mergePackageFeatures
+ (
+ args // {
+ inherit rootPackageId;
+ target = target // { test = runTests; };
+ }
+ );
+ # Memoize built packages so that reappearing packages are only built once.
+ builtByPackageIdByPkgs = mkBuiltByPackageIdByPkgs pkgs;
+ mkBuiltByPackageIdByPkgs = pkgs:
+ let
+ self = {
+ crates = lib.mapAttrs (packageId: value: buildByPackageIdForPkgsImpl self pkgs packageId) crateConfigs;
+ build = mkBuiltByPackageIdByPkgs pkgs.buildPackages;
+ };
+ in
+ self;
+ buildByPackageIdForPkgsImpl = self: pkgs: packageId:
+ let
+ features = mergedFeatures."${packageId}" or [ ];
+ crateConfig' = crateConfigs."${packageId}";
+ crateConfig =
+ builtins.removeAttrs crateConfig' [ "resolvedDefaultFeatures" "devDependencies" ];
+ devDependencies =
+ lib.optionals
+ (runTests && packageId == rootPackageId)
+ (crateConfig'.devDependencies or [ ]);
+ dependencies =
+ dependencyDerivations {
+ inherit features target;
+ buildByPackageId = depPackageId:
+ # proc_macro crates must be compiled for the build architecture
+ if crateConfigs.${depPackageId}.procMacro or false
+ then self.build.crates.${depPackageId}
+ else self.crates.${depPackageId};
+ dependencies =
+ (crateConfig.dependencies or [ ])
+ ++ devDependencies;
+ };
+ buildDependencies =
+ dependencyDerivations {
+ inherit features target;
+ buildByPackageId = depPackageId:
+ self.build.crates.${depPackageId};
+ dependencies = crateConfig.buildDependencies or [ ];
+ };
+ filterEnabledDependenciesForThis = dependencies: filterEnabledDependencies {
+ inherit dependencies features target;
+ };
+ dependenciesWithRenames =
+ lib.filter (d: d ? "rename")
+ (
+ filterEnabledDependenciesForThis
+ (
+ (crateConfig.buildDependencies or [ ])
+ ++ (crateConfig.dependencies or [ ])
+ ++ devDependencies
+ )
+ );
+ # Crate renames have the form:
+ #
+ # {
+ # crate_name = [
+ # { version = "1.2.3"; rename = "crate_name01"; }
+ # ];
+ # # ...
+ # }
+ crateRenames =
+ let
+ grouped =
+ lib.groupBy
+ (dependency: dependency.name)
+ dependenciesWithRenames;
+ versionAndRename = dep:
+ let
+ package = crateConfigs."${dep.packageId}";
+ in
+ { inherit (dep) rename; version = package.version; };
+ in
+ lib.mapAttrs (name: choices: builtins.map versionAndRename choices) grouped;
+ in
+ buildRustCrateForPkgsFunc pkgs
+ (
+ crateConfig // {
+ src = crateConfig.src or (
+ pkgs.fetchurl rec {
+ name = "${crateConfig.crateName}-${crateConfig.version}.tar.gz";
+ # https://www.pietroalbini.org/blog/downloading-crates-io/
+ # Not rate-limited, CDN URL.
+ url = "https://static.crates.io/crates/${crateConfig.crateName}/${crateConfig.crateName}-${crateConfig.version}.crate";
+ sha256 =
+ assert (lib.assertMsg (crateConfig ? sha256) "Missing sha256 for ${name}");
+ crateConfig.sha256;
+ }
+ );
+ extraRustcOpts = lib.lists.optional (targetFeatures != [ ]) "-C target-feature=${lib.concatMapStringsSep "," (x: "+${x}") targetFeatures}";
+ inherit features dependencies buildDependencies crateRenames release;
+ }
+ );
+ in
+ builtByPackageIdByPkgs;
+
+ /* Returns the actual derivations for the given dependencies. */
+ dependencyDerivations =
+ { buildByPackageId
+ , features
+ , dependencies
+ , target
+ }:
+ assert (builtins.isList features);
+ assert (builtins.isList dependencies);
+ assert (builtins.isAttrs target);
+ let
+ enabledDependencies = filterEnabledDependencies {
+ inherit dependencies features target;
+ };
+ depDerivation = dependency: buildByPackageId dependency.packageId;
+ in
+ map depDerivation enabledDependencies;
+
+ /* Returns a sanitized version of val with all values substituted that cannot
+ be serialized as JSON.
+ */
+ sanitizeForJson = val:
+ if builtins.isAttrs val
+ then lib.mapAttrs (n: v: sanitizeForJson v) val
+ else if builtins.isList val
+ then builtins.map sanitizeForJson val
+ else if builtins.isFunction val
+ then "function"
+ else val;
+
+ /* Returns various tools to debug a crate. */
+ debugCrate = { packageId, target ? defaultTarget }:
+ assert (builtins.isString packageId);
+ let
+ debug = rec {
+ # The built tree as passed to buildRustCrate.
+ buildTree = buildRustCrateWithFeatures {
+ buildRustCrateForPkgsFunc = _: lib.id;
+ inherit packageId;
+ };
+ sanitizedBuildTree = sanitizeForJson buildTree;
+ dependencyTree = sanitizeForJson
+ (
+ buildRustCrateWithFeatures {
+ buildRustCrateForPkgsFunc = _: crate: {
+ "01_crateName" = crate.crateName or false;
+ "02_features" = crate.features or [ ];
+ "03_dependencies" = crate.dependencies or [ ];
+ };
+ inherit packageId;
+ }
+ );
+ mergedPackageFeatures = mergePackageFeatures {
+ features = rootFeatures;
+ inherit packageId target;
+ };
+ diffedDefaultPackageFeatures = diffDefaultPackageFeatures {
+ inherit packageId target;
+ };
+ };
+ in
+ { internal = debug; };
+
+ /* Returns differences between cargo default features and crate2nix default
+ features.
+
+ This is useful for verifying the feature resolution in crate2nix.
+ */
+ diffDefaultPackageFeatures =
+ { crateConfigs ? crates
+ , packageId
+ , target
+ }:
+ assert (builtins.isAttrs crateConfigs);
+ let
+ prefixValues = prefix: lib.mapAttrs (n: v: { "${prefix}" = v; });
+ mergedFeatures =
+ prefixValues
+ "crate2nix"
+ (mergePackageFeatures { inherit crateConfigs packageId target; features = [ "default" ]; });
+ configs = prefixValues "cargo" crateConfigs;
+ combined = lib.foldAttrs (a: b: a // b) { } [ mergedFeatures configs ];
+ onlyInCargo =
+ builtins.attrNames
+ (lib.filterAttrs (n: v: !(v ? "crate2nix") && (v ? "cargo")) combined);
+ onlyInCrate2Nix =
+ builtins.attrNames
+ (lib.filterAttrs (n: v: (v ? "crate2nix") && !(v ? "cargo")) combined);
+ differentFeatures = lib.filterAttrs
+ (
+ n: v:
+ (v ? "crate2nix")
+ && (v ? "cargo")
+ && (v.crate2nix.features or [ ]) != (v."cargo".resolved_default_features or [ ])
+ )
+ combined;
+ in
+ builtins.toJSON {
+ inherit onlyInCargo onlyInCrate2Nix differentFeatures;
+ };
+
+ /* Returns an attrset mapping packageId to the list of enabled features.
+
+ If multiple paths to a dependency enable different features, the
+ corresponding feature sets are merged. Features in rust are additive.
+ */
+ mergePackageFeatures =
+ { crateConfigs ? crates
+ , packageId
+ , rootPackageId ? packageId
+ , features ? rootFeatures
+ , dependencyPath ? [ crates.${packageId}.crateName ]
+ , featuresByPackageId ? { }
+ , target
+ # Adds devDependencies to the crate with rootPackageId.
+ , runTests ? false
+ , ...
+ } @ args:
+ assert (builtins.isAttrs crateConfigs);
+ assert (builtins.isString packageId);
+ assert (builtins.isString rootPackageId);
+ assert (builtins.isList features);
+ assert (builtins.isList dependencyPath);
+ assert (builtins.isAttrs featuresByPackageId);
+ assert (builtins.isAttrs target);
+ assert (builtins.isBool runTests);
+ let
+ crateConfig = crateConfigs."${packageId}" or (builtins.throw "Package not found: ${packageId}");
+ expandedFeatures = expandFeatures (crateConfig.features or { }) features;
+ enabledFeatures = enableFeatures (crateConfig.dependencies or [ ]) expandedFeatures;
+ depWithResolvedFeatures = dependency:
+ let
+ packageId = dependency.packageId;
+ features = dependencyFeatures enabledFeatures dependency;
+ in
+ { inherit packageId features; };
+ resolveDependencies = cache: path: dependencies:
+ assert (builtins.isAttrs cache);
+ assert (builtins.isList dependencies);
+ let
+ enabledDependencies = filterEnabledDependencies {
+ inherit dependencies target;
+ features = enabledFeatures;
+ };
+ directDependencies = map depWithResolvedFeatures enabledDependencies;
+ foldOverCache = op: lib.foldl op cache directDependencies;
+ in
+ foldOverCache
+ (
+ cache: { packageId, features }:
+ let
+ cacheFeatures = cache.${packageId} or [ ];
+ combinedFeatures = sortedUnique (cacheFeatures ++ features);
+ in
+ if cache ? ${packageId} && cache.${packageId} == combinedFeatures
+ then cache
+ else
+ mergePackageFeatures {
+ features = combinedFeatures;
+ featuresByPackageId = cache;
+ inherit crateConfigs packageId target runTests rootPackageId;
+ }
+ );
+ cacheWithSelf =
+ let
+ cacheFeatures = featuresByPackageId.${packageId} or [ ];
+ combinedFeatures = sortedUnique (cacheFeatures ++ enabledFeatures);
+ in
+ featuresByPackageId // {
+ "${packageId}" = combinedFeatures;
+ };
+ cacheWithDependencies =
+ resolveDependencies cacheWithSelf "dep"
+ (
+ crateConfig.dependencies or [ ]
+ ++ lib.optionals
+ (runTests && packageId == rootPackageId)
+ (crateConfig.devDependencies or [ ])
+ );
+ cacheWithAll =
+ resolveDependencies
+ cacheWithDependencies "build"
+ (crateConfig.buildDependencies or [ ]);
+ in
+ cacheWithAll;
+
+ /* Returns the enabled dependencies given the enabled features. */
+ filterEnabledDependencies = { dependencies, features, target }:
+ assert (builtins.isList dependencies);
+ assert (builtins.isList features);
+ assert (builtins.isAttrs target);
+
+ lib.filter
+ (
+ dep:
+ let
+ targetFunc = dep.target or (features: true);
+ in
+ targetFunc { inherit features target; }
+ && (
+ !(dep.optional or false)
+ || builtins.any (doesFeatureEnableDependency dep) features
+ )
+ )
+ dependencies;
+
+ /* Returns whether the given feature should enable the given dependency. */
+ doesFeatureEnableDependency = { name, rename ? null, ... }: feature:
+ let
+ prefix = "${name}/";
+ len = builtins.stringLength prefix;
+ startsWithPrefix = builtins.substring 0 len feature == prefix;
+ in
+ (rename == null && feature == name)
+ || (rename != null && rename == feature)
+ || startsWithPrefix;
+
+ /* Returns the expanded features for the given inputFeatures by applying the
+ rules in featureMap.
+
+ featureMap is an attribute set which maps feature names to lists of further
+ feature names to enable in case this feature is selected.
+ */
+ expandFeatures = featureMap: inputFeatures:
+ assert (builtins.isAttrs featureMap);
+ assert (builtins.isList inputFeatures);
+ let
+ expandFeature = feature:
+ assert (builtins.isString feature);
+ [ feature ] ++ (expandFeatures featureMap (featureMap."${feature}" or [ ]));
+ outFeatures = lib.concatMap expandFeature inputFeatures;
+ in
+ sortedUnique outFeatures;
+
+ /* This function adds optional dependencies as features if they are enabled
+ indirectly by dependency features. This function mimics Cargo's behavior
+ described in a note at:
+ https://doc.rust-lang.org/nightly/cargo/reference/features.html#dependency-features
+ */
+ enableFeatures = dependencies: features:
+ assert (builtins.isList features);
+ assert (builtins.isList dependencies);
+ let
+ additionalFeatures = lib.concatMap
+ (
+ dependency:
+ assert (builtins.isAttrs dependency);
+ let
+ enabled = builtins.any (doesFeatureEnableDependency dependency) features;
+ in
+ if (dependency.optional or false) && enabled then [ dependency.name ] else [ ]
+ )
+ dependencies;
+ in
+ sortedUnique (features ++ additionalFeatures);
+
+ /*
+ Returns the actual features for the given dependency.
+
+ features: The features of the crate that refers this dependency.
+ */
+ dependencyFeatures = features: dependency:
+ assert (builtins.isList features);
+ assert (builtins.isAttrs dependency);
+ let
+ defaultOrNil =
+ if dependency.usesDefaultFeatures or true
+ then [ "default" ]
+ else [ ];
+ explicitFeatures = dependency.features or [ ];
+ additionalDependencyFeatures =
+ let
+ dependencyPrefix = (dependency.rename or dependency.name) + "/";
+ dependencyFeatures =
+ builtins.filter (f: lib.hasPrefix dependencyPrefix f) features;
+ in
+ builtins.map (lib.removePrefix dependencyPrefix) dependencyFeatures;
+ in
+ defaultOrNil ++ explicitFeatures ++ additionalDependencyFeatures;
+
+ /* Sorts and removes duplicates from a list of strings. */
+ sortedUnique = features:
+ assert (builtins.isList features);
+ assert (builtins.all builtins.isString features);
+ let
+ outFeaturesSet = lib.foldl (set: feature: set // { "${feature}" = 1; }) { } features;
+ outFeaturesUnique = builtins.attrNames outFeaturesSet;
+ in
+ builtins.sort (a: b: a < b) outFeaturesUnique;
+
+ deprecationWarning = message: value:
+ if strictDeprecation
+ then builtins.throw "strictDeprecation enabled, aborting: ${message}"
+ else builtins.trace message value;
+
+ #
+ # crate2nix/default.nix (excerpt end)
+ #
+ };
+}
+
diff --git a/bytecode/src/bytecode.rs b/bytecode/src/bytecode.rs
index 84a9b8b..df07485 100644
--- a/bytecode/src/bytecode.rs
+++ b/bytecode/src/bytecode.rs
@@ -1,598 +1,429 @@
-use crate::data::{Pointer, Value};
+use crate::data::Value;
use crate::heap::Heap;
-use std::io::{BufWriter, Read, StdinLock, Write};
-// The cute scheme virtual machine has two stacks:
-// - the data stack and
-// - the locals stack.
-// There are four pointers:
-// - the instruction pointer,
-// - the argument pointer,
-// - and the (data) stack pointer.
-// These pointers cannot be manipulated directly, but are referred to in
-// the comments below on the opcodes.
+// The cute scheme virtual machine is a register based VM.
+// There are 256 registers, also called locals.
-#[derive(Debug, Eq, PartialEq)]
+#[derive(Debug, Eq, PartialEq, Clone, Copy)]
+pub struct Local(pub u8);
+
+#[derive(Debug, Eq, PartialEq, Clone, Copy)]
+pub enum Arg {
+ L(Local),
+ Const(Value),
+}
+
+#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum Op {
// Signed arithmetic
// =================
- // ( -- n )
- // Pushes a constant.
- Const(i64),
- // ( n1 n2 -- n3 )
// Adds two integers.
- Add,
- // ( n1 n2 -- n3 )
+ Add(Local, Arg, Arg),
// Subtracts two integers.
- Sub,
- // ( n1 n2 -- n3 )
+ Sub(Local, Arg, Arg),
// Multiplies two integers.
- Mul,
- // ( n1 n2 -- n3 )
+ Mul(Local, Arg, Arg),
// Divides two integers and truncates the result.
- Div,
- // ( n1 n2 -- n3 )
+ Div(Local, Arg, Arg),
// Remainder from Div.
- Mod,
+ Mod(Local, Arg, Arg),
// Heap
// ======
- // ( n -- a )
// Allocates n words and returns the address.
- Alloc,
- // ( n -- a )
+ Alloc(Local, Arg),
// Allocates n bytes and returns the address. The contents of this allocation will be treated
// as raw data and not walked by the garbage collector.
- AllocBytevector,
- // ( a i -- u )
+ AllocBytevector(Local, Arg),
// Fetches a 64 bit word from the specified address plus the
// given offset.
- Peek,
- // ( u a i )
+ Peek(Local, Arg, Arg),
// Stores a 64 bit word at the specified address plus the
// given offset.
- Poke,
- // ( a i -- n )
+ Poke(Arg, Arg, Arg),
// Fetches a byte from the specified address plus the given offset.
- PeekByte,
- // ( n a i )
+ PeekByte(Local, Arg, Arg),
// Stores a byte at the specified address plus the given offset.
- PokeByte,
+ PokeByte(Arg, Arg, Arg),
- // Stack
- // =====
+ // Locals
+ // ======
- // ( n -- )
- // Deletes an element from the stack.
- Pop,
- // ( -- n )
- // Copies an argument from the locals stack to the data stack. The argument is an index above the argument pointer.
- Local(u8),
+ // Loads a value into a register.
+ Mov(Local, Arg),
// Control flow
// ============
- // ( f -- )
- // Jumps to the specified offset if the argument is not false.
- If(i64),
- // ( i-addr a1 a2 ... an )
- // ( Locals stack: -- instruction-ptr a1 a2 ... an argument-ptr )
- // Pushes the instruction pointer, moves n arguments to the locals
- // stack, pushes the old argument pointer, and jumps to the
- // specified location.
- Call(u8),
- // ( Locals stack: instruction-ptr a1 a2 ... an argument-ptr )
- // Pops the argument pointer (effectively popping n more arguments which were added by Call),
- // and then pops the instruction pointer.
- Ret,
- // ( n )
+ // Jumps to the specified address.
+ Jmp(Arg),
+ // Jumps to the specified address if the argument is not false. Note: the first argument is the
+ // condition and the second argument is the address.
+ JmpIf(Arg, Arg),
// Terminates the interpreter with the given status code.
- Exit,
-
- // Input and output
- // ================
-
- // ( b file -- f )
- // Writes a byte to a file. Returns true on success, false on error.
- PutC,
- // ( file -- b )
- // Reads one byte from a file. Returns -1 on EOF, 0 on error.
- GetC,
+ Exit(Arg),
}
-struct Stack {
- v: Vec<Value>,
+struct Interpreter {
+ locals: Vec<Value>,
+ heap: Heap,
}
-impl Stack {
- fn pop(&mut self) -> Result<Value, String> {
- return self.v.pop().ok_or(String::from("stack underflow"));
- }
-
- fn pop_int(&mut self) -> Result<i64, String> {
- return self.pop()?.to_int();
- }
-
- fn push_int(&mut self, i: i64) {
- self.v.push(Value::from_int(i));
+impl Interpreter {
+ fn read_arg(&self, x: Arg) -> Value {
+ match x {
+ Arg::L(Local(i)) => self.locals[usize::from(i)],
+ Arg::Const(c) => c,
+ }
}
- fn pop_pointer(&mut self) -> Result<Pointer, String> {
- return self.pop()?.to_pointer();
+ fn set_arg(&mut self, dest: Local, val: Value) {
+ let Local(i) = dest;
+ self.locals[usize::from(i)] = val;
}
- fn push_pointer(&mut self, p: Pointer) {
- self.v.push(Value::from_pointer(p));
+ fn add(&mut self, dest: Local, x: Arg, y: Arg) -> Result<(), String> {
+ let n1 = self.read_arg(x).to_int()?;
+ let n2 = self.read_arg(y).to_int()?;
+ self.set_arg(dest, Value::from_int(n1.wrapping_add(n2)));
+ Ok(())
}
- fn pop_usize(&mut self) -> Result<usize, String> {
- let Value(stack_representation) = self.pop()?;
- Ok(usize::try_from(stack_representation).unwrap() >> 3)
+ fn sub(&mut self, dest: Local, x: Arg, y: Arg) -> Result<(), String> {
+ let n1 = self.read_arg(x).to_int()?;
+ let n2 = self.read_arg(y).to_int()?;
+ self.set_arg(dest, Value::from_int(n1.wrapping_sub(n2)));
+ Ok(())
}
- fn push_usize(&mut self, p: usize) {
- // We're going to disguise p as a pointer by shifting.
- if p >= 0x2000000000000000
- /* 2^61 */
- {
- panic!("pointer overflow!");
- }
- self.v.push(Value(u64::try_from(p).unwrap() << 3));
+ fn mul(&mut self, dest: Local, x: Arg, y: Arg) -> Result<(), String> {
+ let n1 = self.read_arg(x).to_int()?;
+ let n2 = self.read_arg(y).to_int()?;
+ self.set_arg(dest, Value::from_int(n1.wrapping_mul(n2)));
+ Ok(())
}
-}
-
-fn fn_const(stack: &mut Stack, n: i64) {
- stack.push_int(n);
-}
-
-fn add(stack: &mut Stack) -> Result<(), String> {
- let n2 = stack.pop_int()?;
- let n1 = stack.pop_int()?;
- stack.push_int(n1.wrapping_add(n2));
- Ok(())
-}
-fn sub(stack: &mut Stack) -> Result<(), String> {
- let n2 = stack.pop_int()?;
- let n1 = stack.pop_int()?;
- stack.push_int(n1.wrapping_sub(n2));
- Ok(())
-}
-
-fn mul(stack: &mut Stack) -> Result<(), String> {
- let n2 = stack.pop_int()?;
- let n1 = stack.pop_int()?;
- stack.push_int(n1.wrapping_mul(n2));
- Ok(())
-}
-
-fn div(stack: &mut Stack) -> Result<(), String> {
- let n2 = stack.pop_int()?;
- let n1 = stack.pop_int()?;
- stack.push_int(n1.wrapping_div(n2));
- Ok(())
-}
-
-fn fn_mod(stack: &mut Stack) -> Result<(), String> {
- let n2 = stack.pop_int()?;
- let n1 = stack.pop_int()?;
- stack.push_int(n1.wrapping_rem(n2));
- Ok(())
-}
-
-fn alloc(stack: &mut Stack, locals: &mut Stack, heap: &mut Heap) -> Result<(), String> {
- let n = stack.pop_int()?;
- if n < 0 {
- return Err(String::from("tried to allocate negative memory"));
+ fn div(&mut self, dest: Local, x: Arg, y: Arg) -> Result<(), String> {
+ let n1 = self.read_arg(x).to_int()?;
+ let n2 = self.read_arg(y).to_int()?;
+ self.set_arg(dest, Value::from_int(n1.wrapping_div(n2)));
+ Ok(())
}
- let p = heap.alloc(usize::try_from(n).unwrap(), &mut stack.v, &mut locals.v)?;
- stack.push_pointer(p);
- return Ok(());
-}
-fn alloc_bytevector(stack: &mut Stack, locals: &mut Stack, heap: &mut Heap) -> Result<(), String> {
- let n = stack.pop_int()?;
- if n < 0 {
- return Err(String::from("tried to allocate negative memory"));
+ fn fn_mod(&mut self, dest: Local, x: Arg, y: Arg) -> Result<(), String> {
+ let n1 = self.read_arg(x).to_int()?;
+ let n2 = self.read_arg(y).to_int()?;
+ self.set_arg(dest, Value::from_int(n1.wrapping_rem(n2)));
+ Ok(())
}
- let p = heap.alloc_bytevector(usize::try_from(n).unwrap(), &mut stack.v, &mut locals.v)?;
- stack.push_pointer(p);
- return Ok(());
-}
-
-fn peek(stack: &mut Stack, heap: &Heap) -> Result<(), String> {
- let i = stack.pop_int()?;
- let a = stack.pop_pointer()?;
- stack.v.push(heap.peek(a.offset(i))?);
- Ok(())
-}
-
-fn poke(stack: &mut Stack, heap: &mut Heap) -> Result<(), String> {
- let i = stack.pop_int()?;
- let a = stack.pop_pointer()?;
- let u = stack.pop()?;
- heap.poke(u, a.offset(i))?;
- Ok(())
-}
-
-fn peek_byte(stack: &mut Stack, heap: &Heap) -> Result<(), String> {
- let i = stack.pop_int()?;
- let a = stack.pop_pointer()?;
- stack.push_int(i64::from(heap.peek_byte(a.offset(i))));
- Ok(())
-}
-
-fn poke_byte(stack: &mut Stack, heap: &mut Heap) -> Result<(), String> {
- let i = stack.pop_int()?;
- let a = stack.pop_pointer()?;
- let n = stack.pop_int()?;
- heap.poke_byte((n & 0xff).try_into().unwrap(), a.offset(i));
- Ok(())
-}
-
-fn pop(stack: &mut Stack) -> Result<(), String> {
- stack.pop()?;
- Ok(())
-}
-
-fn local(stack: &mut Stack, locals: &Stack, n: u8) -> Result<(), String> {
- let i = locals
- .v
- .len()
- .checked_sub(usize::from(n) + 1)
- .ok_or("out of bounds")?;
- stack.v.push(locals.v[i]);
- Ok(())
-}
-fn fn_if(stack: &mut Stack, ip: &mut usize, n: i64) -> Result<(), String> {
- let f = stack.pop_int()?;
- if f != 0 {
- if n > 0 {
- *ip = ip
- .checked_add(n.try_into().unwrap())
- .ok_or("invalid offset")?;
- } else {
- *ip = ip
- .checked_sub((-n).try_into().unwrap())
- .ok_or("invalid offset")?;
+ fn alloc(&mut self, dest: Local, size: Arg) -> Result<(), String> {
+ let n = self.read_arg(size).to_int()?;
+ if n < 0 {
+ return Err(String::from("tried to allocate negative memory"));
}
+ let p = self
+ .heap
+ .alloc(usize::try_from(n).unwrap(), &mut self.locals)?;
+ self.set_arg(dest, Value::from_pointer(p));
+ Ok(())
}
- Ok(())
-}
-
-fn call(stack: &mut Stack, locals: &mut Stack, ip: &mut usize, n: u8) -> Result<(), String> {
- // Push the instruction pointer.
- locals.push_usize(*ip);
- let old_ap = locals.v.len();
- // Move n arguments to the locals stack.
- let a1_idx = stack.v.len() - usize::from(n);
- for i in a1_idx..stack.v.len() {
- locals.v.push(stack.v[i]);
- }
- stack.v.truncate(a1_idx);
- // Push the old argument pointer.
- locals.push_usize(old_ap);
- // Jump to the specified location.
- let i_addr = stack.pop_int()?;
- let i_addr_usize = match usize::try_from(i_addr) {
- Ok(x) => x,
- Err(_) => {
- return Err(String::from("invalid address"));
- }
- };
- // Subtract 1 because the interpreter will also increment the
- // instruction pointer.
- *ip = i_addr_usize - 1;
- Ok(())
-}
-
-fn ret(locals: &mut Stack, ip: &mut usize) -> Result<(), String> {
- let ap = locals.pop_usize()?;
- locals.v.truncate(ap);
- let return_address = locals.pop_usize()?;
- *ip = return_address;
- Ok(())
-}
-
-trait File: Write + Read {}
-struct FileTable<'a> {
- files: Vec<Box<dyn File + 'a>>,
-}
-
-impl<'a> FileTable<'a> {
- fn file(&mut self, f: i64) -> Result<&mut (dyn File + 'a), String> {
- if !(0 <= f && usize::try_from(f).unwrap() < self.files.len()) {
- return Err(String::from("invalid file"));
+ fn alloc_bytevector(&mut self, dest: Local, size: Arg) -> Result<(), String> {
+ let n = self.read_arg(size).to_int()?;
+ if n < 0 {
+ return Err(String::from("tried to allocate negative memory"));
}
- Ok(&mut *self.files[usize::try_from(f).unwrap()])
+ let p = self
+ .heap
+ .alloc_bytevector(usize::try_from(n).unwrap(), &mut self.locals)?;
+ self.set_arg(dest, Value::from_pointer(p));
+ Ok(())
}
-}
-
-struct Stdin<'a>(StdinLock<'a>);
-impl<'a> Write for Stdin<'a> {
- fn write(&mut self, _: &[u8]) -> std::io::Result<usize> {
- Err(std::io::Error::new(
- std::io::ErrorKind::InvalidInput,
- "can't write to stdin",
- ))
+ fn peek(&mut self, dest: Local, ptr: Arg, offset: Arg) -> Result<(), String> {
+ let p = self.read_arg(ptr).to_pointer()?;
+ let o = self.read_arg(offset).to_int()?;
+ if o < 0 {
+ return Err(String::from("pointer offset can't be negative"));
+ }
+ let result = self.heap.peek(p.offset(usize::try_from(o).unwrap()))?;
+ self.set_arg(dest, result);
+ Ok(())
}
- fn flush(&mut self) -> std::io::Result<()> {
+ fn poke(&mut self, word: Arg, ptr: Arg, offset: Arg) -> Result<(), String> {
+ let w = self.read_arg(word);
+ let p = self.read_arg(ptr).to_pointer()?;
+ let o = self.read_arg(offset).to_int()?;
+ if o < 0 {
+ return Err(String::from("pointer offset can't be negative"));
+ }
+ self.heap.poke(w, p.offset(usize::try_from(o).unwrap()))?;
Ok(())
}
-}
-impl<'a> Read for Stdin<'a> {
- fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
- let Stdin(ref mut handle) = self;
- handle.read(buf)
+ fn peek_byte(&mut self, dest: Local, ptr: Arg, offset: Arg) -> Result<(), String> {
+ let p = self.read_arg(ptr).to_pointer()?;
+ let o = self.read_arg(offset).to_int()?;
+ if o < 0 {
+ return Err(String::from("pointer offset can't be negative"));
+ }
+ let result = self.heap.peek_byte(p.offset(usize::try_from(o).unwrap()))?;
+ self.set_arg(dest, Value::from_int(i64::from(result)));
+ Ok(())
}
-}
-
-impl<'a> File for Stdin<'a> {}
-struct Out<T>(T);
-
-impl<T: Write> Write for Out<T> {
- fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
- let Out(ref mut handle) = self;
- handle.write(buf)
+ fn poke_byte(&mut self, word: Arg, ptr: Arg, offset: Arg) -> Result<(), String> {
+ let w = self.read_arg(word).to_int()?;
+ let p = self.read_arg(ptr).to_pointer()?;
+ let o = self.read_arg(offset).to_int()?;
+ if o < 0 {
+ return Err(String::from("pointer offset can't be negative"));
+ }
+ if !(0 <= w && w <= 0xff) {
+ return Err(format!("value {} is not byte-sized", w));
+ }
+ self.heap.poke_byte(
+ u8::try_from(w).unwrap(),
+ p.offset(usize::try_from(o).unwrap()),
+ )?;
+ Ok(())
}
- fn flush(&mut self) -> std::io::Result<()> {
- let Out(ref mut handle) = self;
- handle.flush()
+ fn mov(&mut self, dest: Local, src: Arg) {
+ self.set_arg(dest, self.read_arg(src));
}
-}
-impl<T> Read for Out<T> {
- fn read(&mut self, _: &mut [u8]) -> std::io::Result<usize> {
- Err(std::io::Error::new(
- std::io::ErrorKind::InvalidInput,
- "invalid file for reading",
- ))
+ fn jmp(&mut self, ip: &mut usize, addr: Arg) -> Result<(), String> {
+ let a = self.read_arg(addr).to_int()?;
+ if a < 0 {
+ return Err(String::from("can't jump to a negative address"));
+ }
+ *ip = usize::try_from(a).unwrap();
+ Ok(())
}
-}
-
-impl<T: Write> File for Out<T> {}
-fn putc(stack: &mut Stack, files: &mut FileTable) -> Result<(), String> {
- let file = stack.pop_int()?;
- let byte = stack.pop_int()?;
- match files
- .file(file)?
- .write(&vec![(byte & 0xff).try_into().unwrap()])
- {
- Ok(_) => {
- stack.push_int(-1);
+ fn jmp_if(&mut self, ip: &mut usize, cond: Arg, addr: Arg) -> Result<(), String> {
+ let c = self.read_arg(cond);
+ let a = self.read_arg(addr).to_int()?;
+ if a < 0 {
+ return Err(String::from("can't jump to a negative address"));
}
- Err(_) => {
- stack.push_int(0);
+ if c != Value::from_bool(false) {
+ *ip = usize::try_from(a).unwrap();
}
+ Ok(())
}
- Ok(())
-}
-fn getc(stack: &mut Stack, files: &mut FileTable) -> Result<(), String> {
- let file = stack.pop_int()?;
- let mut buf = vec![0];
- match files.file(file)?.read(&mut buf) {
- Ok(1) => {
- stack.push_int(buf[0].into());
- }
- Ok(0) => {
- stack.push_int(-1);
- }
- _ => {
- stack.push_int(0);
+ fn eval(&mut self, prog: &[Op]) -> Result<u8, String> {
+ let mut ip = 0;
+ loop {
+ if ip >= prog.len() {
+ return Err(String::from("invalid instruction pointer"));
+ }
+ let op = prog[ip];
+ ip += 1;
+ match op {
+ Op::Add(dest, x, y) => self.add(dest, x, y)?,
+ Op::Sub(dest, x, y) => self.sub(dest, x, y)?,
+ Op::Mul(dest, x, y) => self.mul(dest, x, y)?,
+ Op::Div(dest, x, y) => self.div(dest, x, y)?,
+ Op::Mod(dest, x, y) => self.fn_mod(dest, x, y)?,
+ Op::Alloc(dest, size) => self.alloc(dest, size)?,
+ Op::AllocBytevector(dest, size) => self.alloc_bytevector(dest, size)?,
+ Op::Peek(dest, ptr, offset) => self.peek(dest, ptr, offset)?,
+ Op::Poke(word, ptr, offset) => self.poke(word, ptr, offset)?,
+ Op::PeekByte(dest, ptr, offset) => self.peek_byte(dest, ptr, offset)?,
+ Op::PokeByte(word, ptr, offset) => self.poke_byte(word, ptr, offset)?,
+ Op::Mov(dest, src) => self.mov(dest, src),
+ Op::Jmp(addr) => self.jmp(&mut ip, addr)?,
+ Op::JmpIf(cond, addr) => self.jmp_if(&mut ip, cond, addr)?,
+ Op::Exit(code) => {
+ let n = self.read_arg(code).to_int()?;
+ return Ok((n & 0xff) as u8);
+ }
+ }
}
}
- Ok(())
}
pub fn eval(prog: &[Op]) -> Result<u8, String> {
- let mut stack = Stack { v: Vec::new() };
- let mut locals_stack = Stack { v: Vec::new() };
- let mut heap = Heap::new();
- let mut ip = 0;
- let stdin = std::io::stdin();
- let mut files = FileTable {
- files: vec![
- Box::new(Stdin(stdin.lock())),
- Box::new(Out(BufWriter::new(std::io::stdout()))),
- Box::new(Out(BufWriter::new(std::io::stderr()))),
- ],
+ let mut interpreter = Interpreter {
+ locals: vec![Value(0); 256],
+ heap: Heap::new(),
};
- loop {
- if ip >= prog.len() {
- return Err(String::from("invalid instruction pointer"));
- }
- match prog[ip] {
- Op::Const(n) => fn_const(&mut stack, n),
- Op::Add => add(&mut stack)?,
- Op::Sub => sub(&mut stack)?,
- Op::Mul => mul(&mut stack)?,
- Op::Div => div(&mut stack)?,
- Op::Mod => fn_mod(&mut stack)?,
- Op::Alloc => alloc(&mut stack, &mut locals_stack, &mut heap)?,
- Op::AllocBytevector => alloc_bytevector(&mut stack, &mut locals_stack, &mut heap)?,
- Op::Peek => peek(&mut stack, &heap)?,
- Op::Poke => poke(&mut stack, &mut heap)?,
- Op::PeekByte => peek_byte(&mut stack, &heap)?,
- Op::PokeByte => poke_byte(&mut stack, &mut heap)?,
- Op::Pop => pop(&mut stack)?,
- Op::Local(n) => local(&mut stack, &locals_stack, n)?,
- Op::If(n) => fn_if(&mut stack, &mut ip, n)?,
- Op::Call(n) => call(&mut stack, &mut locals_stack, &mut ip, n)?,
- Op::Ret => ret(&mut locals_stack, &mut ip)?,
- Op::PutC => putc(&mut stack, &mut files)?,
- Op::GetC => getc(&mut stack, &mut files)?,
- Op::Exit => {
- let n = stack.pop_int()?;
- return Ok((n & 0xff) as u8);
- }
- }
- ip += 1;
- }
+ interpreter.eval(prog)
}
#[cfg(test)]
mod tests {
+ use super::Arg::*;
use super::Op::*;
use super::*;
#[test]
fn eval_const() {
- assert_eq!(Ok(5), eval(&vec![Const(5), Exit]));
+ assert_eq!(Ok(5), eval(&vec![Exit(Const(Value::from_int(5)))]));
}
#[test]
fn eval_add() {
- assert_eq!(Ok(10), eval(&vec![Const(5), Const(5), Add, Exit]));
+ assert_eq!(
+ Ok(10),
+ eval(&vec![
+ Add(
+ Local(0),
+ Const(Value::from_int(5)),
+ Const(Value::from_int(5))
+ ),
+ Exit(L(Local(0))),
+ ])
+ );
}
#[test]
fn eval_sub() {
- assert_eq!(Ok(2), eval(&vec![Const(5), Const(3), Sub, Exit]));
+ assert_eq!(
+ Ok(2),
+ eval(&vec![
+ Sub(
+ Local(0),
+ Const(Value::from_int(5)),
+ Const(Value::from_int(3))
+ ),
+ Exit(L(Local(0))),
+ ])
+ );
}
#[test]
fn eval_mul() {
- assert_eq!(Ok(25), eval(&vec![Const(5), Const(5), Mul, Exit]));
- }
-
- #[test]
- fn eval_div() {
- assert_eq!(Ok(2), eval(&vec![Const(5), Const(2), Div, Exit]));
- }
-
- #[test]
- fn eval_mod() {
- assert_eq!(Ok(1), eval(&vec![Const(5), Const(2), Mod, Exit]));
- }
-
- #[test]
- fn eval_alloc() {
- assert_eq!(Ok(0), eval(&vec![Const(10), Alloc, Pop, Const(0), Exit]));
- }
-
- #[test]
- fn eval_bytevector() {
assert_eq!(
- Ok(0),
- eval(&vec![Const(10), AllocBytevector, Pop, Const(0), Exit])
+ Ok(25),
+ eval(&vec![
+ Mul(
+ Local(0),
+ Const(Value::from_int(5)),
+ Const(Value::from_int(5))
+ ),
+ Exit(L(Local(0))),
+ ])
);
}
#[test]
- fn eval_peek() {
+ fn eval_div() {
assert_eq!(
- Ok(0),
- eval(&vec![Const(8), Alloc, Const(0), Peek, Const(0), Exit])
+ Ok(2),
+ eval(&vec![
+ Div(
+ Local(0),
+ Const(Value::from_int(5)),
+ Const(Value::from_int(2))
+ ),
+ Exit(L(Local(0))),
+ ])
);
}
#[test]
- fn eval_poke() {
+ fn eval_mod() {
assert_eq!(
- Ok(0),
+ Ok(1),
eval(&vec![
- Const(5),
- Const(8),
- Alloc,
- Const(0),
- Poke,
- Const(0),
- Exit
+ Mod(
+ Local(0),
+ Const(Value::from_int(5)),
+ Const(Value::from_int(2))
+ ),
+ Exit(L(Local(0))),
])
);
}
#[test]
- fn eval_peek_byte() {
+ fn eval_alloc() {
assert_eq!(
Ok(0),
- eval(&vec![Const(1), Alloc, Const(0), PeekByte, Exit])
+ eval(&vec![
+ Alloc(Local(0), Const(Value::from_int(10))),
+ Exit(Const(Value::from_int(0))),
+ ])
);
}
#[test]
- fn eval_poke_byte() {
+ fn eval_bytevector() {
assert_eq!(
Ok(0),
eval(&vec![
- Const(5),
- Const(1),
- Alloc,
- Const(0),
- PokeByte,
- Const(0),
- Exit
+ AllocBytevector(Local(0), Const(Value::from_int(10))),
+ Exit(Const(Value::from_int(0))),
])
);
}
#[test]
- fn eval_pop() {
- assert_eq!(Ok(5), eval(&vec![Const(5), Const(10), Pop, Exit]));
- }
-
- #[test]
- fn eval_if_true() {
+ fn eval_peek() {
assert_eq!(
Ok(5),
- eval(&vec![Const(5), Const(1), If(2), Const(5), Add, Exit])
- );
- }
-
- #[test]
- fn eval_if_false() {
- assert_eq!(
- Ok(10),
- eval(&vec![Const(5), Const(0), If(2), Const(5), Add, Exit])
+ eval(&vec![
+ Alloc(Local(0), Const(Value::from_int(1))),
+ Poke(
+ Const(Value::from_int(5)),
+ L(Local(0)),
+ Const(Value::from_int(0))
+ ),
+ Peek(Local(1), L(Local(0)), Const(Value::from_int(0))),
+ Exit(L(Local(1))),
+ ])
);
}
#[test]
- fn eval_call() {
+ fn eval_poke() {
assert_eq!(
- Ok(5),
- eval(&vec![Const(3), Const(5), Call(1), Local(1), Exit])
+ Ok(0),
+ eval(&vec![
+ Alloc(Local(0), Const(Value::from_int(1))),
+ Poke(
+ Const(Value::from_int(5)),
+ L(Local(0)),
+ Const(Value::from_int(0))
+ ),
+ Exit(Const(Value::from_int(0))),
+ ])
);
}
#[test]
- fn eval_ret() {
+ fn eval_peek_byte() {
assert_eq!(
- Ok(5),
- eval(&vec![Const(4), Const(5), Call(1), Exit, Local(1), Ret])
+ Ok(0),
+ eval(&vec![
+ AllocBytevector(Local(0), Const(Value::from_int(1))),
+ PeekByte(Local(1), L(Local(0)), Const(Value::from_int(0))),
+ Exit(L(Local(1))),
+ ])
);
}
#[test]
- fn eval_putc() {
+ fn eval_poke_byte() {
assert_eq!(
- Ok(5),
+ Ok(0),
eval(&vec![
- Const(5),
- Const(88),
- Const(1),
- PutC,
- If(2),
- Const(5),
- Add,
- Exit
+ AllocBytevector(Local(0), Const(Value::from_int(1))),
+ PokeByte(
+ Const(Value::from_int(5)),
+ L(Local(0)),
+ Const(Value::from_int(0))
+ ),
+ Exit(Const(Value::from_int(0))),
])
);
}
-
- #[test]
- fn eval_getc_err() {
- assert_eq!(Ok(0), eval(&vec![Const(1), GetC, Exit]));
- }
}
diff --git a/bytecode/src/collector.rs b/bytecode/src/collector.rs
deleted file mode 100644
index 38a2cf9..0000000
--- a/bytecode/src/collector.rs
+++ /dev/null
@@ -1 +0,0 @@
-fn collect_garbage(heap:
diff --git a/bytecode/src/data.rs b/bytecode/src/data.rs
index 4331f46..d6f9bd2 100644
--- a/bytecode/src/data.rs
+++ b/bytecode/src/data.rs
@@ -1,4 +1,7 @@
+use std::fmt;
+
// The data representations of values of different types are given below.
+// - 0 represents an undefined value. Any operation with 0 will cause an error.
// - Pointers are 64 bit unsigned (positive) ints which are
// word-aligned, i.e. 0 mod 8. All types not listed below are
// allocated on the heap behind a pointer.
@@ -18,12 +21,17 @@
pub struct Pointer(pub usize);
impl Pointer {
- pub fn offset(self, i: i64) -> Self {
+ pub fn offset(self, i: usize) -> Self {
let Pointer(u) = self;
- if i > 0 {
- return Pointer(u + usize::try_from(i).unwrap());
- }
- return Pointer(u - usize::try_from(-i).unwrap());
+ Pointer(u + i)
+ }
+}
+
+impl fmt::LowerHex for Pointer {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let val = self.0;
+
+ fmt::LowerHex::fmt(&val, f)
}
}
@@ -31,14 +39,9 @@ impl Pointer {
pub struct Value(pub u64);
impl Value {
- pub fn is_nil(self) -> bool {
- let Value(stack_representation) = self;
- return stack_representation == 0;
- }
-
pub fn is_pointer(self) -> bool {
let Value(stack_representation) = self;
- return stack_representation & 0x7 == 0;
+ return stack_representation != 0 && stack_representation & 0x7 == 0;
}
pub fn from_pointer(p: Pointer) -> Self {
@@ -71,47 +74,12 @@ impl Value {
return Ok(stack_representation as i64 >> 1);
}
- pub fn is_bool(self) -> bool {
- let Value(stack_representation) = self;
- return stack_representation == 0xa || stack_representation == 0x2;
- }
-
pub fn from_bool(b: bool) -> Self {
if b {
return Value(0xa);
}
return Value(0x2);
}
-
- pub fn to_bool(self) -> Result<bool, String> {
- let Value(stack_representation) = self;
- if !self.is_bool() {
- return Err(format!("value {:x} is not a boolean", stack_representation));
- }
- return Ok(stack_representation == 0xa);
- }
-
- pub fn is_char(self) -> bool {
- let Value(stack_representation) = self;
- return stack_representation & 0x7 == 4;
- }
-
- pub fn from_char(c: char) -> Self {
- return Value(u64::from(c) << 32 | 0x4);
- }
-
- pub fn to_char(self) -> Result<char, String> {
- let Value(stack_representation) = self;
- if !self.is_char() {
- return Err(format!("value {:x} is not a char", stack_representation));
- }
- return Ok(
- char::from_u32((stack_representation >> 32) as u32).ok_or(format!(
- "value {:x} is not a valid char",
- stack_representation >> 32
- ))?,
- );
- }
}
#[cfg(test)]
@@ -119,16 +87,6 @@ mod tests {
use super::*;
#[test]
- fn is_nil() {
- assert_eq!(true, Value(0).is_nil());
- }
-
- #[test]
- fn is_not_nil() {
- assert_eq!(false, Value(1).is_nil());
- }
-
- #[test]
fn is_pointer() {
assert_eq!(true, Value(0xf8).is_pointer());
}
@@ -169,21 +127,6 @@ mod tests {
}
#[test]
- fn true_is_bool() {
- assert_eq!(true, Value(0xa).is_bool());
- }
-
- #[test]
- fn false_is_bool() {
- assert_eq!(true, Value(2).is_bool());
- }
-
- #[test]
- fn is_not_bool() {
- assert_eq!(false, Value(0).is_bool());
- }
-
- #[test]
fn true_from_bool() {
assert_eq!(Value(0xa), Value::from_bool(true));
}
@@ -192,34 +135,4 @@ mod tests {
fn false_from_bool() {
assert_eq!(Value(2), Value::from_bool(false));
}
-
- #[test]
- fn true_to_bool() {
- assert_eq!(Ok(true), Value(0xa).to_bool());
- }
-
- #[test]
- fn false_to_bool() {
- assert_eq!(Ok(false), Value(2).to_bool());
- }
-
- #[test]
- fn is_char() {
- assert_eq!(true, Value(0x6100000004).is_char());
- }
-
- #[test]
- fn is_not_char() {
- assert_eq!(false, Value(0).is_char());
- }
-
- #[test]
- fn from_char() {
- assert_eq!(Value(0x5800000004), Value::from_char('X'));
- }
-
- #[test]
- fn to_char() {
- assert_eq!(Ok('😂'), Value(0x1f60200000004).to_char());
- }
}
diff --git a/bytecode/src/encoding.rs b/bytecode/src/encoding.rs
index deacae5..8f49ab6 100644
--- a/bytecode/src/encoding.rs
+++ b/bytecode/src/encoding.rs
@@ -1,225 +1,170 @@
-use crate::bytecode::Op;
+// This file must stay in sync with encoding.csc.
+use crate::bytecode::{Arg, Local, Op};
+use crate::data::Value;
+use std::io;
use std::io::Read;
-fn read_tag<T: Read>(prog: &mut T) -> Result<u64, String> {
+fn read_const<T: Read>(prog: &mut T) -> Result<Value, String> {
let mut buf = [0; 8];
match prog.read_exact(&mut buf) {
Ok(()) => (),
- Err(_) => {
- return Err(String::from("error reading input"));
+ Err(e) => {
+ return Err(format!("error reading input: {}", e));
}
};
- Ok(u64::from_le_bytes(buf))
+ Ok(Value(u64::from_le_bytes(buf)))
}
-fn read_i64<T: Read>(prog: &mut T) -> Result<i64, String> {
- let mut buf = [0; 8];
+fn read_local<T: Read>(prog: &mut T) -> Result<Local, String> {
+ let mut buf = [0; 1];
match prog.read_exact(&mut buf) {
Ok(()) => (),
- Err(_) => {
- return Err(String::from("error reading input"));
+ Err(e) => {
+ return Err(format!("error reading input: {}", e));
}
};
- Ok(i64::from_le_bytes(buf))
+ Ok(Local(buf[0]))
}
-fn read_u8<T: Read>(prog: &mut T) -> Result<u8, String> {
- let mut buf = vec![0];
+fn read_arg<T: Read>(prog: &mut T, is_const: bool) -> Result<Arg, String> {
+ if is_const {
+ let c = read_const(prog)?;
+ return Ok(Arg::Const(c));
+ }
+ let l = read_local(prog)?;
+ Ok(Arg::L(l))
+}
+
+fn op_decoding<T: Read>(prog: &mut T) -> Result<Option<Op>, String> {
+ let mut buf = [0; 1];
match prog.read_exact(&mut buf) {
Ok(()) => (),
- Err(_) => {
- return Err(String::from("error reading input"));
+ Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => {
+ return Ok(None);
+ }
+ Err(e) => {
+ return Err(format!("error reading input: {}", e));
}
};
- Ok(buf[0])
-}
-
-fn op_decoding<T: Read>(prog: &mut T) -> Result<Op, String> {
- let op = match read_tag(prog)? {
- 1010 => Op::Const(read_i64(prog)?),
- 1020 => Op::Add,
- 1030 => Op::Sub,
- 1040 => Op::Mul,
- 1050 => Op::Div,
- 1060 => Op::Mod,
- 2010 => Op::Alloc,
- 2020 => Op::Peek,
- 2030 => Op::Poke,
- 2040 => Op::PeekByte,
- 2050 => Op::PokeByte,
- 3010 => Op::Pop,
- 3020 => Op::Local(read_u8(prog)?),
- 4010 => Op::If(read_i64(prog)?),
- 4020 => Op::Call(read_u8(prog)?),
- 4030 => Op::Ret,
- 4040 => Op::Exit,
- 5010 => Op::PutC,
- 5020 => Op::GetC,
+ let tag = buf[0] >> 2;
+ let arg1_const = buf[0] & 2 != 0;
+ let arg2_const = buf[0] & 1 != 0;
+ let op = match tag {
+ // Note: arguments are evaluated left to right.
+ 0 => Op::Mov(read_local(prog)?, read_arg(prog, arg1_const)?),
+ 1 => Op::JmpIf(read_arg(prog, arg1_const)?, read_arg(prog, arg2_const)?),
+ 2 => Op::Jmp(read_arg(prog, arg1_const)?),
+ 3 => Op::Alloc(read_local(prog)?, read_arg(prog, arg1_const)?),
+ 4 => Op::Peek(
+ read_local(prog)?,
+ read_arg(prog, arg1_const)?,
+ read_arg(prog, arg2_const)?,
+ ),
+ 5 => Op::Poke(
+ read_arg(prog, arg1_const)?,
+ read_arg(prog, false)?,
+ read_arg(prog, arg2_const)?,
+ ),
+ 6 => Op::Add(
+ read_local(prog)?,
+ read_arg(prog, arg1_const)?,
+ read_arg(prog, arg2_const)?,
+ ),
+ 7 => Op::Sub(
+ read_local(prog)?,
+ read_arg(prog, arg1_const)?,
+ read_arg(prog, arg2_const)?,
+ ),
+ 8 => Op::Mul(
+ read_local(prog)?,
+ read_arg(prog, arg1_const)?,
+ read_arg(prog, arg2_const)?,
+ ),
+ 9 => Op::Div(
+ read_local(prog)?,
+ read_arg(prog, arg1_const)?,
+ read_arg(prog, arg2_const)?,
+ ),
+ 10 => Op::Mod(
+ read_local(prog)?,
+ read_arg(prog, arg1_const)?,
+ read_arg(prog, arg2_const)?,
+ ),
+ 11 => Op::PeekByte(
+ read_local(prog)?,
+ read_arg(prog, arg1_const)?,
+ read_arg(prog, arg2_const)?,
+ ),
+ 12 => Op::PokeByte(
+ read_arg(prog, arg1_const)?,
+ read_arg(prog, false)?,
+ read_arg(prog, arg2_const)?,
+ ),
+ 13 => Op::Exit(read_arg(prog, arg1_const)?),
+ 14 => Op::AllocBytevector(read_local(prog)?, read_arg(prog, arg1_const)?),
_ => {
return Err(String::from("invalid opcode"));
}
};
- Ok(op)
+ Ok(Some(op))
}
-pub fn decode(prog: &[u8]) -> Result<Vec<Op>, String> {
- let mut reader = prog;
+pub fn decode<T: Read>(prog: &mut T) -> Result<Vec<Op>, String> {
let mut out = Vec::new();
- while reader.len() > 0 {
- out.push(op_decoding(&mut reader)?);
+ loop {
+ match op_decoding(prog)? {
+ Some(op) => {
+ out.push(op);
+ }
+ None => {
+ return Ok(out);
+ }
+ }
}
- Ok(out)
}
mod tests {
use super::*;
+ use Arg::*;
+ use Op::*;
#[test]
- fn decode_const() {
+ fn decode_mov_const() {
assert_eq!(
- Ok(vec![Op::Const(10)]),
- decode(&vec![0xf2, 0x3, 0, 0, 0, 0, 0, 0, 0xa, 0, 0, 0, 0, 0, 0, 0])
+ Ok(vec![Mov(Local(0), Const(Value::from_int(10)))]),
+ decode(&mut vec![0x2, 0, 0x15, 0, 0, 0, 0, 0, 0, 0].as_slice())
);
}
#[test]
- fn decode_add() {
+ fn decode_mov_local() {
assert_eq!(
- Ok(vec![Op::Add]),
- decode(&vec![0xfc, 0x3, 0, 0, 0, 0, 0, 0])
+ Ok(vec![Mov(Local(0), L(Local(1)))]),
+ decode(&mut vec![0, 0, 1].as_slice()),
);
}
#[test]
- fn decode_sub() {
- assert_eq!(Ok(vec![Op::Sub]), decode(&vec![0x6, 0x4, 0, 0, 0, 0, 0, 0]));
- }
-
- #[test]
- fn decode_mul() {
+ fn decode_jmpif() {
assert_eq!(
- Ok(vec![Op::Mul]),
- decode(&vec![0x10, 0x4, 0, 0, 0, 0, 0, 0])
+ Ok(vec![JmpIf(L(Local(0)), L(Local(1)))]),
+ decode(&mut vec![4, 0, 1].as_slice())
);
}
#[test]
- fn decode_div() {
+ fn decode_jmp() {
assert_eq!(
- Ok(vec![Op::Div]),
- decode(&vec![0x1a, 0x4, 0, 0, 0, 0, 0, 0])
- );
- }
-
- #[test]
- fn decode_mod() {
- assert_eq!(
- Ok(vec![Op::Mod]),
- decode(&vec![0x24, 0x4, 0, 0, 0, 0, 0, 0])
+ Ok(vec![Jmp(L(Local(0)))]),
+ decode(&mut vec![8, 0].as_slice())
);
}
#[test]
fn decode_alloc() {
assert_eq!(
- Ok(vec![Op::Alloc]),
- decode(&vec![0xda, 0x7, 0, 0, 0, 0, 0, 0])
- );
- }
-
- #[test]
- fn decode_peek() {
- assert_eq!(
- Ok(vec![Op::Peek]),
- decode(&vec![0xe4, 0x7, 0, 0, 0, 0, 0, 0])
- );
- }
-
- #[test]
- fn decode_poke() {
- assert_eq!(
- Ok(vec![Op::Poke]),
- decode(&vec![0xee, 0x7, 0, 0, 0, 0, 0, 0])
- );
- }
-
- #[test]
- fn decode_peek_byte() {
- assert_eq!(
- Ok(vec![Op::PeekByte]),
- decode(&vec![0xf8, 0x7, 0, 0, 0, 0, 0, 0])
- );
- }
-
- #[test]
- fn decode_poke_byte() {
- assert_eq!(
- Ok(vec![Op::PokeByte]),
- decode(&vec![0x2, 0x8, 0, 0, 0, 0, 0, 0])
- );
- }
-
- #[test]
- fn decode_pop() {
- assert_eq!(
- Ok(vec![Op::Pop]),
- decode(&vec![0xc2, 0xb, 0, 0, 0, 0, 0, 0])
- );
- }
-
- #[test]
- fn decode_local() {
- assert_eq!(
- Ok(vec![Op::Local(10)]),
- decode(&vec![0xcc, 0xb, 0, 0, 0, 0, 0, 0, 0xa])
- );
- }
-
- #[test]
- fn decode_if() {
- assert_eq!(
- Ok(vec![Op::If(10)]),
- decode(&vec![0xaa, 0xf, 0, 0, 0, 0, 0, 0, 0xa, 0, 0, 0, 0, 0, 0, 0])
- );
- }
-
- #[test]
- fn decode_call() {
- assert_eq!(
- Ok(vec![Op::Call(10)]),
- decode(&vec![0xb4, 0xf, 0, 0, 0, 0, 0, 0, 0xa])
- );
- }
-
- #[test]
- fn decode_ret() {
- assert_eq!(
- Ok(vec![Op::Ret]),
- decode(&vec![0xbe, 0xf, 0, 0, 0, 0, 0, 0])
- );
- }
-
- #[test]
- fn decode_exit() {
- assert_eq!(
- Ok(vec![Op::Exit]),
- decode(&vec![0xc8, 0xf, 0, 0, 0, 0, 0, 0])
- );
- }
-
- #[test]
- fn decode_putc() {
- assert_eq!(
- Ok(vec![Op::PutC]),
- decode(&vec![0x92, 0x13, 0, 0, 0, 0, 0, 0])
- );
- }
-
- #[test]
- fn decode_getc() {
- assert_eq!(
- Ok(vec![Op::GetC]),
- decode(&vec![0x9c, 0x13, 0, 0, 0, 0, 0, 0])
+ Ok(vec![Alloc(Local(0), L(Local(1)))]),
+ decode(&mut vec![12, 0, 1].as_slice())
);
}
}
diff --git a/bytecode/src/heap.rs b/bytecode/src/heap.rs
index 1706d10..d32283e 100644
--- a/bytecode/src/heap.rs
+++ b/bytecode/src/heap.rs
@@ -32,6 +32,7 @@ pub struct Heap {
heap: Vec<u64>,
free_pointer: usize,
spare_heap: Vec<u64>,
+ last_reachable_cells: usize,
}
impl Heap {
@@ -40,13 +41,14 @@ impl Heap {
heap: vec![0; 512], // 4 kB
free_pointer: 0,
spare_heap: vec![0; 512],
+ last_reachable_cells: 0,
}
}
fn alloc_size(&mut self, p: Pointer) -> Result<usize, String> {
let (u, _) = open_ptr(p);
if u == 0 {
- return Err("cannot get the size of a nil pointer");
+ return Err(String::from("cannot get the size of a nil pointer"));
}
// Alloc size is stored just below the pointer.
let size = self.heap[u - 1];
@@ -105,29 +107,12 @@ impl Heap {
return Ok(());
}
- fn collect_garbage(
- &mut self,
- size_hint: usize,
- stack: &mut [Value],
- locals: &mut [Value],
- ) -> Result<(), String> {
- const MAX_HEAP_SIZE: usize = 4 * 1024 * 1024; // 4 GB
- // Always at least double the heap size (keeping in mind the max heap size).
- let mut size_hint = size_hint;
- if size_hint < self.heap.len() {
- size_hint = self.heap.len();
- }
- let mut new_heap_size = self.heap.len() + size_hint;
- if new_heap_size > MAX_HEAP_SIZE / 2 {
- new_heap_size = MAX_HEAP_SIZE / 2;
- }
- self.spare_heap.resize(new_heap_size, 0);
+ fn collect_garbage(&mut self, locals: &mut [Value]) -> Result<(), String> {
+ self.spare_heap.resize(self.heap.len(), 0);
let mut spare_heap_ptr = 0;
let mut rewrites = HashMap::new();
- self.walk_gc_roots(stack, &mut spare_heap_ptr, &mut rewrites)?;
self.walk_gc_roots(locals, &mut spare_heap_ptr, &mut rewrites)?;
- // Walk the stacks and rewrite.
- rewrite_pointers(stack, &rewrites)?;
+ // Rewrite values.
rewrite_pointers(locals, &rewrites)?;
// Activate the new heap!
std::mem::swap(&mut self.heap, &mut self.spare_heap);
@@ -160,6 +145,7 @@ impl Heap {
)?;
}
}
+ self.last_reachable_cells = spare_heap_ptr;
// Done??
return Ok(());
}
@@ -167,42 +153,34 @@ impl Heap {
fn alloc_b(
&mut self,
n: usize,
- stack: &mut [Value],
locals: &mut [Value],
bytevector_p: bool,
) -> Result<Pointer, String> {
+ if self.free_pointer > 2 * self.last_reachable_cells {
+ self.collect_garbage(locals)?;
+ }
let n_cells = (n + 7) / 8;
- if self.heap.len() - self.free_pointer < n_cells {
- self.collect_garbage(n, stack, locals)?;
+ if self.free_pointer + n_cells + 1 > self.heap.len() {
+ self.heap.resize(self.free_pointer + n_cells + 1, 0);
}
- let len_p = &mut self.heap[self.free_pointer]?;
+ let len_p = &mut self.heap[self.free_pointer];
*len_p = u64::try_from(n).unwrap() << 1;
if bytevector_p {
*len_p |= 1;
}
self.free_pointer += 1;
let p = Pointer(self.free_pointer * 8);
- let n_cells = n / 8;
- self.heap[self.free_pointer..self.free_pointer + n].fill(0);
+ self.heap[self.free_pointer..self.free_pointer + n_cells].fill(0);
+ self.free_pointer += n_cells;
return Ok(p);
}
- pub fn alloc(
- &mut self,
- n: usize,
- stack: &mut [Value],
- locals: &mut [Value],
- ) -> Result<Pointer, String> {
- return self.alloc_b(n, stack, locals, false);
+ pub fn alloc(&mut self, n: usize, locals: &mut [Value]) -> Result<Pointer, String> {
+ return self.alloc_b(n * 8, locals, false);
}
- pub fn alloc_bytevector(
- &mut self,
- n: usize,
- stack: &mut [Value],
- locals: &mut [Value],
- ) -> Result<Pointer, String> {
- return self.alloc_b(n, stack, locals, true);
+ pub fn alloc_bytevector(&mut self, n: usize, locals: &mut [Value]) -> Result<Pointer, String> {
+ return self.alloc_b(n, locals, true);
}
pub fn peek(&self, p: Pointer) -> Result<Value, String> {
@@ -218,16 +196,17 @@ impl Heap {
let Value(x) = v;
if u >= self.heap.len() {
return Err(format!("invalid pointer {:x}", p));
+ }
self.heap[u] = x;
return Ok(());
}
- fn peek_byte(&self, p: Pointer) -> Result<u8, String> {
+ pub fn peek_byte(&self, p: Pointer) -> Result<u8, String> {
let (word_cnt, word_offset) = open_ptr(p);
if word_cnt >= self.heap.len() {
return Err(format!("invalid pointer {:x}", p));
}
- return (self.heap[word_cnt] >> word_offset * 8) as u8;
+ Ok((self.heap[word_cnt] >> word_offset * 8) as u8)
}
pub fn poke_byte(&mut self, u: u8, p: Pointer) -> Result<(), String> {
@@ -237,6 +216,7 @@ impl Heap {
}
let surrounding_word = self.heap[word_cnt];
let mask = !(0xff << word_offset * 8);
- self.heap[word_cnt] = surrounding_word & mask | u << word_offset * 8;
+ self.heap[word_cnt] = surrounding_word & mask | u64::from(u) << word_offset * 8;
+ Ok(())
}
}
diff --git a/bytecode/src/main.rs b/bytecode/src/main.rs
index 683ff26..8315d5a 100644
--- a/bytecode/src/main.rs
+++ b/bytecode/src/main.rs
@@ -3,12 +3,8 @@ mod data;
mod encoding;
mod heap;
-use std::io::Read;
-
fn main() {
- let mut buf = Vec::new();
- std::io::stdin().lock().read_to_end(&mut buf).unwrap();
- let prog = encoding::decode(&buf).unwrap();
+ let prog = encoding::decode(&mut std::io::stdin().lock()).unwrap();
let exit_code = bytecode::eval(&prog).unwrap();
std::process::exit(i32::from(exit_code));
}
diff --git a/bytecode/src/stack.rs b/bytecode/src/stack.rs
deleted file mode 100644
index 66d3f7c..0000000
--- a/bytecode/src/stack.rs
+++ /dev/null
@@ -1,33 +0,0 @@
-struct Stack {
- v: Vec<u64>,
-}
-
-impl Stack {
- fn pop(&mut self) -> Result<u64, String> {
- self.v.pop().ok_or(String::from("stack underflow"))
- }
-
- fn pop_int(&mut self) -> Result<i64, String> {
- Ok(self.pop()? as i64 >> 1)
- }
-
- fn push_int(&mut self, i: i64) {
- self.v.push((i as u64) << 1 | 1);
- }
-
- fn pop_pointer(&mut self) -> Result<Pointer, String> {
- Ok(Pointer::from_bytes(self.pop()?))
- }
-
- fn push_pointer(&mut self, p: Pointer) {
- self.v.push(p.bytes());
- }
-
- fn pop_usize(&mut self) -> Result<usize, String> {
- Ok(usize::try_from(self.pop()?).unwrap() >> 1)
- }
-
- fn push_usize(&mut self, p: usize) {
- self.v.push(u64::try_from(p).unwrap() << 1 | 1);
- }
-}