mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 71e8dbcd97 |
@@ -5,11 +5,7 @@ updates:
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
cooldown:
|
||||
default-days: 7
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
AllCops:
|
||||
DisabledByDefault: true
|
||||
TargetRubyVersion: 3.4
|
||||
|
||||
Layout/AssignmentIndentation:
|
||||
Enabled: true
|
||||
|
||||
Layout/BlockEndNewline:
|
||||
Enabled: true
|
||||
|
||||
Layout/IndentationStyle:
|
||||
Enabled: true
|
||||
EnforcedStyle: spaces
|
||||
@@ -1,26 +1,10 @@
|
||||
{
|
||||
"retryOn429": true,
|
||||
"retryCount": 3,
|
||||
"aliveStatusCodes": [
|
||||
200,
|
||||
206,
|
||||
502
|
||||
],
|
||||
"ignorePatterns": [
|
||||
{
|
||||
"pattern": "^https://github.com/mruby/mruby/commit/"
|
||||
},
|
||||
{
|
||||
"pattern": "^http://jp.rubyist.net/magazine/\\?0034-Enumerable_lz"
|
||||
},
|
||||
{
|
||||
"pattern": "^https://ruby-doc.org/core-2.3.3"
|
||||
},
|
||||
{
|
||||
"pattern": "^mailto:"
|
||||
},
|
||||
{
|
||||
"pattern": "^https://www.iso.org/"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
LD: ${{ matrix.cc }}
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
- name: Ruby version
|
||||
run: ruby -v
|
||||
- name: Compiler version
|
||||
@@ -39,30 +39,6 @@ jobs:
|
||||
- name: Build and test
|
||||
run: rake -m test:run:serial
|
||||
|
||||
Cosmopolitan:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v6
|
||||
- name: Ruby version
|
||||
run: ruby -v
|
||||
- name: Cache cosmocc
|
||||
uses: actions/cache@v5
|
||||
id: cache-cosmocc
|
||||
with:
|
||||
path: ~/cosmo
|
||||
key: cosmocc-${{ runner.os }}-20260104
|
||||
- name: Install cosmocc
|
||||
if: steps.cache-cosmocc.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
mkdir -p ~/cosmo && cd ~/cosmo
|
||||
wget https://cosmo.zip/pub/cosmocc/cosmocc.zip
|
||||
unzip cosmocc.zip
|
||||
- name: Build and test
|
||||
run: |
|
||||
COSMO_ROOT=~/cosmo rake -m test:run:serial MRUBY_CONFIG=cosmopolitan
|
||||
|
||||
Windows-VC:
|
||||
runs-on: windows-2022
|
||||
timeout-minutes: 10
|
||||
@@ -70,7 +46,7 @@ jobs:
|
||||
MRUBY_CONFIG: ci/msvc
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
- name: Ruby version
|
||||
run: ruby -v
|
||||
- name: Build and test
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
name: CodeQL Analysis
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
jobs:
|
||||
codeql:
|
||||
name: CodeQL
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: ["actions"]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v4
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v4
|
||||
with:
|
||||
category: "Security"
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
LDFLAGS: --coverage
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
- name: Ruby version
|
||||
run: ruby -v
|
||||
- name: Compiler version
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
echo \`\`\`
|
||||
} > "$GITHUB_STEP_SUMMARY"
|
||||
- name: Upload coverage report
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: coverage-${{ github.sha }}
|
||||
path: coverage/
|
||||
|
||||
@@ -9,7 +9,7 @@ jobs:
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/labeler@v6
|
||||
- uses: actions/labeler@v5
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
sync-labels: true
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
# https://pre-commit.com/
|
||||
name: Lint
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
pre-commit:
|
||||
name: Run pre-commit
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v4
|
||||
- name: Install
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install pre-commit
|
||||
- name: Set PY
|
||||
run: echo "PY=$(python -VV | sha256sum | cut -d' ' -f1)" >> "$GITHUB_ENV"
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/pre-commit
|
||||
key: pre-commit|${{ env.PY }}|${{ hashFiles('.pre-commit-config.yaml') }}
|
||||
- name: Run pre-commit
|
||||
run: pre-commit run --all-files
|
||||
ls-lint:
|
||||
name: Run ls-lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ls-lint/action@v2.3.1
|
||||
with:
|
||||
config: .github/linters/.ls-lint.yml
|
||||
@@ -1,17 +0,0 @@
|
||||
# https://github.com/loeffel-io/ls-lint
|
||||
name: Lint
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
ls-lint:
|
||||
name: Run ls-lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: ls-lint/action@v2.3.1
|
||||
with:
|
||||
config: .github/linters/.ls-lint.yml
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
fuzz-seconds: 600
|
||||
dry-run: false
|
||||
- name: Upload Crash
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: artifacts
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
# https://github.com/j178/prek
|
||||
name: pre-commit
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
pre-commit:
|
||||
name: Run pre-commit
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v6
|
||||
- uses: j178/prek-action@v1
|
||||
with:
|
||||
extra-args: --all-files
|
||||
- name: Run manual pre-commit hooks
|
||||
run: prek run --color=always --all-files --hook-stage manual
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref_name }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
- name: Builds
|
||||
id: builds
|
||||
run: |
|
||||
|
||||
@@ -15,12 +15,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# Full git history is needed to get a proper list of changed files within `super-linter`
|
||||
fetch-depth: 0
|
||||
- name: Lint Code Base
|
||||
uses: super-linter/super-linter/slim@v8.5.0
|
||||
uses: super-linter/super-linter/slim@v7.4.0
|
||||
env:
|
||||
# VALIDATE_BASH_EXEC: true
|
||||
VALIDATE_DOCKERFILE_HADOLINT: true
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ benchmark/**/*.dat
|
||||
benchmark/*.pdf
|
||||
benchmark/*.png
|
||||
bin
|
||||
/build
|
||||
build
|
||||
doc/api
|
||||
doc/capi
|
||||
compile_commands.json
|
||||
|
||||
+23
-33
@@ -1,9 +1,11 @@
|
||||
---
|
||||
# https://github.com/j178/prek
|
||||
# https://pre-commit.com/
|
||||
default_stages: [pre-commit, pre-push]
|
||||
default_language_version:
|
||||
# force all unspecified Python hooks to run python3
|
||||
python: python3
|
||||
node: 24.11.1
|
||||
# force all unspecified Node hooks to run Node.js v22.15.0 LTS
|
||||
node: 22.15.0
|
||||
minimum_pre_commit_version: "3.2.0"
|
||||
exclude: "^tools/lrama/"
|
||||
repos:
|
||||
@@ -20,35 +22,25 @@ repos:
|
||||
- id: prettier
|
||||
name: run prettier
|
||||
description: format files with prettier
|
||||
entry: prettier --write '**/*.json' '**/*.md' '**/*.yaml' '**/*.yml'
|
||||
entry: prettier --write .
|
||||
files: \.(json|md|ya?ml)$
|
||||
language: node
|
||||
additional_dependencies: ["prettier@3.7.4"]
|
||||
stages: [manual]
|
||||
- id: check-zip-file-is-not-committed
|
||||
name: disallow zip files
|
||||
description: Zip files are not allowed in the repository
|
||||
language: fail
|
||||
entry: |
|
||||
Zip files are not allowed in the repository as they are hard to
|
||||
track and have security implications. Please remove the zip file from the repository.
|
||||
files: \.zip$
|
||||
additional_dependencies: ["prettier@3.5.3"]
|
||||
- repo: https://github.com/gitleaks/gitleaks
|
||||
rev: v8.30.0
|
||||
rev: v8.25.1
|
||||
hooks:
|
||||
- id: gitleaks
|
||||
name: run gitleaks
|
||||
description: detect hardcoded secrets with gitleaks
|
||||
- repo: https://github.com/oxipng/oxipng
|
||||
rev: v10.0.0
|
||||
- repo: https://github.com/shssoichiro/oxipng
|
||||
rev: v9.1.5
|
||||
hooks:
|
||||
- id: oxipng
|
||||
name: run oxipng
|
||||
description: use lossless compression to optimize PNG files
|
||||
args: ["--fix", "-o", "4", "--strip", "safe", "--alpha"]
|
||||
stages: [manual]
|
||||
args: ["-o", "4", "--strip", "safe", "--alpha"]
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v6.0.0
|
||||
rev: v5.0.0
|
||||
hooks:
|
||||
- id: check-added-large-files
|
||||
- id: check-case-conflict
|
||||
@@ -87,7 +79,7 @@ repos:
|
||||
args: [--whitespaces-count, "2"]
|
||||
exclude: Makefile$
|
||||
- repo: https://github.com/rhysd/actionlint
|
||||
rev: v1.7.9
|
||||
rev: v1.7.7
|
||||
hooks:
|
||||
- id: actionlint
|
||||
name: run actionlint
|
||||
@@ -98,8 +90,15 @@ repos:
|
||||
- id: codespell
|
||||
name: run codespell
|
||||
description: check spelling with codespell
|
||||
- repo: https://github.com/jumanjihouse/pre-commit-hooks
|
||||
rev: 3.0.0
|
||||
hooks:
|
||||
- id: script-must-not-have-extension
|
||||
name: run script-must-not-have-extension
|
||||
description: local policy is to exclude extension from all shell files
|
||||
types: [shell]
|
||||
- repo: https://github.com/igorshubovych/markdownlint-cli
|
||||
rev: v0.46.0
|
||||
rev: v0.44.0
|
||||
hooks:
|
||||
- id: markdownlint
|
||||
name: run markdownlint
|
||||
@@ -108,31 +107,22 @@ repos:
|
||||
types: [markdown]
|
||||
files: \.md$
|
||||
- repo: https://github.com/tcort/markdown-link-check
|
||||
rev: v3.14.2
|
||||
rev: v3.13.7
|
||||
hooks:
|
||||
- id: markdown-link-check
|
||||
name: run markdown-link-check
|
||||
description: checks hyperlinks in Markdown files
|
||||
args: [--config=.github/linters/mlc_config.json, -q]
|
||||
stages: [manual]
|
||||
types: [markdown]
|
||||
files: \.md$
|
||||
- repo: https://github.com/rubocop/rubocop
|
||||
rev: v1.81.7
|
||||
hooks:
|
||||
- id: rubocop
|
||||
name: run rubocop
|
||||
description: RuboCop is a Ruby code style checker (linter) and formatter based on the community-driven Ruby Style Guide
|
||||
exclude: ^test/t/syntax\.rb$
|
||||
args: [--config=.github/linters/.rubocop.yml]
|
||||
- repo: https://github.com/shellcheck-py/shellcheck-py
|
||||
rev: v0.11.0.1
|
||||
rev: v0.10.0.1
|
||||
hooks:
|
||||
- id: shellcheck
|
||||
name: run shellcheck
|
||||
description: check shell scripts with a static analysis tool
|
||||
- repo: https://github.com/adrienverge/yamllint
|
||||
rev: v1.37.1
|
||||
rev: v1.37.0
|
||||
hooks:
|
||||
- id: yamllint
|
||||
name: run yamllint
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
# Authors of mruby (mruby developers)
|
||||
|
||||
## The List of Contributors sorted by number of commits (as of 2026-03-02 02877f0)
|
||||
## The List of Contributors sorted by number of commits (as of 2025-06-03 a3e7280)
|
||||
|
||||
7532 Yukihiro "Matz" Matsumoto (@matz)*
|
||||
712 dearblue (@dearblue)*
|
||||
6258 Yukihiro "Matz" Matsumoto (@matz)*
|
||||
681 dearblue (@dearblue)*
|
||||
587 KOBAYASHI Shuji (@shuujii)
|
||||
353 Daniel Bovensiepen (@bovi)*
|
||||
345 Takeshi Watanabe (@take-cheeze)*
|
||||
333 Masaki Muranaka (@monaka)
|
||||
255 John Bampton (@jbampton)
|
||||
234 Jun Hiroe (@suzukaze)
|
||||
233 John Bampton (@jbampton)
|
||||
228 Tomoyuki Sahara (@tsahara)*
|
||||
220 Cremno (@cremno)*
|
||||
209 Yuki Kurihara (@ksss)+
|
||||
@@ -43,7 +43,6 @@
|
||||
22 Zachary Scott (@zzak)*
|
||||
21 Ryan Lopopolo (@lopopolo)
|
||||
20 Ryan Scott (@ryan-scott-dev)*
|
||||
20 google-labs-jules[bot] (@google-jules)
|
||||
19 Bouke van der Bijl (@bouk)
|
||||
19 Jared Breeden (@jbreeden)*
|
||||
19 go kikuta (@gkta)*
|
||||
@@ -53,7 +52,6 @@
|
||||
18 leviongit (@leviongit)
|
||||
18 mirichi (@mirichi)
|
||||
17 Mitchell Blank Jr (@mitchblank)*
|
||||
16 HASUMI Hitoshi (@hasumikin)
|
||||
16 bggd (@bggd)
|
||||
16 kano4 (@kano4)
|
||||
15 Felix Jones (@felixjones)*
|
||||
@@ -63,11 +61,11 @@
|
||||
14 fn ⌃ ⌥ (@FnControlOption)
|
||||
13 Jose Narvaez (@goyox86)
|
||||
13 Patrick Hogan (@pbhogan)
|
||||
13 google-labs-jules[bot] (@google-jules)
|
||||
12 Akira Kuroda (@akuroda)
|
||||
12 Kouki Ooyatsu (kaishuu0123)*
|
||||
12 NAKAMURA Usaku (@unak)*
|
||||
12 Ray Chason (@chasonr)*
|
||||
12 SiZiOUS (@suzious)
|
||||
12 Takashi Sawanaka (@sdottaka)*
|
||||
12 Ukrainskiy Sergey (@ukrainskiysergey)
|
||||
12 Xuejie "Rafael" Xiao (@xxuejie)*
|
||||
@@ -76,7 +74,6 @@
|
||||
11 RIZAL Reckordp (@Reckordp)+
|
||||
11 Seeker (@SeekingMeaning)
|
||||
11 takkaw (@takkaw)
|
||||
10 Hendrik (@Asmod4n)
|
||||
10 Miura Hideki (@miura1729)
|
||||
10 Narihiro Nakamura (@authorNari)
|
||||
10 YAMAMOTO Masaya (pandax381)
|
||||
@@ -89,6 +86,7 @@
|
||||
7 Bhargava Shastry (@bshastry)*
|
||||
7 Kouichi Nakanishi (@keizo042)
|
||||
7 Rubyist (@expeditiousRubyist)
|
||||
7 SiZiOUS (@sizious)
|
||||
7 Simon Génier (@simon-shopify)
|
||||
7 Terence Lee (@hone)
|
||||
7 roco (@rystyle)*
|
||||
@@ -97,30 +95,28 @@
|
||||
6 Beoran (@beoran)
|
||||
6 David Siaw (@davidsiaw)*
|
||||
6 Frederick John Milens III (@fjmilens3)
|
||||
6 HASUMI Hitoshi (@hasumikin)
|
||||
6 Hiro Asari (@BanzaiMan)
|
||||
6 INOUE Yasuyuki (@yasuyuki)
|
||||
6 Junji Sawada (@junjis0203)
|
||||
6 Kenji Okimoto (@okkez)+
|
||||
6 Paweł Świątkowski (@katafrakt)
|
||||
6 Selman ULUG (@selman)
|
||||
6 Yusuke Endoh (@mame)*
|
||||
6 buty4649 (@buty4649)
|
||||
6 masahino (@masahino)
|
||||
5 Chris Reuter (@suetanvil)
|
||||
5 Davide D'Agostino (@DAddYE)
|
||||
5 Eric Hodel (@drbrain)
|
||||
5 Hendrik (@Asmod4n)
|
||||
5 Ichito Nagata (@i110)
|
||||
5 Keita Obo (@ktaobo)*
|
||||
5 Max Anselm (@silverhammermba)
|
||||
5 Rodrigo Malizia (@rmalizia44)+
|
||||
5 Ryan Davis (@zenspider)
|
||||
5 Syohei YOSHIDA (@syohex)
|
||||
5 TOMITA Masahiro (@tmtm)
|
||||
5 Yurie Yamane (@yurie)+
|
||||
5 dreamedge (@dreamedge)
|
||||
5 nkshigeru (@nkshigeru)
|
||||
5 xuejianqing (@joans321)
|
||||
4 Chris Hasiński (@khasinski)
|
||||
4 Dante Catalfamo (@dantecatalfamo)
|
||||
4 Goro Kikuchi (@gorogit)
|
||||
4 Herwin Weststrate (@herwinw)
|
||||
@@ -135,7 +131,6 @@
|
||||
4 NARUSE, Yui (@nurse)
|
||||
4 Ravil Bayramgalin (@brainopia)*+
|
||||
4 Satoshi Odawara (@SatoshiOdawara)
|
||||
4 UENO, M. (@eunos-1128)
|
||||
4 Yuhei Okazaki (@Yuuhei-Okazaki)*
|
||||
4 Yuji Yamano (@yyamano)
|
||||
4 kurodash (@kurodash)*
|
||||
@@ -152,7 +147,6 @@
|
||||
3 Jonas Minnberg (@sasq64)
|
||||
3 Joseph McCullough (@joequery)
|
||||
3 Mark McCurry (@fundamental)
|
||||
3 Meder Kydyraliev (@meder)
|
||||
3 Nobuhiro Iwamatsu (@iwamatsu)
|
||||
3 Per Lundberg (@perlun)*
|
||||
3 Rob Fors (@robfors)*
|
||||
@@ -205,9 +199,9 @@
|
||||
2 Tomás Pollak (@tomas)*
|
||||
2 Yutaka HARA (@yhara)*+
|
||||
2 Zhang Xiaohui (@hifoolno)
|
||||
2 buty4649 (@buty4649)
|
||||
2 icm7216 (@icm7216)
|
||||
1 A-Sat (@asatou)+
|
||||
1 AN Long (@aisk)
|
||||
1 Abinoam Praxedes Marques Junior (@abinoam)
|
||||
1 Alex Wang (@nanamiwang)+
|
||||
1 AlexDenisov (@AlexDenisov)
|
||||
@@ -270,15 +264,15 @@
|
||||
1 Nicholas (@knf)
|
||||
1 Nozomi SATO (@nozomiS)
|
||||
1 Okumura Takahiro (@hfm)
|
||||
1 Oliver Chang (@oliverchang)
|
||||
1 Patrick Ellis (@pje)
|
||||
1 Patrick Pokatilo (@SHyx0rmZ)
|
||||
1 Pavel Evstigneev (@Paxa)+
|
||||
1 Paweł Świątkowski (@katafrakt)
|
||||
1 Pete Kinnecom (@petekinnecom)
|
||||
1 Piotr Usewicz (@pusewicz)
|
||||
1 Prayag Verma (@pra85)
|
||||
1 Ranmocy (@ranmocy)
|
||||
1 Robert McNally (@wolfmcnally)
|
||||
1 Ryan Davis (@zenspider)
|
||||
1 Ryan Scott Lewis (@RyanScottLewis)
|
||||
1 Ryo Okubo (@syucream)
|
||||
1 SAkira a.k.a. Akira Suzuki (@sakisakira)
|
||||
|
||||
+26
-34
@@ -18,50 +18,41 @@ things in mind before submitting your pull request:
|
||||
- Use mrbgem to provide non ISO features (classes, modules and methods) unless
|
||||
you have a special reason to implement them in the core
|
||||
|
||||
## Security Issues
|
||||
## pre-commit
|
||||
|
||||
If you discover a security vulnerability:
|
||||
A framework for managing and maintaining multi-language `pre-commit` hooks.
|
||||
`pre-commit` can be [installed](https://pre-commit.com/#installation) with `pip`, `curl`, `brew` or `conda`.
|
||||
|
||||
- **High priority security vulnerabilities** (RCE): Report via email to <matz@ruby.or.jp>
|
||||
- **VM crashes from valid Ruby code**: Please report as regular bug reports on our issue tracker
|
||||
You need to first install `pre-commit` and then install the `pre-commit` hooks with `pre-commit install`.
|
||||
Now `pre-commit` will run automatically on git commit!
|
||||
|
||||
For detailed guidance on what qualifies as a security issue and what doesn't, see [SECURITY.md](SECURITY.md).
|
||||
It's usually a good idea to run the hooks against all the files when adding new hooks (usually `pre-commit`
|
||||
will only run on the changed files during git hooks). Use `pre-commit run --all-files` to check all files.
|
||||
|
||||
## prek
|
||||
To run a single hook use `pre-commit run --all-files <hook_id>`
|
||||
|
||||
We use [prek](https://github.com/j178/prek), a fast Rust-based pre-commit hook manager.
|
||||
It reads the standard `.pre-commit-config.yaml` format.
|
||||
|
||||
Install `prek` following the [installation guide](https://github.com/j178/prek#installation),
|
||||
then install the hooks with `prek install`.
|
||||
Now `prek` will run automatically on git commit!
|
||||
|
||||
It's usually a good idea to run the hooks against all the files when adding new hooks (usually `prek`
|
||||
will only run on the changed files during git hooks). Use `prek run --all-files` to check all files.
|
||||
|
||||
To run a single hook use `prek run --all-files <hook_id>`
|
||||
|
||||
To update use `prek autoupdate`
|
||||
To update use `pre-commit autoupdate`
|
||||
|
||||
Sometimes you might need to skip one or more hooks which can be done with the `SKIP` environment variable.
|
||||
|
||||
`$ SKIP=yamllint git commit -m "foo"`
|
||||
|
||||
For convenience, we have added `prek run --all-files`, `prek install` and `prek autoupdate`
|
||||
For convenience, we have added `pre-commit run --all-files`, `pre-commit install` and `pre-commit autoupdate`
|
||||
to both the Makefile and the Rakefile. Run them with:
|
||||
|
||||
- `make check` or `rake check`
|
||||
- `make checkinstall` or `rake checkinstall`
|
||||
- `make checkupdate` or `rake checkupdate`
|
||||
|
||||
To configure hooks you can modify the config file [.pre-commit-config.yaml](.pre-commit-config.yaml).
|
||||
We use [GitHub Actions](.github/workflows/pre-commit.yml) to run `prek` on every pull request.
|
||||
To configure `pre-commit` you can modify the config file [.pre-commit-config.yaml](.pre-commit-config.yaml).
|
||||
We use [GitHub Actions](.github/workflows/lint.yml) to run `pre-commit` on every pull request.
|
||||
|
||||
### prek quick links
|
||||
### pre-commit quick links
|
||||
|
||||
- [prek GitHub](https://github.com/j178/prek)
|
||||
- [Installation](https://github.com/j178/prek#installation)
|
||||
- [Usage](https://github.com/j178/prek#usage)
|
||||
- [Quick start](https://pre-commit.com/#quick-start)
|
||||
- [Usage](https://pre-commit.com/#usage)
|
||||
- [pre-commit autoupdate](https://pre-commit.com/#pre-commit-autoupdate)
|
||||
- [Temporarily disabling hooks](https://pre-commit.com/#temporarily-disabling-hooks)
|
||||
|
||||
## Docker
|
||||
|
||||
@@ -97,19 +88,20 @@ mruby-test latest ec60f9536948 29 seconds ago 1.29GB
|
||||
```
|
||||
|
||||
You can also run any custom `docker-compose` command which will override
|
||||
the default. For example to run `prek run --all-files` type:
|
||||
the default. For example to run `pre-commit run --all-files` type:
|
||||
|
||||
`$ docker-compose -p mruby run test prek run --all-files`
|
||||
`$ docker-compose -p mruby run test pre-commit run --all-files`
|
||||
|
||||
For convenience, you can also run `prek` with:
|
||||
For convenience, you can also run `pre-commit` with:
|
||||
|
||||
- `make composecheck`
|
||||
- `rake composecheck`
|
||||
|
||||
The bonus of running `prek` with `docker-compose` is that you won't need
|
||||
to install `prek` and the hooks on your local machine.
|
||||
The bonus of running `pre-commit` with `docker-compose` is that you won't need
|
||||
to install `pre-commit` and the hooks on your local machine. And that also
|
||||
means you won't need to install `brew`, `conda` or `pip`.
|
||||
|
||||
Note limitation: currently running `prek` with `docker-compose` we
|
||||
Note limitation: currently running `pre-commit` with `docker-compose` we
|
||||
skip the `check-executables-have-shebangs` hook.
|
||||
|
||||
Two more examples of custom `docker-compose` commands are:
|
||||
@@ -127,7 +119,7 @@ can use the `-f` flag:
|
||||
|
||||
## Spell Checking
|
||||
|
||||
We are using `prek` to run [codespell](https://github.com/codespell-project/codespell)
|
||||
We are using `pre-commit` to run [codespell](https://github.com/codespell-project/codespell)
|
||||
to check code for common misspellings. We have a small custom dictionary file [codespell.txt](.github/linters/codespell.txt).
|
||||
|
||||
## Coding conventions
|
||||
@@ -176,7 +168,7 @@ language itself. Please note the following hints for your Ruby code:
|
||||
#### Comply with the Ruby standard (ISO/IEC 30170:2012)
|
||||
|
||||
mruby is currently targeting to execute Ruby code which complies to ISO/IEC
|
||||
30170:2012 (<https://www.iso.org/standard/59579.html>),
|
||||
30170:2012 (<https://www.iso.org/iso/iso_catalogue/catalogue_tc/catalogue_detail.htm?csnumber=59579>),
|
||||
unless there's a clear reason, e.g. the latest Ruby has changed behavior from ISO.
|
||||
|
||||
## Building documentation
|
||||
|
||||
+2
-2
@@ -2,8 +2,8 @@ GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
coderay (1.1.3)
|
||||
rake (13.3.1)
|
||||
yard (0.9.38)
|
||||
rake (13.3.0)
|
||||
yard (0.9.37)
|
||||
yard-coderay (0.1.0)
|
||||
coderay
|
||||
yard
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
RAKE = rake
|
||||
DOCKER_COMPOSE = docker-compose
|
||||
PRE_COMMIT = prek
|
||||
PRE_COMMIT = pre-commit
|
||||
|
||||
define check_command
|
||||
@command -v $(1) >/dev/null 2>&1 || { \
|
||||
@@ -11,53 +11,46 @@ define check_command
|
||||
}
|
||||
endef
|
||||
|
||||
# For colors
|
||||
ifneq ($(shell tty -s),)
|
||||
CYAN := $(shell tput setaf 6)
|
||||
RESET := $(shell tput sgr0)
|
||||
else
|
||||
CYAN :=
|
||||
RESET :=
|
||||
endif
|
||||
|
||||
.PHONY: all test clean check checkinstall checkupdate composecheck composetest check_rake check_docker_compose check_pre_commit help
|
||||
.DEFAULT_GOAL := all
|
||||
|
||||
all: check_rake ## build all targets, install (locally) in-repo
|
||||
all : check_rake
|
||||
$(RAKE)
|
||||
.PHONY : all
|
||||
|
||||
test: check_rake all ## build and run all mruby tests
|
||||
test : check_rake all
|
||||
$(RAKE) test
|
||||
.PHONY : test
|
||||
|
||||
clean: check_rake ## clean all built and in-repo installed artifacts
|
||||
clean : check_rake
|
||||
$(RAKE) clean
|
||||
.PHONY : clean
|
||||
|
||||
check: check_pre_commit ## run all prek hooks against all files
|
||||
check : check_pre_commit
|
||||
$(PRE_COMMIT) run --all-files
|
||||
.PHONY : check
|
||||
|
||||
checkinstall: check_pre_commit ## install the prek hooks
|
||||
checkinstall : check_pre_commit
|
||||
$(PRE_COMMIT) install
|
||||
.PHONY : checkinstall
|
||||
|
||||
checkupdate: check_pre_commit ## check the prek hooks for updates
|
||||
checkupdate : check_pre_commit
|
||||
$(PRE_COMMIT) autoupdate
|
||||
.PHONY : checkupdate
|
||||
|
||||
composecheck: check_docker_compose check_pre_commit ## run all prek hooks against all files with docker-compose
|
||||
composecheck : check_docker_compose check_pre_commit
|
||||
$(DOCKER_COMPOSE) -p mruby run test $(PRE_COMMIT) run --all-files
|
||||
.PHONY : composecheck
|
||||
|
||||
composetest: check_docker_compose ## build and run all mruby tests with docker-compose
|
||||
composetest : check_docker_compose
|
||||
$(DOCKER_COMPOSE) -p mruby run test
|
||||
.PHONY : composetest
|
||||
|
||||
check_rake: ## check if Rake is installed
|
||||
check_rake:
|
||||
$(call check_command, $(RAKE))
|
||||
.PHONY : check_rake
|
||||
|
||||
check_docker_compose: ## check if docker-compose is installed
|
||||
check_docker_compose:
|
||||
$(call check_command, $(DOCKER_COMPOSE))
|
||||
.PHONY : check_docker_compose
|
||||
|
||||
check_pre_commit: ## check if prek is installed
|
||||
check_pre_commit:
|
||||
$(call check_command, $(PRE_COMMIT))
|
||||
|
||||
help: ## display this help message
|
||||
@echo "Usage: make <target>"
|
||||
@echo
|
||||
@echo "Available targets:"
|
||||
@grep -E '^[a-z_-]+:.*##' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*## *"}; {printf " $(CYAN)%-20s$(RESET) %s\n", $$1, $$2}'
|
||||
.PHONY : check_pre_commit
|
||||
|
||||
@@ -0,0 +1,254 @@
|
||||
NEWS
|
||||
----
|
||||
|
||||
# User visible changes in `mruby3.4` from `mruby3.3`
|
||||
|
||||
"**_NOTE_**:" are changes to be aware of.
|
||||
|
||||
# The language
|
||||
|
||||
- mruby now supports `private` and `protected` visibility ([b0db0bd](https://github.com/mruby/mruby/commit/b0db0bd))
|
||||
- Maximum length of inlined symbols reduced from 5 to 4 characters to provide space for visibility flags ([6442a01](https://github.com/mruby/mruby/commit/6442a01))
|
||||
- Many methods are made private according to CRuby visibility ([4a0e806](https://github.com/mruby/mruby/commit/4a0e806))
|
||||
- Generate OP_SSEND for `self.method` type calls ([111fe4b](https://github.com/mruby/mruby/commit/111fe4b))
|
||||
- `initialize` method will be always private ([eb8b412](https://github.com/mruby/mruby/commit/eb8b412))
|
||||
- Add new hooks `method_removed`, `method_undefined` ([9c74f6e](https://github.com/mruby/mruby/commit/9c74f6e))
|
||||
- Add new hooks `singleton_method_removed`, `singleton_method_undefined` ([0863c08](https://github.com/mruby/mruby/commit/0863c08))
|
||||
- Updated `OP_DEF` output from codedump ([3a3e877](https://github.com/mruby/mruby/commit/3a3e877))
|
||||
- Better handling of binary strings, e.g. String#b ([b0127f0](https://github.com/mruby/mruby/commit/b0127f0))
|
||||
- Hash `to_s` format has changed ([baeeb5e](https://github.com/mruby/mruby/commit/baeeb5e))
|
||||
- Some encoding related method such as `#force_encoding` ([e47b4ca](https://github.com/mruby/mruby/commit/e47b4ca)), `#b` ([b0127f0](https://github.com/mruby/mruby/commit/b0127f0))
|
||||
- Constant folding for `String#+` ([6687bdd](https://github.com/mruby/mruby/commit/6687bdd))
|
||||
- Remove Float bit-operation ([db8368f](https://github.com/mruby/mruby/commit/db8368f))
|
||||
- use SWAR technique for strlen performance ([cbb31e6](https://github.com/mruby/mruby/commit/cbb31e6))
|
||||
- use merge sort for `Array#sort` ([5bd63d6](https://github.com/mruby/mruby/commit/5bd63d6))
|
||||
|
||||
# Changes in C API
|
||||
|
||||
- pool.c renamed to mempool.c (and mrb_pool to mrb_mempool) ([49525fa](https://github.com/mruby/mruby/commit/49525fa))
|
||||
- mrb_pool_value renamed to mrb_irep_pool to reduce confusion ([62ef5db](https://github.com/mruby/mruby/commit/62ef5db))
|
||||
- rename BOXNIX_SET_VALUE to BOXNO_SET_VALUE ([#6397](https://github.com/mruby/mruby/pull/6397))
|
||||
- `MRB_FROZEN_P()` is replaced by `mrb_frozen_p()` ([c11d18e](https://github.com/mruby/mruby/commit/c11d18e))
|
||||
- rename `color` to `gc_color` ([0e79f6b](https://github.com/mruby/mruby/commit/0e79f6b), [1e36d76](https://github.com/mruby/mruby/commit/1e36d76))
|
||||
- add `obj->frozen` instead of flags `MRB_SET_FROZEN_FLAG`/`MRB_UNSET_FROZEN_FLAG` ([8276143](https://github.com/mruby/mruby/commit/8276143))
|
||||
|
||||
# Build & Configuration
|
||||
|
||||
- New Build Target: `test:run:serial`, `test:run:serial:lib`, `test:run:serial:bin` ([#6423](https://github.com/mruby/mruby/pull/6423))
|
||||
- New Platform: PlayStation Portable ([#6022](https://github.com/mruby/mruby/pull/6465))
|
||||
- New Platform: emscripten ([#6487](https://github.com/mruby/mruby/pull/6487))
|
||||
- New Config: no-float (with MRB_NO_FLOAT) ([32200f1](https://github.com/mruby/mruby/commit/32200f1))
|
||||
|
||||
# Changes in mrbgems
|
||||
|
||||
- **mruby-print**: removed; if you do not use `mruby-io`, mruby use `#print` etc. in the core ([8c8bbd9](https://github.com/mruby/mruby/commit/8c8bbd9))
|
||||
- **mruby-enum-lazy**: Add Enumerable::Lazy#grep_v to mruby-enum-lazy ([#6171](https://github.com/mruby/mruby/pull/6171))
|
||||
- **mruby-io**: Add `level` argument to `File.dirname` ([#6463](https://github.com/mruby/mruby/pull/6463))
|
||||
- **mruby-io**: File.absolute_path? ([#6482](https://github.com/mruby/mruby/pull/6482))
|
||||
- **mruby-io**: File.absolute_path ([96113a2](https://github.com/mruby/mruby/commit/96113a2))
|
||||
- **mruby-toplevel-ext**: top-level public/private/protected moved to the core ([2a876d2](https://github.com/mruby/mruby/commit/2a876d2))
|
||||
- **mruby-metaprog**: method list methods now works according to the visibility ([9229da1](https://github.com/mruby/mruby/commit/9229da1))
|
||||
- **mruby-metaprog**: `public_instance_methods`, `private_instance_methods`, `protected_instance_methods` ([9e3e7b2](https://github.com/mruby/mruby/commit/9e3e7b2))
|
||||
- **mruby-encoding**: MRB_UTF8_STRING turned on automatically with this gem ([74bdae9](https://github.com/mruby/mruby/commit/74bdae9))
|
||||
|
||||
# Fixed GitHub Issues
|
||||
|
||||
- [#6173](https://github.com/mruby/mruby/issues/6173) Fails to build with tcc(Tiny C Compiler)
|
||||
- [#6156](https://github.com/mruby/mruby/issues/6156) '/LIBPATH' issue
|
||||
- [#6183](https://github.com/mruby/mruby/issues/6183) ".e".to_f returns NAN
|
||||
- [#6182](https://github.com/mruby/mruby/issues/6182) mrb_read_float() converts "0.3" with a small error compared to strtod()
|
||||
- [#6210](https://github.com/mruby/mruby/issues/6210) Unary minus seems broken
|
||||
- [#6255](https://github.com/mruby/mruby/issues/6255) Wrong number of characters in broken UTF-8 string
|
||||
- [#4038](https://github.com/mruby/mruby/issues/4038) Heap buffer overflow in OP_ENTER
|
||||
- [#6262](https://github.com/mruby/mruby/issues/6262) Unable to define == for objects when using Array#delete
|
||||
- [#6267](https://github.com/mruby/mruby/issues/6267) When MRB_UTF8_STRING is enabled, giving byte characters for String#index and String#split gives wrong results
|
||||
- [#6277](https://github.com/mruby/mruby/issues/6277) MSVC: can't use malloc() in string.c with WIN32_LEAN_AND_MEAN
|
||||
- [#6240](https://github.com/mruby/mruby/issues/6240) Differentiate between lib and lib64 in the build settings.
|
||||
- [#6304](https://github.com/mruby/mruby/issues/6304) Calling method_missing with only Kwargs passes arguments incorrectly
|
||||
- [#6317](https://github.com/mruby/mruby/issues/6317) mrb_gc_register() may cause GC and collect the object being protected
|
||||
- [#6307](https://github.com/mruby/mruby/issues/6307) Planned change patch for mrb_vm_exec()
|
||||
- [#6298](https://github.com/mruby/mruby/issues/6298) foo :bar {} is legal in mruby?
|
||||
- [#6326](https://github.com/mruby/mruby/issues/6326) Detect “Use-after-free” with address sanitizer
|
||||
- [#5358](https://github.com/mruby/mruby/issues/5358) static warning from getpwnam
|
||||
- [#6339](https://github.com/mruby/mruby/issues/6339) mrb_ary_delete() may refer to an invalid address (use-after-free)
|
||||
- [#6346](https://github.com/mruby/mruby/issues/6346) Block kwargs are passed as last positional arg when using yield
|
||||
- [#6365](https://github.com/mruby/mruby/issues/6365) powl() not available when compiling for Dreamcast
|
||||
- [#6369](https://github.com/mruby/mruby/issues/6369) 100x Performance Regression from 3.1
|
||||
- [#6270](https://github.com/mruby/mruby/issues/6270) NODE_ZSUPER from deeply nested blocks will result in a truncated digits for block index in OP_ARGARY
|
||||
- [#6297](https://github.com/mruby/mruby/issues/6297) Assigning to a block variable changes the actual block (thus affecting block_given? and yield)
|
||||
- [#6389](https://github.com/mruby/mruby/issues/6389) instance_exec named block args don't work properly
|
||||
- [#6388](https://github.com/mruby/mruby/issues/6388) Recent commit broke my windows build
|
||||
- [#6411](https://github.com/mruby/mruby/issues/6411) Wrong function unwinding when using return in a block
|
||||
- [#6439](https://github.com/mruby/mruby/issues/6439) OP_JMPUW does not call the ensure block when it jumps to the beginning of the begin block
|
||||
- [#6441](https://github.com/mruby/mruby/issues/6441) break inside while loop will execute the ensure block outside of the while loop
|
||||
- [#6453](https://github.com/mruby/mruby/issues/6453) Bigint: incorrect behavior of ^ operator
|
||||
- [#6452](https://github.com/mruby/mruby/issues/6452) Bigint: weird mod behavior
|
||||
- [#6451](https://github.com/mruby/mruby/issues/6451) Bigint: incorrect division behavior
|
||||
- [#6456](https://github.com/mruby/mruby/issues/6456) bigint: bug with division of a small number by a bigint
|
||||
- [#6466](https://github.com/mruby/mruby/issues/6466) mruby-bin-mruby using Kernel#p and Kernel#print in bintest fails test
|
||||
- [#6467](https://github.com/mruby/mruby/issues/6467) Heap-Use-After-Free due to Recursive group_by Calls
|
||||
- [#6471](https://github.com/mruby/mruby/issues/6471) Discrepancy in codegen for binary operations between master branch and 3.3.0
|
||||
- [#6477](https://github.com/mruby/mruby/issues/6477) heap-buffer-overflow in mrb_vm_exec
|
||||
- [#6485](https://github.com/mruby/mruby/issues/6485) Hash#rehash does not check if the hash is frozen
|
||||
- [#6483](https://github.com/mruby/mruby/issues/6483) Hash#default_proc= accepts arbitrary objects that are not callable
|
||||
- [#6491](https://github.com/mruby/mruby/issues/6491) Destroy existing string literals when composing string literals
|
||||
|
||||
# Merged Pull Requests (User Visible Ones)
|
||||
|
||||
- [#6171](https://github.com/mruby/mruby/pull/6171) Add Enumerable::Lazy#grep_v to mruby-enum-lazy
|
||||
- [#6174](https://github.com/mruby/mruby/pull/6174) Fix MRUBY_PACKAGE_DIR in mruby-config.bat
|
||||
- [#6175](https://github.com/mruby/mruby/pull/6175) Corrected strange conditional in mrb_vm_run()
|
||||
- [#6176](https://github.com/mruby/mruby/pull/6176) Stop assuming alias proc in CI_PROC_SET()
|
||||
- [#6177](https://github.com/mruby/mruby/pull/6177) gha: add macOS 14 to the build
|
||||
- [#6184](https://github.com/mruby/mruby/pull/6184) Remove the L_STOP label
|
||||
- [#6185](https://github.com/mruby/mruby/pull/6185) Added mrb_callinfo::u.keep_context for clarity
|
||||
- [#6186](https://github.com/mruby/mruby/pull/6186) Omit NULL check of e->cxt in OP_RETURN_BLK
|
||||
- [#6191](https://github.com/mruby/mruby/pull/6191) Speed up symbol equality comparison
|
||||
- [#6192](https://github.com/mruby/mruby/pull/6192) Fix `OP_STOP` with exception
|
||||
- [#6193](https://github.com/mruby/mruby/pull/6193) Fix wrong assertion in `OP_SENDB`.
|
||||
- [#6194](https://github.com/mruby/mruby/pull/6194) Simplify the calculation of the number of closures in `MRB_TT_FIBER`
|
||||
- [#6197](https://github.com/mruby/mruby/pull/6197) Fix int_xor to call flo_xor.
|
||||
- [#6201](https://github.com/mruby/mruby/pull/6201) tasks/doc.rake: standardize the `rake doc` error messages
|
||||
- [#6202](https://github.com/mruby/mruby/pull/6202) Remove the `.yardoc` folder with `rake doc:clean:api`
|
||||
- [#6204](https://github.com/mruby/mruby/pull/6204) Clean up the `.editorconfig` file
|
||||
- [#6209](https://github.com/mruby/mruby/pull/6209) Minor `.gitignore` clean up
|
||||
- [#6211](https://github.com/mruby/mruby/pull/6211) Minor `.gitignore` clean up; order entries
|
||||
- [#6216](https://github.com/mruby/mruby/pull/6216) Shared empty `iv_tbl` of module
|
||||
- [#6217](https://github.com/mruby/mruby/pull/6217) Strict declaration for `mrb_istruct_size()`
|
||||
- [#6219](https://github.com/mruby/mruby/pull/6219) Avoid assigning a fixed value in the loop
|
||||
- [#6220](https://github.com/mruby/mruby/pull/6220) Reorganize `mrb_cache_entry` and `mrb_method_t` types
|
||||
- [#6221](https://github.com/mruby/mruby/pull/6221) Arranging `each_backtrace()`
|
||||
- [#6222](https://github.com/mruby/mruby/pull/6222) Need to synchronize `dbg->regs` after VM call in `mrdb`
|
||||
- [#6224](https://github.com/mruby/mruby/pull/6224) `mrb_env_unshare()` to break the link to fiber
|
||||
- [#6225](https://github.com/mruby/mruby/pull/6225) Revert "Adjust environment when `mrb_exec_irep` happened."
|
||||
- [#6227](https://github.com/mruby/mruby/pull/6227) fix: `Array#shuffle(!)` result distribution
|
||||
- [#6228](https://github.com/mruby/mruby/pull/6228) Revert "`env` referred from top-level callinfo should not be unshared; fix #4019"
|
||||
- [#6230](https://github.com/mruby/mruby/pull/6230) Fix status of fiber after switched by exception raised
|
||||
- [#6231](https://github.com/mruby/mruby/pull/6231) Add a way to let other gems handle closing of fds in mruby-io
|
||||
- [#6232](https://github.com/mruby/mruby/pull/6232) Fold the code for freeing `env`
|
||||
- [#6233](https://github.com/mruby/mruby/pull/6233) Free stack memory at end of fiber
|
||||
- [#6235](https://github.com/mruby/mruby/pull/6235) fix `Array#delete` always firing the block when deleting `nil`
|
||||
- [#6236](https://github.com/mruby/mruby/pull/6236) unify the code for filter methods (and speed up `#reject!`)
|
||||
- [#6237](https://github.com/mruby/mruby/pull/6237) Stricter env objects to attach to ci
|
||||
- [#6238](https://github.com/mruby/mruby/pull/6238) Minimize zero initialization of the stack
|
||||
- [#6243](https://github.com/mruby/mruby/pull/6243) Fixed base64 decoding in `mruby-pack`
|
||||
- [#6244](https://github.com/mruby/mruby/pull/6244) Revise scope of role of `mrb_vm_run()`
|
||||
- [#6246](https://github.com/mruby/mruby/pull/6246) Fix typo in `test/t/hash.rb`
|
||||
- [#6249](https://github.com/mruby/mruby/pull/6249) Fix spelling in `src/vm.c`
|
||||
- [#6250](https://github.com/mruby/mruby/pull/6250) Fix spelling
|
||||
- [#6251](https://github.com/mruby/mruby/pull/6251) Clean up root move `CODEOWNERS` to `.github` directory
|
||||
- [#6253](https://github.com/mruby/mruby/pull/6253) Allow recycling fibers by GC if not referenced directly
|
||||
- [#6256](https://github.com/mruby/mruby/pull/6256) Update documentation for `mrb_top_run()`
|
||||
- [#6257](https://github.com/mruby/mruby/pull/6257) fix some mrbconf.md typos
|
||||
- [#6260](https://github.com/mruby/mruby/pull/6260) Remove `exc_caught` from `mrb_vm_exec()`
|
||||
- [#6261](https://github.com/mruby/mruby/pull/6261) fix: `to_a` integer ranges with `begin > end` failing
|
||||
- [#6263](https://github.com/mruby/mruby/pull/6263) fix: `Array#delete` mistakenly calling block even if not passed
|
||||
- [#6264](https://github.com/mruby/mruby/pull/6264) Must pass keyword arguments for `Kernel#to_enum`
|
||||
- [#6265](https://github.com/mruby/mruby/pull/6265) Fixes `Dir.children` and `Dir.each_child`
|
||||
- [#6266](https://github.com/mruby/mruby/pull/6266) Passes the nonexistent key as a block argument in `Array#delete`
|
||||
- [#6273](https://github.com/mruby/mruby/pull/6273) Improvements to `mrb_protect_atexit()`
|
||||
- [#6275](https://github.com/mruby/mruby/pull/6275) Fixed `Binding#eval` that failed to assign to the same variable
|
||||
- [#6276](https://github.com/mruby/mruby/pull/6276) Always run `atexit` on the top-level call frame
|
||||
- [#6279](https://github.com/mruby/mruby/pull/6279) Include headers for malloc() explicitly; fix #6277
|
||||
- [#6280](https://github.com/mruby/mruby/pull/6280) Remove `MRB_ENV_CLOSED` flag
|
||||
- [#6281](https://github.com/mruby/mruby/pull/6281) Fixes local variables in `mruby-binding`.
|
||||
- [#6283](https://github.com/mruby/mruby/pull/6283) Simplify `uvenv()`
|
||||
- [#6288](https://github.com/mruby/mruby/pull/6288) Detach `env` of ci explicitly on atexit
|
||||
- [#6289](https://github.com/mruby/mruby/pull/6289) Simplify `OP_RETURN_BLK` and `OP_BREAK`
|
||||
- [#6290](https://github.com/mruby/mruby/pull/6290) Allow to change the output directory name of the `libmruby` file
|
||||
- [#6293](https://github.com/mruby/mruby/pull/6293) Changed the instruction table in `opcode.md`
|
||||
- [#6294](https://github.com/mruby/mruby/pull/6294) Optimise `mrb_iv_get`
|
||||
- [#6302](https://github.com/mruby/mruby/pull/6302) Minor cleanup in mrb_str_init
|
||||
- [#6303](https://github.com/mruby/mruby/pull/6303) mrb_str_aset_m() should return replace instead of str
|
||||
- [#6305](https://github.com/mruby/mruby/pull/6305) Protect keyword arguments in `prepare_missing()`
|
||||
- [#6308](https://github.com/mruby/mruby/pull/6308) Assume that `MRB_CATCH()` has `mrb->exc` set
|
||||
- [#6310](https://github.com/mruby/mruby/pull/6310) Doubling the call stack when extending it
|
||||
- [#6311](https://github.com/mruby/mruby/pull/6311) Added fast-path for positional arguments less than 15 in `OP_SEND`
|
||||
- [#6312](https://github.com/mruby/mruby/pull/6312) Omit error checking at `OP_RETURN`, `OP_RETURN_BLK` and `OP_BREAK`
|
||||
- [#6313](https://github.com/mruby/mruby/pull/6313) Fix wrong column number in opcode.md
|
||||
- [#6314](https://github.com/mruby/mruby/pull/6314) Optimize even?/odd? for big integers
|
||||
- [#6318](https://github.com/mruby/mruby/pull/6318) Shrink variables in `mrb_vm_exec()`
|
||||
- [#6320](https://github.com/mruby/mruby/pull/6320) Shrinking the code in `OP_BREAK` and `OP_RETURN_BLK`
|
||||
- [#6321](https://github.com/mruby/mruby/pull/6321) Avoid warnings in `lib/**/*.rb`
|
||||
- [#6322](https://github.com/mruby/mruby/pull/6322) Fix mrb_ro_data_p on Intel Mac
|
||||
- [#6324](https://github.com/mruby/mruby/pull/6324) Remove `localjump_error()`
|
||||
- [#6327](https://github.com/mruby/mruby/pull/6327) fix ncurses linking issues
|
||||
- [#6328](https://github.com/mruby/mruby/pull/6328) Fix use-after-free in `obj_free()` for env objects
|
||||
- [#6329](https://github.com/mruby/mruby/pull/6329) Fix use-after-free in `mrb_obj_alloc()`
|
||||
- [#6330](https://github.com/mruby/mruby/pull/6330) Add a precondition to call `mrb_env_unshare()`.
|
||||
- [#6331](https://github.com/mruby/mruby/pull/6331) Restore the GC arena with tests
|
||||
- [#6332](https://github.com/mruby/mruby/pull/6332) Must not depend on the “host” build to generate `mruby-compiler/core/y.tab.c`
|
||||
- [#6333](https://github.com/mruby/mruby/pull/6333) Reduce the number of branch instructions in the `heap_p()`
|
||||
- [#6335](https://github.com/mruby/mruby/pull/6335) Add `return_ci` in `CHECKPOINT_MAIN()` of `OP_RETURN`
|
||||
- [#6338](https://github.com/mruby/mruby/pull/6338) Need to place static proc objects into 8-byte alignments
|
||||
- [#6340](https://github.com/mruby/mruby/pull/6340) Fix use-after-free for `Array#<=>`
|
||||
- [#6341](https://github.com/mruby/mruby/pull/6341) Need to restore the GC arena after some function calls
|
||||
- [#6344](https://github.com/mruby/mruby/pull/6344) prefer using `mrb_yield` to call block arguments
|
||||
- [#6347](https://github.com/mruby/mruby/pull/6347) codegen.c,parse.y: remove flattening of `yield` arguments; fix #6346
|
||||
- [#6348](https://github.com/mruby/mruby/pull/6348) Cancel the warning disablement
|
||||
- [#6349](https://github.com/mruby/mruby/pull/6349) Perform GC before deleting directories
|
||||
- [#6350](https://github.com/mruby/mruby/pull/6350) Fixed character encoding conversion function mismatch
|
||||
- [#6351](https://github.com/mruby/mruby/pull/6351) Remove unnecessary `mrb_gc_arena_restore()`
|
||||
- [#6353](https://github.com/mruby/mruby/pull/6353) Fix use-after-free in `mrb_ary_delete()`
|
||||
- [#6356](https://github.com/mruby/mruby/pull/6356) Making splat argument objects invisible from Ruby side
|
||||
- [#6373](https://github.com/mruby/mruby/pull/6373) Add build config for Milk-V Duo (RISC-V Linux) board
|
||||
- [#6382](https://github.com/mruby/mruby/pull/6382) Make array objects invisible in `mrb_gc_register()`
|
||||
- [#6385](https://github.com/mruby/mruby/pull/6385) Small improvements for `mrb_gc_register()`
|
||||
- [#6386](https://github.com/mruby/mruby/pull/6386) Avoid calling `mrb_gv_set()` from `mrb_gc_unregister()`
|
||||
- [#6387](https://github.com/mruby/mruby/pull/6387) Small improvements for `mrb_gc_unregister()`
|
||||
- [#6390](https://github.com/mruby/mruby/pull/6390) Fix use-after-free by `mrb_gc_unregistor()`
|
||||
- [#6391](https://github.com/mruby/mruby/pull/6391) Fixed argument forwarding in `instance_exec`
|
||||
- [#6392](https://github.com/mruby/mruby/pull/6392) Fix argument forwarding in `mrb_exec_irep()`
|
||||
- [#6393](https://github.com/mruby/mruby/pull/6393) Follow-up to #6391
|
||||
- [#6395](https://github.com/mruby/mruby/pull/6395) Storing method-id inside Symbol#to_proc
|
||||
- [#6396](https://github.com/mruby/mruby/pull/6396) Milk-V Build Config: update GPIO gem URL
|
||||
- [#6397](https://github.com/mruby/mruby/pull/6397) boxing_no.h: rename BOXNIX_SET_VALUE -> BOXNO_SET_VALUE
|
||||
- [#6399](https://github.com/mruby/mruby/pull/6399) Add macOS 15 to the build
|
||||
- [#6405](https://github.com/mruby/mruby/pull/6405) `io_read`: use `%i` instead of `%d` in call to `mrb_raisef`
|
||||
- [#6407](https://github.com/mruby/mruby/pull/6407) Allow to exclude specific files in `rake install`
|
||||
- [#6408](https://github.com/mruby/mruby/pull/6408) Improve compliance with C++ standards
|
||||
- [#6410](https://github.com/mruby/mruby/pull/6410) Put `#include <stdlib.h>` in `parse.y`
|
||||
- [#6412](https://github.com/mruby/mruby/pull/6412) Distinguish the call frame of the generator with `OP_RETURN_BLK`
|
||||
- [#6413](https://github.com/mruby/mruby/pull/6413) Add links to documentation in `README.md`
|
||||
- [#6415](https://github.com/mruby/mruby/pull/6415) Fix numbered parameters when used as a singleton
|
||||
- [#6416](https://github.com/mruby/mruby/pull/6416) Optimize the "new" method's iseq
|
||||
- [#6419](https://github.com/mruby/mruby/pull/6419) Follow Ruby's behavior for numbered parameters in -> {}
|
||||
- [#6420](https://github.com/mruby/mruby/pull/6420) Update `labeler.yml`: add label for the `tools` directory
|
||||
- [#6422](https://github.com/mruby/mruby/pull/6422) Add annotations for function names defined in the preprocessor
|
||||
- [#6423](https://github.com/mruby/mruby/pull/6423) Adding a serialized test task
|
||||
- [#6427](https://github.com/mruby/mruby/pull/6427) Need to update `ci` variable after re-entry to VM
|
||||
- [#6428](https://github.com/mruby/mruby/pull/6428) Change the limits of OP_ADDI and OP_SUBI from 0-127 to 0-255.
|
||||
- [#6429](https://github.com/mruby/mruby/pull/6429) Fix numbered parameters when used as hash keys
|
||||
- [#6432](https://github.com/mruby/mruby/pull/6432) Moving code in macro arguments out of macros
|
||||
- [#6434](https://github.com/mruby/mruby/pull/6434) Added document "Layout of the mruby filesystem"
|
||||
- [#6436](https://github.com/mruby/mruby/pull/6436) Make `rake doc:update-index` prettier friendly
|
||||
- [#6437](https://github.com/mruby/mruby/pull/6437) Add more details to the pre-commit config
|
||||
- [#6438](https://github.com/mruby/mruby/pull/6438) Remove unused `MRuby::Build#list_install_excludes` method
|
||||
- [#6440](https://github.com/mruby/mruby/pull/6440) Fix `redo` keyword
|
||||
- [#6442](https://github.com/mruby/mruby/pull/6442) Fixed wrong range condition in `OP_JMPUW`
|
||||
- [#6443](https://github.com/mruby/mruby/pull/6443) Fix NODE_NEGATE for bigints
|
||||
- [#6444](https://github.com/mruby/mruby/pull/6444) Add test cases for bigints
|
||||
- [#6446](https://github.com/mruby/mruby/pull/6446) Omit the `_WIN64` definition check
|
||||
- [#6447](https://github.com/mruby/mruby/pull/6447) Fixed `File.expand_path`
|
||||
- [#6448](https://github.com/mruby/mruby/pull/6448) Suppress presym in `mruby/ext/io.h` file
|
||||
- [#6449](https://github.com/mruby/mruby/pull/6449) Using presym in the `mruby-io/src/file_test.c` file
|
||||
- [#6450](https://github.com/mruby/mruby/pull/6450) Change `MRB_WITH_IO_PREAD_PWRITE` configuration name
|
||||
- [#6454](https://github.com/mruby/mruby/pull/6454) mruby-bigint: handle rhs bigint in int_mod and int_divmod functions
|
||||
- [#6455](https://github.com/mruby/mruby/pull/6455) mruby-bigint: fix rounding behavior in mpz_mdiv and mpz_mdivmod functions
|
||||
- [#6457](https://github.com/mruby/mruby/pull/6457) bigint: fix bug with division of a small number by a bigint
|
||||
- [#6459](https://github.com/mruby/mruby/pull/6459) `FileTest` is a module
|
||||
- [#6461](https://github.com/mruby/mruby/pull/6461) To create a release package file in draft
|
||||
- [#6462](https://github.com/mruby/mruby/pull/6462) Properly cast the return value of `memchr()`
|
||||
- [#6463](https://github.com/mruby/mruby/pull/6463) Add `level` argument to `File.dirname`
|
||||
- [#6465](https://github.com/mruby/mruby/pull/6465) Add initial PlayStation portable crossbuild support
|
||||
- [#6468](https://github.com/mruby/mruby/pull/6468) Fixed missing changes to `IB_FIND_BY_KEY()` parameter names
|
||||
- [#6469](https://github.com/mruby/mruby/pull/6469) Add more `const` qualifier for `RProc`
|
||||
- [#6472](https://github.com/mruby/mruby/pull/6472) Moved tests for `Integer#quo`
|
||||
- [#6473](https://github.com/mruby/mruby/pull/6473) Hide `mpz_and()` symbol
|
||||
- [#6473](https://github.com/mruby/mruby/pull/6473) Hide mpz_and() symbol
|
||||
- [#6474](https://github.com/mruby/mruby/pull/6474) Avoid array object creation with “unknown keyword” error
|
||||
- [#6475](https://github.com/mruby/mruby/pull/6475) Don't include deleted mruby-print
|
||||
- [#6478](https://github.com/mruby/mruby/pull/6478) Fixed buffer overrun in function `chars2bytes()`
|
||||
- [#6479](https://github.com/mruby/mruby/pull/6479) Reimplementation of `File.expand_path` method
|
||||
- [#6482](https://github.com/mruby/mruby/pull/6482) Add `File.absolute_path?` method
|
||||
- [#6487](https://github.com/mruby/mruby/pull/6487) Add Emscripten toolchain & build_config
|
||||
@@ -1,323 +0,0 @@
|
||||
# User visible changes in `mruby4.0` from `mruby3.4`
|
||||
|
||||
"**_NOTE_**:" are changes to be aware of.
|
||||
|
||||
# The language
|
||||
|
||||
## Pattern Matching
|
||||
|
||||
mruby now supports pattern matching (case/in) syntax:
|
||||
|
||||
- Basic pattern matching with `case`/`in` syntax ([dadfac6](https://github.com/mruby/mruby/commit/dadfac6))
|
||||
- Array pattern matching ([ec67fd9](https://github.com/mruby/mruby/commit/ec67fd9))
|
||||
- Hash pattern matching ([2147263](https://github.com/mruby/mruby/commit/2147263))
|
||||
- Find pattern matching (`[*pre, target, *post]`) ([6c4d98b](https://github.com/mruby/mruby/commit/6c4d98b))
|
||||
- Pin operator (`^variable`) ([1de6340](https://github.com/mruby/mruby/commit/1de6340))
|
||||
- Guard clauses (`if`/`unless` conditions) ([07ac110](https://github.com/mruby/mruby/commit/07ac110))
|
||||
- One-line pattern matching (`expr in pattern`) ([e76ce24](https://github.com/mruby/mruby/commit/e76ce24))
|
||||
- Brace-less hash pattern support ([e8096bf](https://github.com/mruby/mruby/commit/e8096bf))
|
||||
|
||||
## Other Language Changes
|
||||
|
||||
- `&nil` in formal parameters to explicitly opt out of block arguments ([b07518e](https://github.com/mruby/mruby/commit/b07518e))
|
||||
- Trailing comma in method definition parameters: `def foo(a, b,)` ([f78334b](https://github.com/mruby/mruby/commit/f78334b))
|
||||
- Array/Hash/String subclasses can now override `[]` and `[]=` methods ([#6675](https://github.com/mruby/mruby/pull/6675))
|
||||
- `OP_SETIDX` optimization for Array and Hash ([ddd8fe1](https://github.com/mruby/mruby/commit/ddd8fe1))
|
||||
|
||||
# Changes in C API
|
||||
|
||||
- **_NOTE_**: `mrb_alloca()` renamed to `mrb_temp_alloc()` ([7fe5c2e](https://github.com/mruby/mruby/commit/7fe5c2e))
|
||||
- **_NOTE_**: `mruby/ext/io.h` renamed to `mruby/io.h` ([2813f79](https://github.com/mruby/mruby/commit/2813f79))
|
||||
- `mrb_gc_add_region()` for contiguous heap region support ([072855a](https://github.com/mruby/mruby/commit/072855a))
|
||||
- `mrb_class_outer()` to get the outer class/module ([3a1b771](https://github.com/mruby/mruby/commit/3a1b771))
|
||||
- `MRB_ENSURE()` macro for exception-safe cleanup ([3ac682b](https://github.com/mruby/mruby/commit/3ac682b))
|
||||
- `mrb_time_get_tm()` for accessing struct tm ([daaaafe](https://github.com/mruby/mruby/commit/daaaafe))
|
||||
- `MRB_OPEN_FAILURE()` macro for checking mrb_open result ([40b0cb9](https://github.com/mruby/mruby/commit/40b0cb9))
|
||||
- `mrb_print_error()` now handles NULL gracefully ([8e50a45](https://github.com/mruby/mruby/commit/8e50a45))
|
||||
- `mrb_open()` returns mrb_state with exc set on init failure ([05ffe0c](https://github.com/mruby/mruby/commit/05ffe0c))
|
||||
- `mrb_utf8_to_buf()` for UTF-8 encoding consolidation ([7e28e68](https://github.com/mruby/mruby/commit/7e28e68))
|
||||
- `kh_is_end()` macro for safe khash iteration ([893cc75](https://github.com/mruby/mruby/commit/893cc75))
|
||||
|
||||
# ROM Method Tables
|
||||
|
||||
All built-in classes and most extension gems now use read-only method
|
||||
tables stored in `.rodata` instead of heap-allocated hash tables. Method
|
||||
definitions no longer consume heap memory, significantly reducing memory
|
||||
footprint for embedded use.
|
||||
|
||||
Core classes converted: BasicObject, Object, Module, Class, Kernel,
|
||||
String, Array, Hash, Numeric, Integer, Float, NilClass, TrueClass,
|
||||
FalseClass, Range, Symbol, Exception, Proc.
|
||||
|
||||
Extension gems converted: mruby-string-ext, mruby-array-ext, mruby-set,
|
||||
mruby-struct, mruby-class-ext, mruby-numeric-ext, mruby-random,
|
||||
mruby-kernel-ext, mruby-complex, mruby-rational, mruby-io, mruby-socket,
|
||||
mruby-method, mruby-metaprog, mruby-time, mruby-hash-ext, mruby-proc-ext,
|
||||
mruby-symbol-ext, mruby-range-ext, mruby-object-ext.
|
||||
|
||||
# GC and Memory
|
||||
|
||||
- **_NOTE_**: `MRB_NO_PRESYM` removed; presym is now always enabled ([81689045](https://github.com/mruby/mruby/commit/81689045))
|
||||
- Replace `gcnext` gray linked list with fixed-size gray stack, reducing per-object overhead ([31fea170](https://github.com/mruby/mruby/commit/31fea170))
|
||||
- `mrb_gc_add_region()` for providing contiguous memory buffers as GC heap pages ([072855a](https://github.com/mruby/mruby/commit/072855a))
|
||||
- Chunk-based pool for symbol string allocation ([e05bd8f](https://github.com/mruby/mruby/commit/e05bd8f))
|
||||
- Reduce `IV_INITIAL_SIZE` from 4 to 2 ([6bd1f51](https://github.com/mruby/mruby/commit/6bd1f51))
|
||||
- Lossless float encoding using rotation in word boxing ([b6148c8](https://github.com/mruby/mruby/commit/b6148c8))
|
||||
- Lossless rotation encoding for 32-bit float32 word boxing ([14a5cfb](https://github.com/mruby/mruby/commit/14a5cfb))
|
||||
- Consolidated irep allocation for .mrb loading ([74fb045](https://github.com/mruby/mruby/commit/74fb045))
|
||||
- Object shapes (hidden classes) for `MRB_TT_OBJECT` IV storage, sharing key layouts across objects with the same instance variable assignment order ([8d10056](https://github.com/mruby/mruby/commit/8d10056))
|
||||
|
||||
# Build & Configuration
|
||||
|
||||
- **_NOTE_**: `MRB_WORDBOX_NO_FLOAT_TRUNCATE` renamed to `MRB_WORDBOX_NO_INLINE_FLOAT` (old name still works) ([59e1fe2](https://github.com/mruby/mruby/commit/59e1fe2))
|
||||
- **_NOTE_**: `MRB_INT64` on 32-bit now requires `MRB_NO_BOXING` (other boxing modes cannot guarantee alignment for heap-allocated 64-bit integers) ([eaaa66b](https://github.com/mruby/mruby/commit/eaaa66b))
|
||||
- Amalgamation support via `rake amalgam` task ([d995ca2](https://github.com/mruby/mruby/commit/d995ca2))
|
||||
- New Platform: Cosmopolitan Libc ([#6681](https://github.com/mruby/mruby/pull/6681))
|
||||
- Emscripten: use native WASM exception handling ([ca364e3](https://github.com/mruby/mruby/commit/ca364e3))
|
||||
- HAL (Hardware Abstraction Layer) for platform abstraction in mruby-io, mruby-socket, mruby-dir, mruby-task ([74ca22f](https://github.com/mruby/mruby/commit/74ca22f))
|
||||
- `MRUBY_MIRB_READLINE` environment variable to control readline library selection ([0aafb83](https://github.com/mruby/mruby/commit/0aafb83))
|
||||
- Inter-gem headers separated from external API headers ([#6671](https://github.com/mruby/mruby/pull/6671))
|
||||
|
||||
# Changes in mrbgems
|
||||
|
||||
## New Gems
|
||||
|
||||
- **mruby-task**: Cooperative multitasking with preemptive scheduling ([ae0d7a0](https://github.com/mruby/mruby/commit/ae0d7a0))
|
||||
- **mruby-benchmark**: Benchmarking gem ([2f40f3d](https://github.com/mruby/mruby/commit/2f40f3d))
|
||||
- **mruby-strftime**: Time#strftime implementation ([b31e22f](https://github.com/mruby/mruby/commit/b31e22f))
|
||||
|
||||
## mruby-bin-mirb Improvements
|
||||
|
||||
- Custom multi-line editor replacing readline ([527018c](https://github.com/mruby/mruby/commit/527018c))
|
||||
- Syntax highlighting for keywords, strings, result values, hash key symbols ([624272b](https://github.com/mruby/mruby/commit/624272b), [1713d4a](https://github.com/mruby/mruby/commit/1713d4a))
|
||||
- Automatic light/dark theme detection via OSC 11 ([db4c8d9](https://github.com/mruby/mruby/commit/db4c8d9))
|
||||
- Tab completion support ([2f15282](https://github.com/mruby/mruby/commit/2f15282))
|
||||
- Colored output for prompts and errors ([b36e0b4](https://github.com/mruby/mruby/commit/b36e0b4))
|
||||
- Auto-indentation and auto-dedent ([d52f318](https://github.com/mruby/mruby/commit/d52f318), [e901b6d](https://github.com/mruby/mruby/commit/e901b6d))
|
||||
- Command history with Up/Down navigation ([5f85c1b](https://github.com/mruby/mruby/commit/5f85c1b))
|
||||
- Line numbers in multi-line prompts ([5a3f0e2](https://github.com/mruby/mruby/commit/5a3f0e2))
|
||||
- UTF-8 multibyte character support ([4a97da3](https://github.com/mruby/mruby/commit/4a97da3))
|
||||
|
||||
## mruby-bigint Improvements
|
||||
|
||||
- Toom-3 multiplication for large numbers ([99620804](https://github.com/mruby/mruby/commit/99620804))
|
||||
- Karatsuba multiplication for medium-sized numbers ([85e81072](https://github.com/mruby/mruby/commit/85e81072))
|
||||
- Balance multiplication for asymmetric operands ([0220ec2b](https://github.com/mruby/mruby/commit/0220ec2b))
|
||||
- Divide-and-conquer optimization for `to_s` ([990ff90f](https://github.com/mruby/mruby/commit/990ff90f))
|
||||
- Consolidated mpn layer for low-level limb operations ([9ef3362f](https://github.com/mruby/mruby/commit/9ef3362f))
|
||||
- Always use 32-bit limbs by default ([c747c77f](https://github.com/mruby/mruby/commit/c747c77f))
|
||||
|
||||
## Other Gem Changes
|
||||
|
||||
- **_NOTE_**: `Hash#deconstruct_keys` removed for CRuby compatibility ([34b9412](https://github.com/mruby/mruby/commit/34b9412))
|
||||
- **mruby-array-ext**: Add `Array#find` and `Array#rfind` methods
|
||||
- **mruby-io**: Add `IO#putc` and `Kernel#putc` ([baff6e6](https://github.com/mruby/mruby/commit/baff6e6))
|
||||
- **mruby-random**: Replace xoshiro with PCG for better memory efficiency ([f1bab01](https://github.com/mruby/mruby/commit/f1bab01))
|
||||
- **mruby-compiler**: Variable-sized AST nodes for reduced memory usage
|
||||
- **mruby-compiler**: `no_return_value` context flag for script optimization ([613b03a](https://github.com/mruby/mruby/commit/613b03a))
|
||||
- `initialize_copy` and `respond_to_missing?` defined as private ([#6708](https://github.com/mruby/mruby/pull/6708))
|
||||
- Struct keyword argument initialization ([#6574](https://github.com/mruby/mruby/pull/6574))
|
||||
|
||||
# Compiler Improvements
|
||||
|
||||
- Variable-sized AST nodes for reduced memory consumption ([821b989](https://github.com/mruby/mruby/commit/821b989))
|
||||
- Pattern matching bytecode optimizations ([21d4135](https://github.com/mruby/mruby/commit/21d4135))
|
||||
- Optimized masgn to generate literals directly into target registers ([fb5d966](https://github.com/mruby/mruby/commit/fb5d966))
|
||||
- Optimized splat of literal arrays in args/literals ([1cb8d73](https://github.com/mruby/mruby/commit/1cb8d73))
|
||||
- Early termination after too many parse errors ([510ebd7](https://github.com/mruby/mruby/commit/510ebd7))
|
||||
|
||||
# VM Optimizations
|
||||
|
||||
New super-instructions that fuse common opcode sequences to reduce bytecode size and improve performance:
|
||||
|
||||
- `OP_SEND0`/`OP_SSEND0`: Zero-argument method call, avoiding argument count setup ([9123ef4](https://github.com/mruby/mruby/commit/9123ef4))
|
||||
- `OP_TDEF`/`OP_SDEF`: Fused method definition combining TCLASS/SCLASS+METHOD+DEF into single instruction, saving 4 bytes per method ([8d4f47e](https://github.com/mruby/mruby/commit/8d4f47e))
|
||||
- `OP_GETIDX0`: Fast path for `array[0]` and `Array#first` access ([680f7ec](https://github.com/mruby/mruby/commit/680f7ec))
|
||||
- `OP_ADDILV`/`OP_SUBILV`: Local variable increment/decrement fusion for `i += n` patterns ([43f64b9](https://github.com/mruby/mruby/commit/43f64b9))
|
||||
- `OP_RETSELF`: Single-byte instruction for `return self` pattern ([a71db8c](https://github.com/mruby/mruby/commit/a71db8c))
|
||||
- `OP_RETNIL`: Single-byte instruction for `return nil` pattern ([64e30bf](https://github.com/mruby/mruby/commit/64e30bf))
|
||||
- `OP_RETTRUE`/`OP_RETFALSE`: Single-byte instructions for `return true`/`return false` patterns ([0b15727](https://github.com/mruby/mruby/commit/0b15727))
|
||||
- `OP_MATCHERR`: Pattern matching error with conditional execution ([944168a](https://github.com/mruby/mruby/commit/944168a))
|
||||
- `OP_BLKCALL`: Direct block call for `yield`, bypassing method dispatch (13-17% faster) ([3aa2872](https://github.com/mruby/mruby/commit/3aa2872))
|
||||
|
||||
Other optimizations:
|
||||
|
||||
- 1.5x stack growth instead of linear growth for reduced reallocations ([f7988c93](https://github.com/mruby/mruby/commit/f7988c93))
|
||||
- Skip keyword argument hash duplication ([5970e350](https://github.com/mruby/mruby/commit/5970e350))
|
||||
|
||||
# Fixed GitHub Issues
|
||||
|
||||
- [#5531](https://github.com/mruby/mruby/issues/5531) Hash recursion detection
|
||||
- [#6506](https://github.com/mruby/mruby/issues/6506) Constant lookup in singleton class
|
||||
- [#6507](https://github.com/mruby/mruby/issues/6507) tally multi-values
|
||||
- [#6508](https://github.com/mruby/mruby/issues/6508) Enumerable#sum index
|
||||
- [#6509](https://github.com/mruby/mruby/issues/6509) scope_new nregs initialization
|
||||
- [#6515](https://github.com/mruby/mruby/issues/6515) y.tab.c in repository
|
||||
- [#6516](https://github.com/mruby/mruby/issues/6516) Private backquote
|
||||
- [#6554](https://github.com/mruby/mruby/issues/6554) Socket private #initialize
|
||||
- [#6570](https://github.com/mruby/mruby/issues/6570) instance_eval crash
|
||||
- [#6613](https://github.com/mruby/mruby/issues/6613) const_added hook during bootstrapping
|
||||
- [#6635](https://github.com/mruby/mruby/issues/6635), [#6636](https://github.com/mruby/mruby/issues/6636) Colon3 constant lookup
|
||||
- [#6637](https://github.com/mruby/mruby/issues/6637) arm64 mingw64 builtin setjmp/longjmp
|
||||
- [#6642](https://github.com/mruby/mruby/issues/6642) Task segfault when sleep called from C
|
||||
- [#6645](https://github.com/mruby/mruby/issues/6645) Set memory leak from double initialization
|
||||
- [#6646](https://github.com/mruby/mruby/issues/6646) IO#gets negative length
|
||||
- [#6647](https://github.com/mruby/mruby/issues/6647) IO#ungetc buffer overflow
|
||||
- [#6648](https://github.com/mruby/mruby/issues/6648) sprintf buffer overread
|
||||
- [#6649](https://github.com/mruby/mruby/issues/6649) Array#sort! use-after-realloc
|
||||
- [#6650](https://github.com/mruby/mruby/issues/6650) Array#fill validation
|
||||
- [#6652](https://github.com/mruby/mruby/issues/6652) Array comparison use-after-realloc
|
||||
- [#6657](https://github.com/mruby/mruby/issues/6657) Exception handling for ||= on class variables
|
||||
- [#6659](https://github.com/mruby/mruby/issues/6659) Super with keyword arguments
|
||||
- [#6660](https://github.com/mruby/mruby/issues/6660) Regression on struct/array/hash == override with super
|
||||
- [#6662](https://github.com/mruby/mruby/issues/6662) Array set operations use-after-free
|
||||
- [#6664](https://github.com/mruby/mruby/issues/6664) Set#flatten memory leak
|
||||
- [#6666](https://github.com/mruby/mruby/issues/6666) Regexp literal with encoding
|
||||
- [#6668](https://github.com/mruby/mruby/issues/6668) Method#== for aliased methods and comparison bug
|
||||
- [#6671](https://github.com/mruby/mruby/issues/6671) Separate inter-gem headers from external API headers
|
||||
- [#6674](https://github.com/mruby/mruby/issues/6674) Document pattern matching limitations
|
||||
- [#6675](https://github.com/mruby/mruby/issues/6675) Allow Hash#[] to be aliased again
|
||||
- [#6687](https://github.com/mruby/mruby/issues/6687) Expand MRB_SYM/MRB_GVSYM support for symbols with special characters
|
||||
- [#6698](https://github.com/mruby/mruby/issues/6698) Bigint tests fail on architectures other than x86_64 and i386
|
||||
- [#6701](https://github.com/mruby/mruby/issues/6701) Heap-use-after-free in mrb_vm_exec involving mruby-rational / mruby-bigint
|
||||
- [#6702](https://github.com/mruby/mruby/issues/6702) mruby-bigint doesn't compile in C++ project
|
||||
- [#6704](https://github.com/mruby/mruby/issues/6704) Heap-buffer-overflow in mrb_vm_exec via malformed source code
|
||||
- [#6705](https://github.com/mruby/mruby/issues/6705) Can't get outer class of an object in C
|
||||
- [#6713](https://github.com/mruby/mruby/issues/6713) mruby-polarssl not work
|
||||
- [#6720](https://github.com/mruby/mruby/issues/6720) Random float range: different behavior from CRuby
|
||||
|
||||
# Merged Pull Requests
|
||||
|
||||
- [#6418](https://github.com/mruby/mruby/pull/6418) Add `ls-lint` with GitHub Actions
|
||||
- [#6492](https://github.com/mruby/mruby/pull/6492) fix a typo, update specs
|
||||
- [#6493](https://github.com/mruby/mruby/pull/6493) Fix TYPO in memory.md
|
||||
- [#6495](https://github.com/mruby/mruby/pull/6495) Remove `MRB_ENDIAN_LOHI()` that is no longer in use
|
||||
- [#6497](https://github.com/mruby/mruby/pull/6497) gha: update `build.yml` try `windows-2025` image
|
||||
- [#6498](https://github.com/mruby/mruby/pull/6498) Clean up and standardize the pre-commit config
|
||||
- [#6501](https://github.com/mruby/mruby/pull/6501) Update pre-commit Node.js version to `v22.14.0 LTS`
|
||||
- [#6502](https://github.com/mruby/mruby/pull/6502) pre-commit: update prettier to the latest version
|
||||
- [#6503](https://github.com/mruby/mruby/pull/6503) misc: fix typos
|
||||
- [#6505](https://github.com/mruby/mruby/pull/6505) mrbgems: fix spelling
|
||||
- [#6510](https://github.com/mruby/mruby/pull/6510) Fixed class method visibility via `module_function`
|
||||
- [#6511](https://github.com/mruby/mruby/pull/6511) Exclude the external project "lrama" from pre-commit
|
||||
- [#6513](https://github.com/mruby/mruby/pull/6513) mruby 3.4.0 released
|
||||
- [#6517](https://github.com/mruby/mruby/pull/6517) core/codegen.c: remove unneeded duplicate semicolon
|
||||
- [#6518](https://github.com/mruby/mruby/pull/6518) Change mrbc_args.flags bit width from 2 to 3
|
||||
- [#6519](https://github.com/mruby/mruby/pull/6519) Add `tools/lrama` to `.prettierignore`
|
||||
- [#6520](https://github.com/mruby/mruby/pull/6520) pre-commit: autoupdate and update node LTS version
|
||||
- [#6521](https://github.com/mruby/mruby/pull/6521) Add codespell config file `.codespellrc`
|
||||
- [#6522](https://github.com/mruby/mruby/pull/6522) gha: label more files
|
||||
- [#6523](https://github.com/mruby/mruby/pull/6523) add `rand(Range)` and unify implementations of `Random#rand` and `Kernel#rand`
|
||||
- [#6524](https://github.com/mruby/mruby/pull/6524) Fix Kernel#p when no argument
|
||||
- [#6525](https://github.com/mruby/mruby/pull/6525) Skip adding empty input to mirb history
|
||||
- [#6526](https://github.com/mruby/mruby/pull/6526) Add build config for Luckfox Pico embedded SBC
|
||||
- [#6528](https://github.com/mruby/mruby/pull/6528) misc: fix spelling
|
||||
- [#6530](https://github.com/mruby/mruby/pull/6530) Revert "class.c (find_visibility_scope): when callinfo returns, \*ep == NULL; #6512"
|
||||
- [#6531](https://github.com/mruby/mruby/pull/6531) Improve method table performance by rehashing at 75% load factor
|
||||
- [#6532](https://github.com/mruby/mruby/pull/6532) Reverted method table optimizations to prioritize memory savings
|
||||
- [#6533](https://github.com/mruby/mruby/pull/6533) Fix calling `extended` callback
|
||||
- [#6534](https://github.com/mruby/mruby/pull/6534) Add descriptive comment to mrb_read_float function
|
||||
- [#6535](https://github.com/mruby/mruby/pull/6535) Added descriptive comments for functions/macros in src/mempool.c
|
||||
- [#6536](https://github.com/mruby/mruby/pull/6536) Add descriptive comments to public functions in src/debug.c
|
||||
- [#6537](https://github.com/mruby/mruby/pull/6537) Updated comments in `cdump.c` to remove the `@brief` tag
|
||||
- [#6539](https://github.com/mruby/mruby/pull/6539) Add descriptive comments for functions in src/load.c
|
||||
- [#6540](https://github.com/mruby/mruby/pull/6540) Add descriptive comments to MRB_API functions in object.c
|
||||
- [#6541](https://github.com/mruby/mruby/pull/6541) Add descriptive comments for MRB_API functions in src/array.c
|
||||
- [#6542](https://github.com/mruby/mruby/pull/6542) Add descriptive comments to MRB_API functions in src/symbol.c
|
||||
- [#6543](https://github.com/mruby/mruby/pull/6543) Add descriptive comments to several functions in src/dump.c
|
||||
- [#6544](https://github.com/mruby/mruby/pull/6544) Fix build strings that must be mutable
|
||||
- [#6545](https://github.com/mruby/mruby/pull/6545) Add descriptive comments for MRB_API functions in src/class.c
|
||||
- [#6548](https://github.com/mruby/mruby/pull/6548) Add descriptive comments to MRB_API functions in src/etc.c
|
||||
- [#6549](https://github.com/mruby/mruby/pull/6549) Add descriptive comments to kernel functions
|
||||
- [#6550](https://github.com/mruby/mruby/pull/6550) Add descriptive comments for MRB_API functions in src/proc.c
|
||||
- [#6551](https://github.com/mruby/mruby/pull/6551) Add descriptive comments for MRB_API functions in src/state.c
|
||||
- [#6552](https://github.com/mruby/mruby/pull/6552) Fix: Correct placement of comments in src/variable.c
|
||||
- [#6553](https://github.com/mruby/mruby/pull/6553) Add descriptive comments for MRB_API functions in src/vm.c
|
||||
- [#6555](https://github.com/mruby/mruby/pull/6555) `mrb_mt_foreach()` needs to update the pointer at each loop
|
||||
- [#6556](https://github.com/mruby/mruby/pull/6556) `iv_foreach()` needs to update the pointer at each loop
|
||||
- [#6560](https://github.com/mruby/mruby/pull/6560) Refactor: Improve Set GC marking and freeing
|
||||
- [#6561](https://github.com/mruby/mruby/pull/6561) pre-commit updates and fix prettier entrypoint
|
||||
- [#6562](https://github.com/mruby/mruby/pull/6562) misc: fix spelling word case
|
||||
- [#6563](https://github.com/mruby/mruby/pull/6563) pre-commit add rubocop with one rule spaces for indentation
|
||||
- [#6564](https://github.com/mruby/mruby/pull/6564) Remove jumanjihouse pre-commit hooks no longer maintained
|
||||
- [#6565](https://github.com/mruby/mruby/pull/6565) Rubocop: fix target Ruby version; add two more cops; fix lint error
|
||||
- [#6566](https://github.com/mruby/mruby/pull/6566) Removed unreferenced variables in `CrossBuild#run_bintest`
|
||||
- [#6567](https://github.com/mruby/mruby/pull/6567) Avoid array object creation in `cmd_bin` method in bintest
|
||||
- [#6568](https://github.com/mruby/mruby/pull/6568) mruby-bin-debugger depends on mruby-bin-mrbc in bintest
|
||||
- [#6569](https://github.com/mruby/mruby/pull/6569) sed s/Mruby/MRuby/g
|
||||
- [#6571](https://github.com/mruby/mruby/pull/6571) Update limitations.md to add behavior on small hash
|
||||
- [#6572](https://github.com/mruby/mruby/pull/6572) Add Claude Code GitHub Workflow
|
||||
- [#6573](https://github.com/mruby/mruby/pull/6573) pre-commit fixes and updates
|
||||
- [#6574](https://github.com/mruby/mruby/pull/6574) Support initializing structs via keyword arguments
|
||||
- [#6575](https://github.com/mruby/mruby/pull/6575) Fix typo in file time methods
|
||||
- [#6581](https://github.com/mruby/mruby/pull/6581) Merge `mrb_obj_iv_inspect()` into `mrb_obj_inspect()`
|
||||
- [#6582](https://github.com/mruby/mruby/pull/6582) Stricter type tag in `mrb_obj_alloc()`
|
||||
- [#6583](https://github.com/mruby/mruby/pull/6583) Add fallback to local build_config.rb before using default configuration
|
||||
- [#6585](https://github.com/mruby/mruby/pull/6585) Fix typo in mruby3.2 docs
|
||||
- [#6586](https://github.com/mruby/mruby/pull/6586) Makefile: refactor add docs and add command line `help` target
|
||||
- [#6587](https://github.com/mruby/mruby/pull/6587) Add Set#hash tests
|
||||
- [#6588](https://github.com/mruby/mruby/pull/6588) Add CodeQL Analysis for GitHub Actions
|
||||
- [#6589](https://github.com/mruby/mruby/pull/6589) Add pre-commit hook `check-zip-file-is-not-committed`
|
||||
- [#6591](https://github.com/mruby/mruby/pull/6591) mruby-eval fix license link in README
|
||||
- [#6593](https://github.com/mruby/mruby/pull/6593) README: Add Contributors Avatars, Star History, Table of Contents
|
||||
- [#6599](https://github.com/mruby/mruby/pull/6599) pre-commit: run `markdown-link-check`, `oxipng`, `prettier` manually
|
||||
- [#6600](https://github.com/mruby/mruby/pull/6600) `dreamcast_shelf build config`: update to use KallistiOS wrappers
|
||||
- [#6601](https://github.com/mruby/mruby/pull/6601) fix: skip local build_config.rb when working in MRUBY_ROOT
|
||||
- [#6602](https://github.com/mruby/mruby/pull/6602) Improved iseq annotations for `new` and `!=`
|
||||
- [#6604](https://github.com/mruby/mruby/pull/6604) pre-commit config updates
|
||||
- [#6607](https://github.com/mruby/mruby/pull/6607) fix bigint on raspberry pi
|
||||
- [#6610](https://github.com/mruby/mruby/pull/6610) Extract golden ratio prime into constant
|
||||
- [#6614](https://github.com/mruby/mruby/pull/6614) Fix uninitialized variable in io_gets causing segmentation fault
|
||||
- [#6617](https://github.com/mruby/mruby/pull/6617) Fix various minor problems and speed up build
|
||||
- [#6618](https://github.com/mruby/mruby/pull/6618) Stop generating unnecessary C++ files in mruby-bin-mruby
|
||||
- [#6621](https://github.com/mruby/mruby/pull/6621) Set up all GEMS before mruby core tasks definition
|
||||
- [#6624](https://github.com/mruby/mruby/pull/6624) Fixed wrong `MRuby::Build.current` at the top level of `mrbgem.rake`
|
||||
- [#6628](https://github.com/mruby/mruby/pull/6628) Revert `File.absolute_path` logic
|
||||
- [#6629](https://github.com/mruby/mruby/pull/6629) pre-commit update
|
||||
- [#6631](https://github.com/mruby/mruby/pull/6631) Revert "Rakefile: make the whole thing parallel unless SERIAL=1"
|
||||
- [#6633](https://github.com/mruby/mruby/pull/6633) Fix a heap-buffer-overflow in str strip! methods
|
||||
- [#6643](https://github.com/mruby/mruby/pull/6643) Fix crash caused by an incorrect node type check in `codegen_masgn`
|
||||
- [#6651](https://github.com/mruby/mruby/pull/6651) Address stack-use-after-return in the mruby bigint implementation
|
||||
- [#6653](https://github.com/mruby/mruby/pull/6653) Improve HAL-related components for MinGW
|
||||
- [#6655](https://github.com/mruby/mruby/pull/6655) Preventing Memory Leaks in `Array#__combination_init`
|
||||
- [#6656](https://github.com/mruby/mruby/pull/6656) Fix integer overflow in allocation size calculation
|
||||
- [#6663](https://github.com/mruby/mruby/pull/6663) Added the `kh_is_end()` macro function
|
||||
- [#6665](https://github.com/mruby/mruby/pull/6665) Fixed use-after-free with `Set#join`
|
||||
- [#6670](https://github.com/mruby/mruby/pull/6670) Arranging VM dispatch macros
|
||||
- [#6673](https://github.com/mruby/mruby/pull/6673) Adjust broken license links; clean up Markdown
|
||||
- [#6677](https://github.com/mruby/mruby/pull/6677) gha: run pre-commit with `--color=always`
|
||||
- [#6678](https://github.com/mruby/mruby/pull/6678) Put ls-lint and pre-commit in separate workflow files
|
||||
- [#6679](https://github.com/mruby/mruby/pull/6679) pre-commit autoupdate; update node and prettier
|
||||
- [#6681](https://github.com/mruby/mruby/pull/6681) Add Cosmopolitan Libc build configuration
|
||||
- [#6689](https://github.com/mruby/mruby/pull/6689) docs: fix pre-commit manual hooks; fix link
|
||||
- [#6694](https://github.com/mruby/mruby/pull/6694) Fix mirb build under Cosmopolitan
|
||||
- [#6695](https://github.com/mruby/mruby/pull/6695) Dependabot: add a cooldown period for new releases
|
||||
- [#6696](https://github.com/mruby/mruby/pull/6696) Fix parse error with required kwargs and omitted parens
|
||||
- [#6699](https://github.com/mruby/mruby/pull/6699) Fix mruby-task for PicoRuby Integration
|
||||
- [#6700](https://github.com/mruby/mruby/pull/6700) Fix float/double pack/unpack on s390x
|
||||
- [#6706](https://github.com/mruby/mruby/pull/6706) Refactor task class to use symbol IDs
|
||||
- [#6708](https://github.com/mruby/mruby/pull/6708) `initialize_copy` and `respond_to_missing?` defined as private
|
||||
- [#6709](https://github.com/mruby/mruby/pull/6709) Add the `MRB_ENSURE()` macro
|
||||
- [#6711](https://github.com/mruby/mruby/pull/6711) Fix out of bounds read and write in IO.select
|
||||
- [#6714](https://github.com/mruby/mruby/pull/6714) Fix OP_DEBUG operand type and add NULL check for debug_op_hook
|
||||
- [#6716](https://github.com/mruby/mruby/pull/6716) Fixes identity for proc object
|
||||
- [#6717](https://github.com/mruby/mruby/pull/6717) Fix mruby-task: wrapping by critical section and setting initial task receiver
|
||||
- [#6718](https://github.com/mruby/mruby/pull/6718) Add installation instructions for conda and Homebrew
|
||||
|
||||
# Security Fixes
|
||||
|
||||
- Buffer overflow in bigint uadd ([3f2611e](https://github.com/mruby/mruby/commit/3f2611e))
|
||||
- Stack buffer overflow in Montgomery reduction ([edce0a3](https://github.com/mruby/mruby/commit/edce0a3))
|
||||
- Buffer overflow in pack_uu encoding ([2993302](https://github.com/mruby/mruby/commit/2993302))
|
||||
- Buffer overflow in IO#ungetc ([01ab2ff](https://github.com/mruby/mruby/commit/01ab2ff))
|
||||
- Heap-buffer-overflow in pattern alternation codegen ([eea9e30](https://github.com/mruby/mruby/commit/eea9e30))
|
||||
- Out of bounds read and write in IO.select ([44831711](https://github.com/mruby/mruby/commit/44831711))
|
||||
- Off-by-one in bounds check for symbol names and pool strings in load.c ([b3b8c01](https://github.com/mruby/mruby/commit/b3b8c01))
|
||||
- Use-after-free in Set operations ([a6b55e7](https://github.com/mruby/mruby/commit/a6b55e7))
|
||||
- Use-after-free in Array set operations ([729b84c](https://github.com/mruby/mruby/commit/729b84c))
|
||||
- Use-after-free in Set#join ([0e653eb](https://github.com/mruby/mruby/commit/0e653eb))
|
||||
- Use-after-realloc in Array#sort! ([eb39897](https://github.com/mruby/mruby/commit/eb39897))
|
||||
- Heap-use-after-free in insertion_sort ([099d2c47](https://github.com/mruby/mruby/commit/099d2c47))
|
||||
- Integer overflow in str_check_length ([6afff1c3](https://github.com/mruby/mruby/commit/6afff1c3))
|
||||
- Integer overflow in Integer#lcm ([070bef24](https://github.com/mruby/mruby/commit/070bef24))
|
||||
- Multiple memory leak fixes in bigint, Set, Array, and Task gems
|
||||
@@ -12,28 +12,11 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
### Table of contents
|
||||
|
||||
- [What is mruby](#what-is-mruby)
|
||||
- [How to get mruby](#how-to-get-mruby)
|
||||
- [mruby homepage](#mruby-homepage)
|
||||
- [Mailing list](#mailing-list)
|
||||
- [How to compile, test, and install (mruby and gems)](#how-to-compile-test-and-install-mruby-and-gems)
|
||||
- [Amalgamation (single-file build)](#amalgamation-single-file-build)
|
||||
- [Building documentation](#building-documentation)
|
||||
- [How to customize mruby (mrbgems)](#how-to-customize-mruby-mrbgems)
|
||||
- [Index of Document](#index-of-document)
|
||||
- [License](#license)
|
||||
- [Note for License](#note-for-license)
|
||||
- [How to Contribute](#how-to-contribute)
|
||||
- [Star History](#star-history)
|
||||
- [Contributors](#contributors)
|
||||
|
||||
## What is mruby
|
||||
|
||||
mruby is the lightweight implementation of the Ruby language complying to (part
|
||||
of) the [ISO standard][ISO-standard] with more recent features provided by Ruby 4.x.
|
||||
Also, its syntax is Ruby 4.x compatible.
|
||||
of) the [ISO standard][ISO-standard] with more recent features provided by Ruby 3.x.
|
||||
Also, its syntax is Ruby 3.x compatible except for pattern matching.
|
||||
|
||||
You can link and embed mruby within your application. The "mruby" interpreter
|
||||
program and the interactive "mirb" shell are provided as examples. You can also
|
||||
@@ -47,11 +30,9 @@ of the Ministry of Economy, Trade and Industry of Japan.
|
||||
|
||||
## How to get mruby
|
||||
|
||||
To get mruby, you can download the stable version 4.0.0 from the official mruby
|
||||
To get mruby, you can download the stable version 3.4.0 from the official mruby
|
||||
GitHub repository or clone the trunk of the mruby source tree with the "git
|
||||
clone" command. You can also install and compile mruby using [ruby-install](https://github.com/postmodern/ruby-install), [ruby-build](https://github.com/rbenv/ruby-build), [rvm](https://github.com/rvm/rvm), [conda](https://anaconda.org/channels/conda-forge/packages/mruby/overview) or [Homebrew](https://formulae.brew.sh/formula/mruby).
|
||||
|
||||
The release candidate version 4.0.0 of mruby can be downloaded via the following URL: [https://github.com/mruby/mruby/archive/4.0.0-rc.zip](https://github.com/mruby/mruby/archive/4.0.0-rc.zip)
|
||||
clone" command. You can also install and compile mruby using [ruby-install](https://github.com/postmodern/ruby-install), [ruby-build](https://github.com/rbenv/ruby-build) or [rvm](https://github.com/rvm/rvm).
|
||||
|
||||
The latest development version of mruby can be downloaded via the following URL: [https://github.com/mruby/mruby/zipball/master](https://github.com/mruby/mruby/zipball/master)
|
||||
|
||||
@@ -80,21 +61,6 @@ rake all test
|
||||
|
||||
See the [compile.md](doc/guides/compile.md) file for the detail.
|
||||
|
||||
## Amalgamation (single-file build)
|
||||
|
||||
mruby supports amalgamation, which combines all source files into a single
|
||||
`mruby.c` and `mruby.h` for easy embedding (similar to SQLite).
|
||||
|
||||
```console
|
||||
rake amalgam
|
||||
```
|
||||
|
||||
Output files are generated in `build/host/amalgam/`. To use:
|
||||
|
||||
```console
|
||||
gcc -I./build/host/amalgam your_app.c ./build/host/amalgam/mruby.c -o your_app -lm
|
||||
```
|
||||
|
||||
## Building documentation
|
||||
|
||||
There are two sets of documentation in mruby: the mruby API (generated by YARD) and C API (Doxygen and Graphviz)
|
||||
@@ -129,20 +95,15 @@ extensions in C and/or Ruby. For a guide on how to use mrbgems, consult the
|
||||
<!-- BEGIN OF MRUBY DOCUMENT INDEX -->
|
||||
|
||||
- [About the Limitations of mruby](doc/limitations.md)
|
||||
- [About Amalgamation (Single-File Build)](doc/guides/amalgamation.md)
|
||||
- [C API Reference](doc/guides/capi.md)
|
||||
- [About the Compile](doc/guides/compile.md)
|
||||
- [About the Debugger with the `mrdb` Command](doc/guides/debugger.md)
|
||||
- [About GC Arena](doc/guides/gc-arena-howto.md)
|
||||
- [Getting Started with mruby](doc/guides/getting-started.md)
|
||||
- [About the mruby directory structure](doc/guides/hier.md)
|
||||
- [About Linking with `libmruby`](doc/guides/link.md)
|
||||
- [About Memory Allocator Customization and Heap Regions](doc/guides/memory.md)
|
||||
- [About Memory Allocator Customization](doc/guides/memory.md)
|
||||
- [About Build-time Configurations](doc/guides/mrbconf.md)
|
||||
- [About the Build-time Library Manager](doc/guides/mrbgems.md)
|
||||
- [ROM Method Tables for Memory-Efficient Method Registration](doc/guides/rom-method-table.md)
|
||||
- [About the Symbols](doc/guides/symbol.md)
|
||||
- [Internal Implementation / About mruby Architecture](doc/internal/architecture.md)
|
||||
- [Internal Implementation / About Value Boxing](doc/internal/boxing.md)
|
||||
- [Internal Implementation / About mruby Virtual Machine Instructions](doc/internal/opcode.md)
|
||||
|
||||
@@ -174,13 +135,5 @@ Please ask us if you want to distribute your code under another license.
|
||||
To contribute to mruby, please refer to the [contribution guidelines][contribution-guidelines] and send a pull request to the [mruby GitHub repository](https://github.com/mruby/mruby).
|
||||
By contributing, you grant non-exclusive rights to your code under the MIT License.
|
||||
|
||||
## Star History
|
||||
|
||||
[](https://www.star-history.com/#mruby/mruby&Date)
|
||||
|
||||
## Contributors
|
||||
|
||||
[](https://github.com/mruby/mruby/graphs/contributors)
|
||||
|
||||
[ISO-standard]: https://www.iso.org/standard/59579.html
|
||||
[ISO-standard]: https://www.iso.org/iso/iso_catalogue/catalogue_tc/catalogue_detail.htm?csnumber=59579
|
||||
[contribution-guidelines]: CONTRIBUTING.md
|
||||
|
||||
@@ -16,11 +16,6 @@ require "mruby/build"
|
||||
MRUBY_CONFIG = MRuby::Build.mruby_config_path
|
||||
load MRUBY_CONFIG
|
||||
|
||||
# set up all gems
|
||||
MRuby.each_target do
|
||||
gems.setup(self) if enable_gems?
|
||||
end
|
||||
|
||||
# load basic rules
|
||||
MRuby.each_target do |build|
|
||||
build.define_rules
|
||||
@@ -37,7 +32,6 @@ load "#{MRUBY_ROOT}/tasks/test.rake"
|
||||
load "#{MRUBY_ROOT}/tasks/benchmark.rake"
|
||||
load "#{MRUBY_ROOT}/tasks/doc.rake"
|
||||
load "#{MRUBY_ROOT}/tasks/install.rake"
|
||||
load "#{MRUBY_ROOT}/tasks/amalgam.rake"
|
||||
|
||||
##############################
|
||||
# generic build targets, rules
|
||||
@@ -78,22 +72,22 @@ end
|
||||
|
||||
desc "run all pre-commit hooks against all files"
|
||||
task :check do
|
||||
sh "prek run --all-files"
|
||||
sh "pre-commit run --all-files"
|
||||
end
|
||||
|
||||
desc "install the pre-commit hooks"
|
||||
task :checkinstall do
|
||||
sh "prek install"
|
||||
sh "pre-commit install"
|
||||
end
|
||||
|
||||
desc "check the pre-commit hooks for updates"
|
||||
task :checkupdate do
|
||||
sh "prek autoupdate"
|
||||
sh "pre-commit autoupdate"
|
||||
end
|
||||
|
||||
desc "run all pre-commit hooks against all files with docker-compose"
|
||||
task :composecheck do
|
||||
sh "docker-compose -p mruby run test prek run --all-files"
|
||||
sh "docker-compose -p mruby run test pre-commit run --all-files"
|
||||
end
|
||||
|
||||
desc "build and run all mruby tests with docker-compose"
|
||||
|
||||
+10
-44
@@ -2,53 +2,19 @@
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
To report a security vulnerability, please email the mruby team at <matz@ruby.or.jp>. We appreciate your efforts to disclose your findings responsibly.
|
||||
If you have any security concern, contact <matz@ruby.or.jp>.
|
||||
|
||||
## Scope
|
||||
|
||||
mruby is an embeddable Ruby implementation. Its security model is designed for integration into a host application, which is responsible for sandboxing and resource management. This policy defines what we consider a security vulnerability within the mruby interpreter itself.
|
||||
We consider the following issues as vulnerabilities:
|
||||
|
||||
### High Priority Security Vulnerabilities
|
||||
- Remote code execution
|
||||
- Crash caused by a valid Ruby script
|
||||
|
||||
We consider the following issues to be **high priority security vulnerabilities**:
|
||||
We _don't_ consider the following issues as vulnerabilities:
|
||||
|
||||
- **Remote Code Execution (RCE)**: The ability to execute arbitrary machine code or shell commands from within a Ruby script, beyond the intended execution scope of the script itself.
|
||||
|
||||
### Lower Priority: Crashes (Preferably Report as Bugs)
|
||||
|
||||
We **accept but deprioritize** the following issues. We recommend reporting them as **bug reports** on our issue tracker rather than security reports:
|
||||
|
||||
- **VM Crash on Valid Ruby Code**: Segmentation faults, assertion failures, or other interpreter crashes triggered by syntactically and semantically valid Ruby scripts.
|
||||
- _Recommendation_: Please report these as bugs on our issue tracker.
|
||||
- _Rationale_: While we will fix these issues, they typically only result in denial of service (DoS), not arbitrary code execution. They are lower priority than RCE vulnerabilities.
|
||||
- _Note_: This does not include standard Ruby exceptions like `TypeError` or `ZeroDivisionError`, which are expected behavior.
|
||||
- _Example_: A segmentation fault when running `[1, 2, 3].map { |x| x * 2 }` is best reported as a bug.
|
||||
|
||||
### Out of Scope: Not Considered Security Vulnerabilities
|
||||
|
||||
We do **not** consider the following issues to be security vulnerabilities:
|
||||
|
||||
- **Resource Exhaustion**: Infinite loops, excessive memory allocation, or high CPU usage originating from a Ruby script.
|
||||
- _Rationale_: The host application is responsible for implementing resource limits, sandboxing, and execution timeouts. mruby provides the execution engine; the host provides the constraints.
|
||||
- _Example_: `loop {}` or `"a" * (2**30)` are not vulnerabilities, even if they lead to memory or CPU exhaustion.
|
||||
|
||||
- **Crashes from Malformed Bytecode**: Crashes resulting from loading or executing corrupted or intentionally malformed `.mrb` files.
|
||||
- _Rationale_: mruby's bytecode format is not a security boundary. Applications should only execute bytecode from trusted sources.
|
||||
- _Example_: A crash discovered by fuzzing `.mrb` files is not considered a vulnerability.
|
||||
|
||||
- **Crashes from C API Misuse**: Crashes caused by incorrect usage of mruby's C API from the embedding application.
|
||||
- _Rationale_: The C API is a trusted interface for developers. The caller is responsible for adhering to the API contract (e.g., not passing `NULL` pointers, managing object lifetimes correctly).
|
||||
- _Example_: Calling `mrb_funcall()` with an invalid `mrb_state*` pointer is not a vulnerability.
|
||||
|
||||
- **Theoretical Undefined Behavior (UB)**: Issues reported by tools like ASAN, UBSan, or Valgrind that do not lead to a demonstrable crash or exploitable behavior in practice.
|
||||
- _Rationale_: While we strive for clean, well-defined code, our focus is on practical security impact. We prioritize fixing UB that is exploitable over issues that are purely theoretical.
|
||||
- _Example_: An integer overflow in an intermediate calculation that gets handled correctly before affecting program output or control flow.
|
||||
|
||||
- **Warnings on Large Memory Allocations**: Tooling warnings related to large memory allocations that do not result in a crash.
|
||||
- _Rationale_: mruby is designed to handle `malloc(3)` returning `NULL` on large allocation requests. This is considered graceful error handling, not a vulnerability.
|
||||
|
||||
### Summary
|
||||
|
||||
- **High Priority Security Reports**: Remote code execution vulnerabilities.
|
||||
- **Accepted (but preferably as bug reports)**: VM crashes from valid Ruby code.
|
||||
- **Not Accepted as Security Issues**: Resource exhaustion, malformed bytecode, C API misuse, theoretical undefined behavior, or allocation warnings.
|
||||
- Runtime C undefined behavior (including integer overflow)
|
||||
- Crash caused by misused API
|
||||
- Crash caused by modified compiled binary
|
||||
- ASAN/Valgrind warning for too big memory allocation
|
||||
mruby assumes `malloc(3)` returns `NULL` for too big allocations
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
# Things to Do in the future
|
||||
|
||||
# After mruby 3.4
|
||||
# After mruby 3.1
|
||||
|
||||
- parser and code generator independent from `mrb_state` (picoruby?)
|
||||
- variable sized AST node
|
||||
- iv/hash entry cache
|
||||
- method inline caching improvements (cache method lookup results)
|
||||
- more peephole optimization (if possible)
|
||||
- built-in profiler (method call tracing, stack profiling, detailed memory analysis)
|
||||
- improved REPL (mirb) features (syntax highlighting)
|
||||
- configurable memory pools (per-object-type, memory-constrained devices)
|
||||
- suspend/resume VM state (serialize/deserialize for power cycling)
|
||||
- CMake build support (better IDE integration, standard C tooling)
|
||||
|
||||
# Things to do (Things we need to consider)
|
||||
|
||||
- `begin ... end while cond` to behave as CRuby
|
||||
- special variables ($1,$2..)
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
def mandelbrot(c_r, c_i)
|
||||
limit=95
|
||||
iterations=0
|
||||
cr = (c_r * 100).to_i
|
||||
ci = (c_i * 100).to_i
|
||||
zr = zi = 0
|
||||
# Avoid sqrt by squaring the threshold: sqrt(x) < 1000 => x < 1000000
|
||||
while iterations<limit
|
||||
zr2 = zr*zr
|
||||
zi2 = zi*zi
|
||||
break if zr2+zi2 >= 1000000
|
||||
zr, zi = (zr2-zi2)/100+cr, (zr*zi*2)/100+ci
|
||||
iterations+=1
|
||||
end
|
||||
return iterations
|
||||
end
|
||||
|
||||
def mandel_calc(min_r, min_i, max_r, max_i, res)
|
||||
cur_i = min_i
|
||||
while cur_i > max_i
|
||||
putc "|"
|
||||
cur_r = min_r
|
||||
while cur_r < max_r
|
||||
ch = 127 - mandelbrot(cur_r, cur_i)
|
||||
putc ch # Use putc with integer - no string allocation!
|
||||
cur_r += res
|
||||
end
|
||||
putc "|"
|
||||
putc "\n"
|
||||
cur_i -= res
|
||||
end
|
||||
end
|
||||
|
||||
mandel_calc(-2, 1, 1, -1, 0.04)
|
||||
@@ -3,45 +3,39 @@
|
||||
#
|
||||
# contributed by Karl von Laudermann
|
||||
# modified by Jeremy Echols
|
||||
# optimized: while loops instead of for..in to avoid closure overhead
|
||||
|
||||
size = 600 # ARGV[0].to_i
|
||||
|
||||
puts "P4\n#{size} #{size}"
|
||||
|
||||
# Cache constants in local variables to avoid repeated constant lookup
|
||||
iter = 49
|
||||
limit_squared = 4.0
|
||||
ITER = 49 # Iterations - 1 for easy for..in looping
|
||||
LIMIT_SQUARED = 4.0 # Presquared limit
|
||||
|
||||
byte_acc = 0
|
||||
bit_num = 0
|
||||
|
||||
count_size = size - 1
|
||||
count_size = size - 1 # Precomputed size for easy for..in looping
|
||||
|
||||
# Use while loops instead of for..in to avoid closure/upvalue overhead
|
||||
y = 0
|
||||
while y <= count_size
|
||||
x = 0
|
||||
while x <= count_size
|
||||
# For..in loops are faster than .upto, .downto, .times, etc.
|
||||
for y in 0..count_size
|
||||
for x in 0..count_size
|
||||
zr = 0.0
|
||||
zi = 0.0
|
||||
cr = (2.0*x/size)-1.5
|
||||
ci = (2.0*y/size)-1.0
|
||||
escape = false
|
||||
|
||||
# Use while instead of for..in to avoid closure overhead
|
||||
i = 0
|
||||
while i <= iter
|
||||
# To make use of the for..in code, we use a dummy variable,
|
||||
# like one would in C
|
||||
for dummy in 0..ITER
|
||||
tr = zr*zr - zi*zi + cr
|
||||
ti = 2*zr*zi + ci
|
||||
zr = tr
|
||||
zi = ti
|
||||
zr, zi = tr, ti
|
||||
|
||||
if (zr*zr+zi*zi) > limit_squared
|
||||
if (zr*zr+zi*zi) > LIMIT_SQUARED
|
||||
escape = true
|
||||
break
|
||||
end
|
||||
i += 1
|
||||
end
|
||||
|
||||
byte_acc = (byte_acc << 1) | (escape ? 0b0 : 0b1)
|
||||
@@ -59,7 +53,5 @@ while y <= count_size
|
||||
byte_acc = 0
|
||||
bit_num = 0
|
||||
end
|
||||
x += 1
|
||||
end
|
||||
y += 1
|
||||
end
|
||||
|
||||
@@ -1,197 +0,0 @@
|
||||
/*
|
||||
* VM Dispatch Micro-benchmark
|
||||
*
|
||||
* This benchmark measures the raw dispatch overhead of the mruby VM
|
||||
* by executing minimal bytecode sequences.
|
||||
*
|
||||
* Compile:
|
||||
* cc -O2 -I include -I build/host/include \
|
||||
* benchmark/vm_dispatch_bench.c \
|
||||
* build/host/lib/libmruby.a -lm -o vm_dispatch_bench
|
||||
*
|
||||
* Run:
|
||||
* ./vm_dispatch_bench
|
||||
*/
|
||||
|
||||
#include <mruby.h>
|
||||
#include <mruby/compile.h>
|
||||
#include <mruby/string.h>
|
||||
#include <mruby/proc.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
|
||||
#define ITERATIONS 10
|
||||
|
||||
static double
|
||||
get_time_ms(void)
|
||||
{
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return ts.tv_sec * 1000.0 + ts.tv_nsec / 1000000.0;
|
||||
}
|
||||
|
||||
static void
|
||||
run_benchmark(mrb_state *mrb, const char *name, const char *code, int iterations)
|
||||
{
|
||||
double times[ITERATIONS];
|
||||
double total = 0.0;
|
||||
double min_time = 1e9;
|
||||
double max_time = 0.0;
|
||||
|
||||
/* Compile once */
|
||||
mrbc_context *cxt = mrbc_context_new(mrb);
|
||||
struct mrb_parser_state *p = mrb_parse_string(mrb, code, cxt);
|
||||
if (!p || p->nerr > 0) {
|
||||
fprintf(stderr, "Failed to parse: %s\n", name);
|
||||
if (p) mrb_parser_free(p);
|
||||
mrbc_context_free(mrb, cxt);
|
||||
return;
|
||||
}
|
||||
struct RProc *proc = mrb_generate_code(mrb, p);
|
||||
mrb_parser_free(p);
|
||||
mrbc_context_free(mrb, cxt);
|
||||
|
||||
if (!proc) {
|
||||
fprintf(stderr, "Failed to compile: %s\n", name);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Warm up */
|
||||
for (int i = 0; i < 3; i++) {
|
||||
mrb_top_run(mrb, proc, mrb_top_self(mrb), 0);
|
||||
mrb->exc = NULL;
|
||||
}
|
||||
|
||||
/* Measure */
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
mrb_gc_arena_save(mrb);
|
||||
mrb_full_gc(mrb);
|
||||
|
||||
double t0 = get_time_ms();
|
||||
mrb_top_run(mrb, proc, mrb_top_self(mrb), 0);
|
||||
double t1 = get_time_ms();
|
||||
|
||||
times[i] = t1 - t0;
|
||||
total += times[i];
|
||||
if (times[i] < min_time) min_time = times[i];
|
||||
if (times[i] > max_time) max_time = times[i];
|
||||
|
||||
mrb->exc = NULL;
|
||||
mrb_gc_arena_restore(mrb, 0);
|
||||
}
|
||||
|
||||
double avg = total / iterations;
|
||||
printf("%-30s avg: %8.2f ms min: %8.2f ms max: %8.2f ms\n",
|
||||
name, avg, min_time, max_time);
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
mrb_state *mrb = mrb_open();
|
||||
if (!mrb) {
|
||||
fprintf(stderr, "Failed to create mrb_state\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("========================================\n");
|
||||
printf("mruby VM Dispatch Micro-benchmarks\n");
|
||||
printf("========================================\n\n");
|
||||
|
||||
/* 1. Pure dispatch overhead */
|
||||
printf("--- Dispatch Overhead ---\n");
|
||||
|
||||
run_benchmark(mrb, "empty_loop_1M",
|
||||
"i = 0; while i < 1000000; i += 1; end", ITERATIONS);
|
||||
|
||||
run_benchmark(mrb, "empty_loop_10M",
|
||||
"i = 0; while i < 10000000; i += 1; end", ITERATIONS);
|
||||
|
||||
/* 2. Arithmetic operations */
|
||||
printf("\n--- Arithmetic ---\n");
|
||||
|
||||
run_benchmark(mrb, "int_add_1M",
|
||||
"x = 0; i = 0; while i < 1000000; x = x + 1; i += 1; end", ITERATIONS);
|
||||
|
||||
run_benchmark(mrb, "int_mul_1M",
|
||||
"x = 1; i = 0; while i < 1000000; x = x * 1; i += 1; end", ITERATIONS);
|
||||
|
||||
run_benchmark(mrb, "float_add_1M",
|
||||
"x = 0.0; i = 0; while i < 1000000; x = x + 1.0; i += 1; end", ITERATIONS);
|
||||
|
||||
/* 3. Method calls */
|
||||
printf("\n--- Method Calls ---\n");
|
||||
|
||||
run_benchmark(mrb, "empty_method_100K",
|
||||
"class X; def m; end; end; "
|
||||
"o = X.new; i = 0; while i < 100000; o.m; i += 1; end", ITERATIONS);
|
||||
|
||||
run_benchmark(mrb, "method_1arg_100K",
|
||||
"class Y; def m(a); a; end; end; "
|
||||
"o = Y.new; i = 0; while i < 100000; o.m(1); i += 1; end", ITERATIONS);
|
||||
|
||||
run_benchmark(mrb, "method_2arg_100K",
|
||||
"class Z; def m(a,b); a+b; end; end; "
|
||||
"o = Z.new; i = 0; while i < 100000; o.m(1,2); i += 1; end", ITERATIONS);
|
||||
|
||||
/* 4. Array access */
|
||||
printf("\n--- Array/Hash ---\n");
|
||||
|
||||
run_benchmark(mrb, "array_read_1M",
|
||||
"a = [0,1,2,3,4,5,6,7,8,9]; "
|
||||
"i = 0; s = 0; while i < 1000000; s += a[i % 10]; i += 1; end", ITERATIONS);
|
||||
|
||||
run_benchmark(mrb, "array_write_1M",
|
||||
"a = [0,0,0,0,0,0,0,0,0,0]; "
|
||||
"i = 0; while i < 1000000; a[i % 10] = i; i += 1; end", ITERATIONS);
|
||||
|
||||
run_benchmark(mrb, "hash_read_100K",
|
||||
"h = {0=>0,1=>1,2=>2,3=>3,4=>4,5=>5,6=>6,7=>7,8=>8,9=>9}; "
|
||||
"i = 0; s = 0; while i < 100000; s += h[i % 10]; i += 1; end", ITERATIONS);
|
||||
|
||||
/* 5. Comparison and branching */
|
||||
printf("\n--- Comparison/Branch ---\n");
|
||||
|
||||
run_benchmark(mrb, "lt_compare_1M",
|
||||
"i = 0; c = 0; while i < 1000000; c += 1 if i < 500000; i += 1; end", ITERATIONS);
|
||||
|
||||
run_benchmark(mrb, "eq_compare_1M",
|
||||
"i = 0; c = 0; while i < 1000000; c += 1 if i == 500000; i += 1; end", ITERATIONS);
|
||||
|
||||
/* 6. Block calls */
|
||||
printf("\n--- Blocks ---\n");
|
||||
|
||||
run_benchmark(mrb, "times_100K",
|
||||
"s = 0; 100000.times { |i| s += i }", ITERATIONS);
|
||||
|
||||
run_benchmark(mrb, "each_100K",
|
||||
"a = (0...1000).to_a; s = 0; 100.times { a.each { |x| s += x } }", ITERATIONS);
|
||||
|
||||
/* 7. Recursion */
|
||||
printf("\n--- Recursion ---\n");
|
||||
|
||||
run_benchmark(mrb, "fib_25",
|
||||
"def fib(n); n < 2 ? n : fib(n-1) + fib(n-2); end; fib(25)", ITERATIONS);
|
||||
|
||||
run_benchmark(mrb, "fib_30",
|
||||
"def fib(n); n < 2 ? n : fib(n-1) + fib(n-2); end; fib(30)", ITERATIONS);
|
||||
|
||||
/* 8. Local variable access */
|
||||
printf("\n--- Local Variables ---\n");
|
||||
|
||||
run_benchmark(mrb, "few_vars_1M",
|
||||
"i = 0; a = 0; b = 0; "
|
||||
"while i < 1000000; a += 1; b += 1; i += 1; end", ITERATIONS);
|
||||
|
||||
run_benchmark(mrb, "many_vars_1M",
|
||||
"i = 0; a = 0; b = 0; c = 0; d = 0; e = 0; f = 0; g = 0; h = 0; "
|
||||
"while i < 1000000; a += 1; b += 1; c += 1; d += 1; e += 1; "
|
||||
"f += 1; g += 1; h += 1; i += 1; end", ITERATIONS);
|
||||
|
||||
printf("\n========================================\n");
|
||||
printf("Benchmark complete\n");
|
||||
printf("========================================\n");
|
||||
|
||||
mrb_close(mrb);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,513 +0,0 @@
|
||||
# VM Optimization Benchmarks for mruby
|
||||
# Usage: ./bin/mruby benchmark/vm_optimization_bench.rb
|
||||
#
|
||||
# Each benchmark is designed to isolate specific VM behaviors:
|
||||
# - Dispatch overhead
|
||||
# - Arithmetic operations
|
||||
# - Method calls
|
||||
# - Array/Hash access
|
||||
# - Loop performance
|
||||
|
||||
# Benchmark infrastructure
|
||||
def measure(name, iterations = 1)
|
||||
# Warm up
|
||||
3.times { yield }
|
||||
|
||||
# Force GC before measurement
|
||||
GC.start
|
||||
|
||||
t0 = Time.now
|
||||
iterations.times { yield }
|
||||
elapsed = Time.now - t0
|
||||
|
||||
puts "#{name}: #{elapsed * 1000 / iterations} ms"
|
||||
elapsed
|
||||
end
|
||||
|
||||
N = 1_000_000
|
||||
M = 100_000
|
||||
|
||||
puts "=" * 60
|
||||
puts "mruby VM Optimization Benchmarks"
|
||||
puts "=" * 60
|
||||
puts
|
||||
|
||||
#=============================================================================
|
||||
# 1. DISPATCH OVERHEAD BENCHMARKS
|
||||
# Target: Tail-call threading, computed goto efficiency
|
||||
#=============================================================================
|
||||
puts "--- Dispatch Overhead ---"
|
||||
|
||||
# 1a. Empty loop (pure dispatch cost)
|
||||
measure("empty_loop", 10) do
|
||||
i = 0
|
||||
while i < N
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
# 1b. NOP-heavy (many instructions, minimal work)
|
||||
measure("nop_sequence", 10) do
|
||||
i = 0
|
||||
while i < M
|
||||
a = 1; b = 2; c = 3; d = 4; e = 5
|
||||
a = 1; b = 2; c = 3; d = 4; e = 5
|
||||
a = 1; b = 2; c = 3; d = 4; e = 5
|
||||
a = 1; b = 2; c = 3; d = 4; e = 5
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
#=============================================================================
|
||||
# 2. ARITHMETIC BENCHMARKS
|
||||
# Target: Type specialization, register variables, ADDI fusion
|
||||
#=============================================================================
|
||||
puts
|
||||
puts "--- Arithmetic Operations ---"
|
||||
|
||||
# 2a. Integer addition (tests OP_ADD fast path)
|
||||
measure("int_add", 10) do
|
||||
x = 0
|
||||
i = 0
|
||||
while i < N
|
||||
x = x + 1
|
||||
i += 1
|
||||
end
|
||||
x
|
||||
end
|
||||
|
||||
# 2b. Integer increment (tests potential OP_INCI fusion)
|
||||
measure("int_increment", 10) do
|
||||
x = 0
|
||||
i = 0
|
||||
while i < N
|
||||
x += 1
|
||||
i += 1
|
||||
end
|
||||
x
|
||||
end
|
||||
|
||||
# 2c. Mixed arithmetic (tests type checking overhead)
|
||||
measure("mixed_arith", 10) do
|
||||
x = 0
|
||||
y = 1.5
|
||||
i = 0
|
||||
while i < M
|
||||
x = x + 1
|
||||
y = y + 0.5
|
||||
i += 1
|
||||
end
|
||||
x
|
||||
end
|
||||
|
||||
# 2d. Comparison in loop (tests OP_LT + JMPNOT fusion potential)
|
||||
measure("comparison_loop", 10) do
|
||||
x = 0
|
||||
while x < N
|
||||
x += 1
|
||||
end
|
||||
x
|
||||
end
|
||||
|
||||
# 2e. Multiple comparisons (branch prediction)
|
||||
measure("multi_compare", 10) do
|
||||
i = 0
|
||||
count = 0
|
||||
while i < M
|
||||
count += 1 if i > 100
|
||||
count += 1 if i < 50000
|
||||
count += 1 if i == 25000
|
||||
i += 1
|
||||
end
|
||||
count
|
||||
end
|
||||
|
||||
#=============================================================================
|
||||
# 3. METHOD CALL BENCHMARKS
|
||||
# Target: Inline caching, method dispatch optimization
|
||||
#=============================================================================
|
||||
puts
|
||||
puts "--- Method Calls ---"
|
||||
|
||||
class BenchClass
|
||||
def empty_method
|
||||
end
|
||||
|
||||
def simple_add(a, b)
|
||||
a + b
|
||||
end
|
||||
|
||||
def self.class_method
|
||||
end
|
||||
end
|
||||
|
||||
$obj = BenchClass.new
|
||||
|
||||
# 3a. Empty method call (pure dispatch overhead)
|
||||
measure("empty_method_call", 10) do
|
||||
obj = $obj
|
||||
i = 0
|
||||
while i < M
|
||||
obj.empty_method
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
# 3b. Method with arguments
|
||||
measure("method_with_args", 10) do
|
||||
obj = $obj
|
||||
i = 0
|
||||
while i < M
|
||||
obj.simple_add(1, 2)
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
# 3c. Self method call (tests OP_SENDSELF potential)
|
||||
class SelfCallBench
|
||||
def run
|
||||
i = 0
|
||||
while i < M
|
||||
helper
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
def helper
|
||||
end
|
||||
end
|
||||
|
||||
measure("self_method_call", 10) do
|
||||
SelfCallBench.new.run
|
||||
end
|
||||
|
||||
# 3d. Polymorphic call site (tests inline cache invalidation)
|
||||
class Duck1
|
||||
def quack; 1; end
|
||||
end
|
||||
class Duck2
|
||||
def quack; 2; end
|
||||
end
|
||||
|
||||
$duck1 = Duck1.new
|
||||
$duck2 = Duck2.new
|
||||
|
||||
measure("polymorphic_call", 10) do
|
||||
d1, d2 = $duck1, $duck2
|
||||
i = 0
|
||||
sum = 0
|
||||
while i < M
|
||||
sum += d1.quack
|
||||
sum += d2.quack
|
||||
i += 1
|
||||
end
|
||||
sum
|
||||
end
|
||||
|
||||
#=============================================================================
|
||||
# 4. ARRAY/HASH BENCHMARKS
|
||||
# Target: GETIDX/SETIDX fast path, bounds checking
|
||||
#=============================================================================
|
||||
puts
|
||||
puts "--- Array/Hash Access ---"
|
||||
|
||||
$ary = Array.new(1000) { |i| i }
|
||||
$hash = {}
|
||||
1000.times { |i| $hash[i] = i }
|
||||
|
||||
# 4a. Array read (sequential)
|
||||
measure("array_read_seq", 10) do
|
||||
ary = $ary
|
||||
i = 0
|
||||
sum = 0
|
||||
while i < M
|
||||
sum += ary[i % 1000]
|
||||
i += 1
|
||||
end
|
||||
sum
|
||||
end
|
||||
|
||||
# 4b. Array read (constant index - tests constant propagation)
|
||||
measure("array_read_const", 10) do
|
||||
ary = $ary
|
||||
i = 0
|
||||
sum = 0
|
||||
while i < M
|
||||
sum += ary[500]
|
||||
i += 1
|
||||
end
|
||||
sum
|
||||
end
|
||||
|
||||
# 4c. Array write
|
||||
measure("array_write", 10) do
|
||||
ary = Array.new(1000, 0)
|
||||
i = 0
|
||||
while i < M
|
||||
ary[i % 1000] = i
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
# 4d. Hash read
|
||||
measure("hash_read", 10) do
|
||||
h = $hash
|
||||
i = 0
|
||||
sum = 0
|
||||
while i < M
|
||||
sum += h[i % 1000]
|
||||
i += 1
|
||||
end
|
||||
sum
|
||||
end
|
||||
|
||||
#=============================================================================
|
||||
# 5. LOOP PATTERN BENCHMARKS
|
||||
# Target: Loop optimization, branch prediction
|
||||
#=============================================================================
|
||||
puts
|
||||
puts "--- Loop Patterns ---"
|
||||
|
||||
# 5a. Simple while loop
|
||||
measure("while_loop", 10) do
|
||||
i = 0
|
||||
while i < N
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
# 5b. times iterator (block overhead)
|
||||
measure("times_iterator", 10) do
|
||||
sum = 0
|
||||
M.times do |i|
|
||||
sum += i
|
||||
end
|
||||
sum
|
||||
end
|
||||
|
||||
# 5c. each iterator on array
|
||||
$small_ary = (0...1000).to_a
|
||||
measure("each_iterator", 10) do
|
||||
ary = $small_ary
|
||||
total = 0
|
||||
1000.times do
|
||||
ary.each { |x| total += x }
|
||||
end
|
||||
total
|
||||
end
|
||||
|
||||
# 5d. Nested loops
|
||||
measure("nested_loop", 10) do
|
||||
sum = 0
|
||||
i = 0
|
||||
while i < 1000
|
||||
j = 0
|
||||
while j < 1000
|
||||
sum += 1
|
||||
j += 1
|
||||
end
|
||||
i += 1
|
||||
end
|
||||
sum
|
||||
end
|
||||
|
||||
#=============================================================================
|
||||
# 6. CONSTANT LOADING BENCHMARKS
|
||||
# Target: Constant pre-computation, pool access
|
||||
#=============================================================================
|
||||
puts
|
||||
puts "--- Constant Loading ---"
|
||||
|
||||
# 6a. Integer literals (tests LOADI optimization)
|
||||
measure("int_literals", 10) do
|
||||
i = 0
|
||||
sum = 0
|
||||
while i < M
|
||||
sum += 1
|
||||
sum += 2
|
||||
sum += 3
|
||||
sum += 42
|
||||
sum += 100
|
||||
i += 1
|
||||
end
|
||||
sum
|
||||
end
|
||||
|
||||
# 6b. Large integer literals (tests LOADL from pool)
|
||||
measure("large_int_literals", 10) do
|
||||
i = 0
|
||||
sum = 0
|
||||
while i < M
|
||||
sum += 1000000
|
||||
sum += 2000000
|
||||
sum += 3000000
|
||||
i += 1
|
||||
end
|
||||
sum
|
||||
end
|
||||
|
||||
# 6c. Float literals
|
||||
measure("float_literals", 10) do
|
||||
i = 0
|
||||
sum = 0.0
|
||||
while i < M
|
||||
sum += 1.5
|
||||
sum += 2.5
|
||||
sum += 3.5
|
||||
i += 1
|
||||
end
|
||||
sum
|
||||
end
|
||||
|
||||
# 6d. String literals (allocation vs interning)
|
||||
measure("string_literals", 5) do
|
||||
i = 0
|
||||
while i < 100000
|
||||
s = "hello"
|
||||
s = "world"
|
||||
s = "test"
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
#=============================================================================
|
||||
# 7. BRANCH PREDICTION BENCHMARKS
|
||||
# Target: mrb_likely/mrb_unlikely effectiveness
|
||||
#=============================================================================
|
||||
puts
|
||||
puts "--- Branch Prediction ---"
|
||||
|
||||
# 7a. Predictable branch (always true)
|
||||
measure("predictable_true", 10) do
|
||||
i = 0
|
||||
count = 0
|
||||
while i < N
|
||||
count += 1 if true
|
||||
i += 1
|
||||
end
|
||||
count
|
||||
end
|
||||
|
||||
# 7b. Predictable branch (always false)
|
||||
measure("predictable_false", 10) do
|
||||
i = 0
|
||||
count = 0
|
||||
while i < N
|
||||
count += 1 if false
|
||||
i += 1
|
||||
end
|
||||
count
|
||||
end
|
||||
|
||||
# 7c. Unpredictable branch (50/50)
|
||||
measure("unpredictable_50", 10) do
|
||||
i = 0
|
||||
count = 0
|
||||
while i < M
|
||||
count += 1 if i & 1 == 0
|
||||
i += 1
|
||||
end
|
||||
count
|
||||
end
|
||||
|
||||
# 7d. Rare branch (error path simulation)
|
||||
measure("rare_branch", 10) do
|
||||
i = 0
|
||||
count = 0
|
||||
while i < N
|
||||
count += 1 if i == -1 # Never true
|
||||
i += 1
|
||||
end
|
||||
count
|
||||
end
|
||||
|
||||
#=============================================================================
|
||||
# 8. REGISTER PRESSURE BENCHMARKS
|
||||
# Target: Register variable optimization
|
||||
#=============================================================================
|
||||
puts
|
||||
puts "--- Register Pressure ---"
|
||||
|
||||
# 8a. Few local variables (should fit in registers)
|
||||
measure("few_locals", 10) do
|
||||
i = 0
|
||||
a = 0
|
||||
while i < N
|
||||
a += 1
|
||||
i += 1
|
||||
end
|
||||
a
|
||||
end
|
||||
|
||||
# 8b. Many local variables (register spilling)
|
||||
measure("many_locals", 10) do
|
||||
i = 0
|
||||
a = 0; b = 0; c = 0; d = 0; e = 0
|
||||
f = 0; g = 0; h = 0; j = 0; k = 0
|
||||
l = 0; m = 0; n = 0; o = 0; p = 0
|
||||
while i < M
|
||||
a += 1; b += 1; c += 1; d += 1; e += 1
|
||||
f += 1; g += 1; h += 1; j += 1; k += 1
|
||||
l += 1; m += 1; n += 1; o += 1; p += 1
|
||||
i += 1
|
||||
end
|
||||
a + b + c + d + e + f + g + h + j + k + l + m + n + o + p
|
||||
end
|
||||
|
||||
#=============================================================================
|
||||
# 9. COMPOSITE BENCHMARKS (Real-world-ish)
|
||||
#=============================================================================
|
||||
puts
|
||||
puts "--- Composite Benchmarks ---"
|
||||
|
||||
# 9a. Fibonacci (recursion + arithmetic)
|
||||
def fib(n)
|
||||
return n if n < 2
|
||||
fib(n - 1) + fib(n - 2)
|
||||
end
|
||||
|
||||
measure("fibonacci_30", 3) do
|
||||
fib(30)
|
||||
end
|
||||
|
||||
# 9b. Tak function (heavy recursion)
|
||||
def tak(x, y, z)
|
||||
if y < x
|
||||
tak(tak(x - 1, y, z), tak(y - 1, z, x), tak(z - 1, x, y))
|
||||
else
|
||||
z
|
||||
end
|
||||
end
|
||||
|
||||
measure("tak_18_12_6", 3) do
|
||||
tak(18, 12, 6)
|
||||
end
|
||||
|
||||
# 9c. Array manipulation
|
||||
measure("array_manipulation", 5) do
|
||||
ary = []
|
||||
10000.times { |i| ary << i }
|
||||
ary.map! { |x| x * 2 }
|
||||
ary.select { |x| x % 3 == 0 }.size
|
||||
end
|
||||
|
||||
# 9d. String operations
|
||||
measure("string_ops", 5) do
|
||||
s = ""
|
||||
10000.times { |i| s = s + i.to_s }
|
||||
s.size
|
||||
end
|
||||
|
||||
# 9e. Hash operations
|
||||
measure("hash_ops", 5) do
|
||||
h = {}
|
||||
50000.times { |i| h[i.to_s] = i }
|
||||
sum = 0
|
||||
h.each { |k, v| sum += v }
|
||||
sum
|
||||
end
|
||||
|
||||
puts
|
||||
puts "=" * 60
|
||||
puts "Benchmark complete"
|
||||
puts "=" * 60
|
||||
@@ -1,86 +0,0 @@
|
||||
# Cosmopolitan Libc build configuration for mruby
|
||||
# https://github.com/jart/cosmopolitan
|
||||
#
|
||||
# Produces Actually Portable Executables (APE) - single binaries that run on:
|
||||
# - Linux (x86_64, ARM64)
|
||||
# - macOS (x86_64, ARM64)
|
||||
# - Windows (x86_64)
|
||||
# - FreeBSD (x86_64)
|
||||
# - OpenBSD (x86_64)
|
||||
# - NetBSD (x86_64)
|
||||
#
|
||||
# Requirements:
|
||||
# Download cosmocc toolchain from https://cosmo.zip/pub/cosmocc/
|
||||
#
|
||||
# Usage:
|
||||
# COSMO_ROOT=/path/to/cosmocc rake MRUBY_CONFIG=cosmopolitan
|
||||
#
|
||||
# The resulting binaries in bin/ will have .com extension and run on all
|
||||
# supported platforms without recompilation.
|
||||
|
||||
COSMO_ROOT = ENV['COSMO_ROOT']
|
||||
|
||||
unless COSMO_ROOT && File.directory?(COSMO_ROOT)
|
||||
msg = <<~MSG
|
||||
Cosmopolitan toolchain not found.
|
||||
|
||||
Please set COSMO_ROOT environment variable to the cosmocc directory:
|
||||
|
||||
mkdir -p ~/cosmo && cd ~/cosmo
|
||||
wget https://cosmo.zip/pub/cosmocc/cosmocc.zip
|
||||
unzip cosmocc.zip
|
||||
export COSMO_ROOT=~/cosmo
|
||||
|
||||
Then run:
|
||||
COSMO_ROOT=~/cosmo rake MRUBY_CONFIG=cosmopolitan
|
||||
MSG
|
||||
raise msg
|
||||
end
|
||||
|
||||
MRuby::Build.new do |conf|
|
||||
# C compiler
|
||||
conf.cc do |cc|
|
||||
cc.command = "#{COSMO_ROOT}/bin/cosmocc"
|
||||
cc.flags = %w[-Os -fno-omit-frame-pointer]
|
||||
end
|
||||
|
||||
# C++ compiler
|
||||
conf.cxx do |cxx|
|
||||
cxx.command = "#{COSMO_ROOT}/bin/cosmoc++"
|
||||
cxx.flags = conf.cc.flags.dup
|
||||
end
|
||||
|
||||
# Linker
|
||||
conf.linker do |linker|
|
||||
linker.command = "#{COSMO_ROOT}/bin/cosmocc"
|
||||
linker.flags = %w[-static]
|
||||
end
|
||||
|
||||
# Archiver
|
||||
conf.archiver do |archiver|
|
||||
archiver.command = "#{COSMO_ROOT}/bin/cosmoar"
|
||||
end
|
||||
|
||||
# APE binaries use .com extension
|
||||
conf.exts.executable = '.com'
|
||||
|
||||
# Cosmopolitan provides POSIX compatibility, explicitly select POSIX HALs
|
||||
conf.gem core: 'hal-posix-io'
|
||||
conf.gem core: 'hal-posix-socket'
|
||||
conf.gem core: 'hal-posix-dir'
|
||||
|
||||
# Standard library
|
||||
conf.gembox 'stdlib'
|
||||
conf.gembox 'stdlib-ext'
|
||||
conf.gembox 'stdlib-io' # Includes mruby-io, mruby-socket, mruby-dir
|
||||
conf.gembox 'math'
|
||||
conf.gembox 'metaprog'
|
||||
|
||||
# Binary tools
|
||||
# Note: mruby-bin-config is a shell script and incompatible with .com extension
|
||||
conf.gem core: 'mruby-bin-mrbc'
|
||||
conf.gem core: 'mruby-bin-mruby'
|
||||
conf.gem core: 'mruby-bin-strip'
|
||||
conf.gem core: 'mruby-bin-mirb'
|
||||
conf.gem core: 'mruby-bin-debugger'
|
||||
end
|
||||
@@ -5,16 +5,12 @@
|
||||
# http://gamedev.allusion.net/softprj/kos/
|
||||
#
|
||||
# This configuration has been improved to be used as KallistiOS Port (kos-ports)
|
||||
# Updated: 2025-07-31
|
||||
# Updated: 2023-12-24
|
||||
#
|
||||
# Tested on GNU/Linux, macOS and Windows (MinGW-w64/MSYS2, Cygwin, DreamSDK)
|
||||
# DreamSDK is based on both MinGW/MSYS and MinGW-w64/MSYS2: https://dreamsdk.org/
|
||||
# DreamSDK is based on MinGW/MSYS: https://dreamsdk.org/
|
||||
#
|
||||
# Install mruby for Sega Dreamcast using the "mruby" kos-port.
|
||||
# See: https://github.com/kallistios/kallistios
|
||||
#
|
||||
# If you want to see examples, check the /examples/dreamcast/mruby directory
|
||||
# in the KallistiOS repository.
|
||||
#
|
||||
MRuby::CrossBuild.new("dreamcast") do |conf|
|
||||
toolchain :gcc
|
||||
@@ -23,37 +19,42 @@ MRuby::CrossBuild.new("dreamcast") do |conf|
|
||||
KOS_BASE = ENV["KOS_BASE"]
|
||||
KOS_CC_BASE = ENV["KOS_CC_BASE"]
|
||||
|
||||
# Check environment variables
|
||||
if KOS_BASE.to_s.empty?
|
||||
raise "Error: KallistiOS is required; KOS_BASE need to be declared; Stop."
|
||||
if (KOS_BASE.nil? || KOS_BASE.empty? || KOS_CC_BASE.nil? || KOS_CC_BASE.empty?)
|
||||
raise "Error: KallistiOS is required; KOS_BASE/KOS_CC_BASE needs to be declared; Stop."
|
||||
end
|
||||
|
||||
# Root directory for KallistiOS wrappers
|
||||
# This will handle specific DreamSDK wrappers if needed
|
||||
KOS_WRAPPERS_BASE = if ENV["ENVIRONMENT_NAME"] == "DreamSDK" && ENV["RAKE_AVAILABLE"] == "0"
|
||||
"#{KOS_CC_BASE}/bin"
|
||||
else
|
||||
"#{KOS_BASE}/utils/build_wrappers"
|
||||
end
|
||||
|
||||
# C compiler
|
||||
# All flags and settings below were extracted from KallistiOS environment files
|
||||
conf.cc do |cc|
|
||||
cc.command = "#{KOS_WRAPPERS_BASE}/kos-cc"
|
||||
cc.command = "#{KOS_CC_BASE}/bin/sh-elf-gcc"
|
||||
cc.include_paths << ["#{KOS_BASE}/include", "#{KOS_BASE}/kernel/arch/dreamcast/include", "#{KOS_BASE}/addons/include", "#{KOS_BASE}/../kos-ports/include"]
|
||||
cc.flags << ["-O2", "-fomit-frame-pointer", "-fno-builtin", "-ml", "-m4-single-only", "-ffunction-sections", "-fdata-sections", "-matomic-model=soft-imask", "-ftls-model=local-exec", "-Wall", "-g"]
|
||||
cc.compile_options = %Q[%{flags} -o "%{outfile}" -c "%{infile}"]
|
||||
cc.defines << %w(_arch_dreamcast)
|
||||
cc.defines << %w(_arch_sub_pristine)
|
||||
end
|
||||
|
||||
# C++ compiler
|
||||
conf.cxx do |cxx|
|
||||
cxx.command = "#{KOS_WRAPPERS_BASE}/kos-c++"
|
||||
cxx.command = conf.cc.command.dup
|
||||
cxx.include_paths = conf.cc.include_paths.dup
|
||||
cxx.flags = conf.cc.flags.dup
|
||||
cxx.flags << %w(-fno-operator-names)
|
||||
cxx.defines = conf.cc.defines.dup
|
||||
cxx.compile_options = conf.cc.compile_options.dup
|
||||
end
|
||||
|
||||
# Linker
|
||||
conf.linker do |linker|
|
||||
linker.command = "#{KOS_WRAPPERS_BASE}/kos-ld"
|
||||
linker.command = "#{KOS_CC_BASE}/bin/sh-elf-gcc"
|
||||
linker.flags << ["-Wl,-Ttext=0x8c010000", "-Wl,--gc-sections", "-T#{KOS_BASE}/utils/ldscripts/shlelf.xc", "-nodefaultlibs", "-Wl,--start-group -lkallisti -lc -lgcc -Wl,--end-group"]
|
||||
linker.library_paths << ["#{KOS_BASE}/lib/dreamcast", "#{KOS_BASE}/addons/lib/dreamcast", "#{KOS_BASE}/../kos-ports/lib"]
|
||||
end
|
||||
|
||||
# Archiver
|
||||
conf.archiver do |archiver|
|
||||
archiver.command = "#{KOS_WRAPPERS_BASE}/kos-ar"
|
||||
archiver.command = "#{KOS_CC_BASE}/bin/sh-elf-ar"
|
||||
archiver.archive_options = 'rcs "%{outfile}" %{objs}'
|
||||
end
|
||||
|
||||
# No executables needed for KallistiOS
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
MRuby::Build.new do |conf|
|
||||
# load specific toolchain settings
|
||||
toolchain :gcc
|
||||
|
||||
# include the GEM box
|
||||
conf.gembox 'full-core'
|
||||
|
||||
conf.cc.flags << '-m32'
|
||||
conf.cc.defines << 'MRB_USE_FLOAT32'
|
||||
conf.linker.flags << '-m32'
|
||||
|
||||
# Turn on `enable_debug` for better debugging
|
||||
conf.enable_debug
|
||||
conf.enable_test
|
||||
conf.enable_bintest
|
||||
end
|
||||
@@ -7,4 +7,5 @@ MRuby::Build.new do |conf|
|
||||
|
||||
conf.build_mrbc_exec
|
||||
conf.disable_libmruby
|
||||
conf.disable_presym
|
||||
end
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
# mruby Documentation
|
||||
|
||||
## Getting Started
|
||||
|
||||
New to mruby? Start here:
|
||||
|
||||
| Document | Description |
|
||||
| -------- | ----------- |
|
||||
| [Getting Started](guides/getting-started.md) | Build mruby and run your first program |
|
||||
| [Language Features](guides/language.md) | Ruby subset supported by mruby |
|
||||
| [Limitations](limitations.md) | Behavioral differences from CRuby |
|
||||
|
||||
## Guides (for embedders and gem authors)
|
||||
|
||||
### Embedding mruby in C
|
||||
|
||||
| Document | Description |
|
||||
| -------- | ----------- |
|
||||
| [C API Reference](guides/capi.md) | Values, classes, methods, error handling, fibers |
|
||||
| [GC Arena](guides/gc-arena-howto.md) | Managing temporary objects in C extensions |
|
||||
| [Linking](guides/link.md) | Linking with `libmruby` |
|
||||
| [Amalgamation](guides/amalgamation.md) | Single-file build for easy integration |
|
||||
| [Precompiled Symbols](guides/symbol.md) | Compile-time symbol allocation |
|
||||
|
||||
### Building and Configuring
|
||||
|
||||
| Document | Description |
|
||||
| -------- | ----------- |
|
||||
| [Compilation](guides/compile.md) | Build system, cross-compilation, toolchains |
|
||||
| [Build Configuration](guides/mrbconf.md) | Compile-time macros (`MRB_*` flags) |
|
||||
| [mrbgems](guides/mrbgems.md) | Creating and managing gems |
|
||||
| [Memory](guides/memory.md) | Allocator customization and heap regions |
|
||||
|
||||
### Tools
|
||||
|
||||
| Document | Description |
|
||||
| -------- | ----------- |
|
||||
| [Debugger](guides/debugger.md) | Using `mrdb` for debugging |
|
||||
| [ROM Method Tables](guides/rom-method-table.md) | Read-only method tables for constrained devices |
|
||||
|
||||
### Reference
|
||||
|
||||
| Document | Description |
|
||||
| -------- | ----------- |
|
||||
| [Directory Structure](guides/hier.md) | Source tree layout |
|
||||
|
||||
## Internals (for mruby contributors)
|
||||
|
||||
Start with [Architecture](internal/architecture.md) for an overview,
|
||||
then dive into the subsystem you need:
|
||||
|
||||
| Document | Description |
|
||||
| -------- | ----------- |
|
||||
| [Architecture](internal/architecture.md) | Overview of object model, VM, GC, compiler |
|
||||
| [Virtual Machine](internal/vm.md) | Dispatch loop, call frames, method lookup, fibers |
|
||||
| [Garbage Collector](internal/gc.md) | Tri-color marking, write barriers, generational GC |
|
||||
| [Compiler Pipeline](internal/compiler.md) | Parser, code generator, IRep, binary format |
|
||||
| [Opcodes](internal/opcode.md) | VM instruction set reference |
|
||||
| [Value Boxing](internal/boxing.md) | How `mrb_value` encodes types |
|
||||
|
||||
## Release Notes
|
||||
|
||||
- [mruby 3.4](mruby3.4.md)
|
||||
- [mruby 3.3](mruby3.3.md)
|
||||
- [mruby 3.2](mruby3.2.md)
|
||||
- [mruby 3.1](mruby3.1.md)
|
||||
- [mruby 3.0](mruby3.0.md)
|
||||
@@ -1,153 +0,0 @@
|
||||
<!-- summary: About Amalgamation (Single-File Build) -->
|
||||
|
||||
# Amalgamation
|
||||
|
||||
Amalgamation combines all mruby source files into a single `mruby.c` and
|
||||
`mruby.h` for easy embedding, similar to SQLite's distribution model.
|
||||
|
||||
## Benefits
|
||||
|
||||
- **Simple integration**: Just two files to add to your project
|
||||
- **Single compilation unit**: Enables better compiler optimization
|
||||
- **No build system required**: Compile directly with any C compiler
|
||||
- **Portable**: No external dependencies beyond standard C library
|
||||
(but see [Platform-Dependent Gems](#platform-dependent-gems) below)
|
||||
|
||||
## Generating Amalgamation
|
||||
|
||||
```console
|
||||
rake amalgam
|
||||
```
|
||||
|
||||
Output files are generated in `build/<target>/amalgam/`:
|
||||
|
||||
- `mruby.h` - All headers concatenated in dependency order
|
||||
- `mruby.c` - All sources concatenated (core + gems + mrblib)
|
||||
|
||||
### With Custom Configuration
|
||||
|
||||
The amalgamation includes gems specified in your build configuration:
|
||||
|
||||
```console
|
||||
MRUBY_CONFIG=build_config/minimal.rb rake amalgam
|
||||
```
|
||||
|
||||
## Using the Amalgamation
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```c
|
||||
#include "mruby.h"
|
||||
|
||||
int main(void) {
|
||||
mrb_state *mrb = mrb_open();
|
||||
mrb_load_string(mrb, "puts 'Hello from mruby!'");
|
||||
mrb_close(mrb);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### Compiling
|
||||
|
||||
```console
|
||||
gcc -I./build/host/amalgam your_app.c ./build/host/amalgam/mruby.c -o your_app -lm
|
||||
```
|
||||
|
||||
For optimized builds:
|
||||
|
||||
```console
|
||||
gcc -O2 -DNDEBUG -I./build/host/amalgam your_app.c ./build/host/amalgam/mruby.c -o your_app -lm
|
||||
```
|
||||
|
||||
## Gem Compatibility
|
||||
|
||||
### Known Working Gems
|
||||
|
||||
The following gems work with amalgamation:
|
||||
|
||||
- `mruby-compiler` - Required for `mrb_load_string`
|
||||
- `mruby-eval` - `eval`, `Binding`
|
||||
- `mruby-array-ext`, `mruby-string-ext`, `mruby-hash-ext`
|
||||
- `mruby-numeric-ext`, `mruby-range-ext`, `mruby-symbol-ext`
|
||||
- `mruby-proc-ext`, `mruby-kernel-ext`, `mruby-object-ext`, `mruby-class-ext`
|
||||
- `mruby-enum-ext`, `mruby-compar-ext`
|
||||
- `mruby-error`, `mruby-math`, `mruby-struct`
|
||||
- `mruby-bigint`, `mruby-rational`, `mruby-complex`
|
||||
- `mruby-io` (with `hal-posix-io`)
|
||||
- `mruby-task` (with `hal-posix-task`)
|
||||
|
||||
### Platform-Dependent Gems
|
||||
|
||||
Gems that use a HAL (Hardware Abstraction Layer) include
|
||||
platform-specific code in the amalgamation. For example, if
|
||||
`mruby-io` selects `hal-posix-io` on Linux, the generated `mruby.c`
|
||||
contains POSIX-specific code and cannot be compiled on Windows.
|
||||
|
||||
If you need amalgamated files for multiple platforms, generate them
|
||||
separately for each target platform (or cross-build configuration).
|
||||
|
||||
### Excluded Gems
|
||||
|
||||
Binary gems (`mruby-bin-*`) are automatically excluded as they contain
|
||||
their own `main()` function. The amalgamation produces a library, not
|
||||
an executable.
|
||||
|
||||
## Example Configuration
|
||||
|
||||
A minimal configuration for amalgamation:
|
||||
|
||||
```ruby
|
||||
# build_config/amalgam.rb
|
||||
MRuby::Build.new do |conf|
|
||||
conf.toolchain :gcc
|
||||
|
||||
conf.gem core: 'mruby-compiler'
|
||||
conf.gem core: 'mruby-error'
|
||||
conf.gem core: 'mruby-eval'
|
||||
conf.gem core: 'mruby-array-ext'
|
||||
conf.gem core: 'mruby-string-ext'
|
||||
conf.gem core: 'mruby-hash-ext'
|
||||
conf.gem core: 'mruby-io'
|
||||
end
|
||||
```
|
||||
|
||||
Generate with:
|
||||
|
||||
```console
|
||||
MRUBY_CONFIG=build_config/amalgam.rb rake amalgam
|
||||
```
|
||||
|
||||
## Output Sizes
|
||||
|
||||
Typical sizes depend on included gems:
|
||||
|
||||
- `mruby.h`: 200-500 KB
|
||||
- `mruby.c`: 2-4 MB
|
||||
|
||||
## Technical Details
|
||||
|
||||
### Header Processing
|
||||
|
||||
- Include guards are stripped to allow concatenation
|
||||
- Headers are ordered by dependency (foundation types first)
|
||||
- Internal includes are commented out (already in `mruby.h`)
|
||||
|
||||
### Source Processing
|
||||
|
||||
- Sources are concatenated in proper initialization order
|
||||
- X-macro headers (like `mruby/ops.h`) are inlined at each use
|
||||
- Local includes (`.cstub` files) are automatically inlined
|
||||
- Generated files (`mrblib.c`, `gem_init.c`) are included
|
||||
|
||||
### Gem Defines
|
||||
|
||||
Gems that add preprocessor defines affecting core structures are
|
||||
automatically detected and included at the top of `mruby.h`.
|
||||
Supported patterns: `MRB_USE_*`, `MRB_UTF8_*`, `HAVE_MRUBY_*`.
|
||||
|
||||
### Build Order
|
||||
|
||||
1. Core sources (`src/*.c`)
|
||||
2. Gem sources (`mrbgems/*/src/*.c` or `core/*.c`)
|
||||
3. Generated mrblib (`build/*/mrblib/mrblib.c`)
|
||||
4. Gem initialization (`build/*/mrbgems/gem_init.c`)
|
||||
@@ -1,856 +0,0 @@
|
||||
<!-- summary: C API Reference -->
|
||||
|
||||
# C API Reference
|
||||
|
||||
This document covers the mruby C API for embedding and extending mruby.
|
||||
|
||||
**Contents:**
|
||||
[Headers](#headers) |
|
||||
[State Management](#state-management) |
|
||||
[Values](#values) |
|
||||
[Defining Classes and Modules](#defining-classes-and-modules) |
|
||||
[Defining Methods](#defining-methods) |
|
||||
[Parsing Arguments](#parsing-arguments) |
|
||||
[Calling Ruby Methods from C](#calling-ruby-methods-from-c) |
|
||||
[String Operations](#string-operations) |
|
||||
[Array Operations](#array-operations) |
|
||||
[Hash Operations](#hash-operations) |
|
||||
[Wrapping C Structures](#wrapping-c-structures) |
|
||||
[Exception Handling](#exception-handling) |
|
||||
[Method Visibility](#method-visibility) |
|
||||
[Proc and Block Handling](#proc-and-block-handling) |
|
||||
[Fiber API](#fiber-api) |
|
||||
[Compilation Contexts](#compilation-contexts) |
|
||||
[Precompiled Bytecode](#precompiled-bytecode) |
|
||||
[GC Arena](#gc-arena) |
|
||||
[Memory Allocation](#memory-allocation)
|
||||
|
||||
## Headers
|
||||
|
||||
```c
|
||||
#include <mruby.h> /* core types, state, class/method definition */
|
||||
#include <mruby/compile.h> /* mrb_load_string, mrb_load_file */
|
||||
#include <mruby/string.h> /* string operations */
|
||||
#include <mruby/array.h> /* array operations */
|
||||
#include <mruby/hash.h> /* hash operations */
|
||||
#include <mruby/data.h> /* wrapping C structs */
|
||||
#include <mruby/class.h> /* class inspection */
|
||||
#include <mruby/value.h> /* value type macros */
|
||||
#include <mruby/irep.h> /* loading precompiled bytecode */
|
||||
#include <mruby/error.h> /* error handling (mrb_protect etc.) */
|
||||
#include <mruby/variable.h> /* instance/class/global variables */
|
||||
```
|
||||
|
||||
## State Management
|
||||
|
||||
```c
|
||||
mrb_state *mrb = mrb_open(); /* create state with all gems */
|
||||
mrb_state *mrb = mrb_open_core(); /* create state without gems */
|
||||
mrb_close(mrb); /* close and free state */
|
||||
```
|
||||
|
||||
`mrb_open()` returns `NULL` on allocation failure. Always check the
|
||||
return value.
|
||||
|
||||
## Values
|
||||
|
||||
All Ruby values are represented as `mrb_value` in C.
|
||||
|
||||
### Creating Values
|
||||
|
||||
```c
|
||||
mrb_nil_value() /* nil */
|
||||
mrb_true_value() /* true */
|
||||
mrb_false_value() /* false */
|
||||
mrb_bool_value(mrb_bool b) /* true or false */
|
||||
mrb_fixnum_value(mrb_int i) /* Integer */
|
||||
mrb_float_value(mrb_state *mrb, mrb_float f) /* Float */
|
||||
mrb_symbol_value(mrb_sym sym) /* Symbol */
|
||||
mrb_obj_value(void *p) /* object pointer to value */
|
||||
mrb_cptr_value(mrb_state *mrb, void *p) /* C pointer */
|
||||
```
|
||||
|
||||
### Type Checking
|
||||
|
||||
```c
|
||||
mrb_type(v) /* returns enum mrb_vtype */
|
||||
mrb_nil_p(v) /* true if nil */
|
||||
mrb_integer_p(v) /* true if Integer */
|
||||
mrb_float_p(v) /* true if Float */
|
||||
mrb_symbol_p(v) /* true if Symbol */
|
||||
mrb_string_p(v) /* true if String */
|
||||
mrb_array_p(v) /* true if Array */
|
||||
mrb_hash_p(v) /* true if Hash */
|
||||
mrb_true_p(v) /* true if true */
|
||||
mrb_false_p(v) /* true if false */
|
||||
mrb_undef_p(v) /* true if undefined */
|
||||
mrb_immediate_p(v) /* true if not a heap object */
|
||||
```
|
||||
|
||||
### Extracting C Values
|
||||
|
||||
```c
|
||||
mrb_integer(v) /* mrb_int from Integer value */
|
||||
mrb_float(v) /* mrb_float from Float value */
|
||||
mrb_symbol(v) /* mrb_sym from Symbol value */
|
||||
mrb_ptr(v) /* void* from object value */
|
||||
mrb_str_to_cstr(mrb, v) /* const char* from String value */
|
||||
```
|
||||
|
||||
### Value Types
|
||||
|
||||
| `mrb_vtype` | Ruby Class | Notes |
|
||||
| ----------- | ---------- | ----- |
|
||||
| `MRB_TT_FALSE` | FalseClass/NilClass | `nil` has `MRB_TT_FALSE` |
|
||||
| `MRB_TT_TRUE` | TrueClass | |
|
||||
| `MRB_TT_INTEGER` | Integer | Immediate value |
|
||||
| `MRB_TT_FLOAT` | Float | May be immediate |
|
||||
| `MRB_TT_SYMBOL` | Symbol | Immediate value |
|
||||
| `MRB_TT_STRING` | String | Heap object |
|
||||
| `MRB_TT_ARRAY` | Array | Heap object |
|
||||
| `MRB_TT_HASH` | Hash | Heap object |
|
||||
| `MRB_TT_OBJECT` | Object | User-defined classes |
|
||||
| `MRB_TT_CLASS` | Class | |
|
||||
| `MRB_TT_MODULE` | Module | |
|
||||
| `MRB_TT_PROC` | Proc | |
|
||||
| `MRB_TT_CDATA` | (C data) | Wrapped C structs |
|
||||
| `MRB_TT_EXCEPTION` | Exception | |
|
||||
| `MRB_TT_FIBER` | Fiber | |
|
||||
|
||||
## Defining Classes and Modules
|
||||
|
||||
```c
|
||||
/* Define a class under Object */
|
||||
struct RClass *my_class = mrb_define_class(mrb, "MyClass", mrb->object_class);
|
||||
|
||||
/* Define a class under another class/module */
|
||||
struct RClass *inner = mrb_define_class_under(mrb, outer, "Inner", mrb->object_class);
|
||||
|
||||
/* Define a module */
|
||||
struct RClass *my_mod = mrb_define_module(mrb, "MyModule");
|
||||
struct RClass *inner_mod = mrb_define_module_under(mrb, outer, "InnerMod");
|
||||
|
||||
/* Include/prepend a module */
|
||||
mrb_include_module(mrb, my_class, my_mod);
|
||||
mrb_prepend_module(mrb, my_class, my_mod);
|
||||
|
||||
/* Look up existing class/module */
|
||||
struct RClass *c = mrb_class_get(mrb, "String");
|
||||
struct RClass *m = mrb_module_get(mrb, "Kernel");
|
||||
|
||||
/* Define a constant */
|
||||
mrb_define_const(mrb, my_class, "VERSION", mrb_str_new_lit(mrb, "1.0"));
|
||||
```
|
||||
|
||||
## Defining Methods
|
||||
|
||||
All C methods have the same signature:
|
||||
|
||||
```c
|
||||
static mrb_value
|
||||
my_method(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
/* self is the receiver */
|
||||
return mrb_nil_value();
|
||||
}
|
||||
```
|
||||
|
||||
Register with:
|
||||
|
||||
```c
|
||||
mrb_define_method(mrb, klass, "name", my_method, MRB_ARGS_NONE());
|
||||
mrb_define_class_method(mrb, klass, "name", my_method, MRB_ARGS_REQ(1));
|
||||
mrb_define_module_function(mrb, mod, "name", my_method, MRB_ARGS_ANY());
|
||||
```
|
||||
|
||||
### Argument Specifiers
|
||||
|
||||
| Macro | Meaning |
|
||||
| ----- | ------- |
|
||||
| `MRB_ARGS_NONE()` | No arguments |
|
||||
| `MRB_ARGS_REQ(n)` | `n` required arguments |
|
||||
| `MRB_ARGS_OPT(n)` | `n` optional arguments |
|
||||
| `MRB_ARGS_ARG(r,o)` | `r` required + `o` optional |
|
||||
| `MRB_ARGS_REST()` | Splat (`*args`) |
|
||||
| `MRB_ARGS_BLOCK()` | Block (`&block`) |
|
||||
| `MRB_ARGS_ANY()` | Any number (same as REST) |
|
||||
| `MRB_ARGS_KEY(n,rest)` | `n` keyword args, `rest`=1 for `**kw` |
|
||||
|
||||
These can be combined with `|`:
|
||||
|
||||
```c
|
||||
MRB_ARGS_REQ(1) | MRB_ARGS_OPT(2) | MRB_ARGS_BLOCK()
|
||||
```
|
||||
|
||||
## Parsing Arguments
|
||||
|
||||
`mrb_get_args()` extracts arguments from the Ruby call stack:
|
||||
|
||||
```c
|
||||
mrb_int mrb_get_args(mrb_state *mrb, const char *format, ...);
|
||||
```
|
||||
|
||||
### Format Specifiers
|
||||
|
||||
| Spec | Ruby Type | C Type(s) | Notes |
|
||||
| ---- | --------- | --------- | ----- |
|
||||
| `o` | any | `mrb_value` | No type check |
|
||||
| `i` | Numeric | `mrb_int` | Coerces to integer |
|
||||
| `f` | Numeric | `mrb_float` | Coerces to float |
|
||||
| `b` | any | `mrb_bool` | Truthiness |
|
||||
| `n` | String/Symbol | `mrb_sym` | Converts to symbol |
|
||||
| `s` | String | `const char*, mrb_int` | Pointer + length |
|
||||
| `z` | String | `const char*` | Null-terminated |
|
||||
| `S` | String | `mrb_value` | String value |
|
||||
| `A` | Array | `mrb_value` | Array value |
|
||||
| `H` | Hash | `mrb_value` | Hash value |
|
||||
| `C` | Class | `mrb_value` | Class/Module value |
|
||||
| `c` | Class | `struct RClass*` | Class pointer |
|
||||
| `a` | Array | `const mrb_value*, mrb_int` | Array pointer + length |
|
||||
| `d` | C Data | `void*` | Requires `mrb_data_type*` |
|
||||
| `&` | Block | `mrb_value` | Block argument |
|
||||
| `*` | rest | `const mrb_value*, mrb_int` | Rest arguments |
|
||||
| `\|` | — | — | Following args are optional |
|
||||
| `?` | — | `mrb_bool` | Was previous optional arg given? |
|
||||
| `:` | keywords | `mrb_kwargs` | Keyword arguments |
|
||||
|
||||
Adding `!` to `S`, `A`, `H`, `C`, `c`, `s`, `z`, `a`, `d` allows `nil`
|
||||
(returns NULL/zero for nil).
|
||||
|
||||
### Examples
|
||||
|
||||
```c
|
||||
/* def method(name, count) */
|
||||
const char *name; mrb_int len, count;
|
||||
mrb_get_args(mrb, "si", &name, &len, &count);
|
||||
|
||||
/* def method(required, optional=nil) */
|
||||
mrb_value req, opt = mrb_nil_value();
|
||||
mrb_get_args(mrb, "o|o", &req, &opt);
|
||||
|
||||
/* def method(*args) */
|
||||
const mrb_value *args; mrb_int argc;
|
||||
mrb_get_args(mrb, "*", &args, &argc);
|
||||
|
||||
/* def method(&block) */
|
||||
mrb_value block;
|
||||
mrb_get_args(mrb, "&", &block);
|
||||
|
||||
/* def method(name:, age: 0) */
|
||||
mrb_sym kw_names[] = { mrb_intern_lit(mrb, "name"), mrb_intern_lit(mrb, "age") };
|
||||
mrb_value kw_values[2];
|
||||
mrb_kwargs kw = { 2, 1, kw_names, kw_values, NULL };
|
||||
mrb_get_args(mrb, ":", &kw);
|
||||
/* kw_values[0] = name (required), kw_values[1] = age (optional, undef if not given) */
|
||||
```
|
||||
|
||||
## Calling Ruby Methods from C
|
||||
|
||||
```c
|
||||
/* Call obj.method(arg1, arg2) */
|
||||
mrb_funcall(mrb, obj, "method", 2, arg1, arg2);
|
||||
|
||||
/* Call with symbol (faster, no string lookup) */
|
||||
mrb_funcall_id(mrb, obj, mrb_intern_lit(mrb, "method"), 2, arg1, arg2);
|
||||
|
||||
/* Call with argv array */
|
||||
mrb_value argv[] = { arg1, arg2 };
|
||||
mrb_funcall_argv(mrb, obj, mrb_intern_lit(mrb, "method"), 2, argv);
|
||||
|
||||
/* Call with block */
|
||||
mrb_funcall_with_block(mrb, obj, mid, argc, argv, block);
|
||||
|
||||
/* Yield to block */
|
||||
mrb_yield(mrb, block, arg);
|
||||
mrb_yield_argv(mrb, block, argc, argv);
|
||||
```
|
||||
|
||||
## String Operations
|
||||
|
||||
```c
|
||||
/* Creation */
|
||||
mrb_str_new_lit(mrb, "hello") /* from string literal */
|
||||
mrb_str_new(mrb, ptr, len) /* from pointer + length */
|
||||
mrb_str_new_cstr(mrb, cstr) /* from null-terminated C string */
|
||||
mrb_str_new_static(mrb, ptr, len) /* from static data (no copy) */
|
||||
|
||||
/* Access */
|
||||
RSTRING_PTR(str) /* char* pointer */
|
||||
RSTRING_LEN(str) /* length */
|
||||
mrb_str_to_cstr(mrb, str) /* null-terminated (may copy) */
|
||||
|
||||
/* Modification */
|
||||
mrb_str_cat(mrb, str, ptr, len) /* append bytes */
|
||||
mrb_str_cat_cstr(mrb, str, cstr) /* append C string */
|
||||
mrb_str_cat_str(mrb, str, str2) /* append String */
|
||||
|
||||
/* Comparison */
|
||||
mrb_str_equal(mrb, str1, str2) /* equality */
|
||||
mrb_str_cmp(mrb, str1, str2) /* comparison (-1, 0, 1) */
|
||||
```
|
||||
|
||||
## Array Operations
|
||||
|
||||
```c
|
||||
/* Creation */
|
||||
mrb_ary_new(mrb) /* empty array */
|
||||
mrb_ary_new_capa(mrb, capa) /* preallocated */
|
||||
mrb_ary_new_from_values(mrb, n, vals) /* from C array */
|
||||
|
||||
/* Access */
|
||||
RARRAY_PTR(ary) /* mrb_value* pointer */
|
||||
RARRAY_LEN(ary) /* length */
|
||||
mrb_ary_entry(ary, idx) /* get element (no mrb needed) */
|
||||
|
||||
/* Modification */
|
||||
mrb_ary_push(mrb, ary, val) /* append */
|
||||
mrb_ary_pop(mrb, ary) /* remove last */
|
||||
mrb_ary_shift(mrb, ary) /* remove first */
|
||||
mrb_ary_unshift(mrb, ary, val) /* prepend */
|
||||
mrb_ary_set(mrb, ary, idx, val) /* set element */
|
||||
mrb_ary_splice(mrb, ary, pos, len, rpl) /* splice */
|
||||
mrb_ary_concat(mrb, ary, other) /* extend */
|
||||
```
|
||||
|
||||
## Hash Operations
|
||||
|
||||
```c
|
||||
/* Creation */
|
||||
mrb_hash_new(mrb) /* empty hash */
|
||||
|
||||
/* Access */
|
||||
mrb_hash_get(mrb, hash, key) /* get value */
|
||||
mrb_hash_fetch(mrb, hash, key, def) /* get with default */
|
||||
mrb_hash_key_p(mrb, hash, key) /* key exists? */
|
||||
mrb_hash_empty_p(mrb, hash) /* empty? */
|
||||
mrb_hash_size(mrb, hash) /* number of entries */
|
||||
|
||||
/* Modification */
|
||||
mrb_hash_set(mrb, hash, key, val) /* set key-value */
|
||||
mrb_hash_delete_key(mrb, hash, key) /* delete key */
|
||||
mrb_hash_merge(mrb, hash1, hash2) /* merge hash2 into hash1 */
|
||||
|
||||
/* Iteration */
|
||||
mrb_hash_keys(mrb, hash) /* Array of keys */
|
||||
mrb_hash_values(mrb, hash) /* Array of values */
|
||||
```
|
||||
|
||||
## Wrapping C Structures
|
||||
|
||||
To expose a C struct to Ruby, use `mrb_data_type` and `Data_Wrap_Struct`:
|
||||
|
||||
```c
|
||||
/* 1. Define the data type with a name and destructor */
|
||||
static void point_free(mrb_state *mrb, void *p) {
|
||||
mrb_free(mrb, p);
|
||||
}
|
||||
|
||||
static const mrb_data_type point_type = {
|
||||
"Point", point_free
|
||||
};
|
||||
|
||||
/* 2. Allocate and initialize */
|
||||
static mrb_value
|
||||
point_init(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
mrb_float x, y;
|
||||
mrb_get_args(mrb, "ff", &x, &y);
|
||||
|
||||
double *data = (double*)mrb_malloc(mrb, sizeof(double) * 2);
|
||||
data[0] = x;
|
||||
data[1] = y;
|
||||
|
||||
DATA_PTR(self) = data;
|
||||
DATA_TYPE(self) = &point_type;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/* 3. Access the wrapped data */
|
||||
static mrb_value
|
||||
point_x(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
double *data = (double*)mrb_data_get_ptr(mrb, self, &point_type);
|
||||
return mrb_float_value(mrb, data[0]);
|
||||
}
|
||||
|
||||
/* 4. Register the class */
|
||||
struct RClass *point = mrb_define_class(mrb, "Point", mrb->object_class);
|
||||
MRB_SET_INSTANCE_TT(point, MRB_TT_CDATA);
|
||||
mrb_define_method(mrb, point, "initialize", point_init, MRB_ARGS_REQ(2));
|
||||
mrb_define_method(mrb, point, "x", point_x, MRB_ARGS_NONE());
|
||||
```
|
||||
|
||||
## Exception Handling
|
||||
|
||||
### Raising Exceptions
|
||||
|
||||
```c
|
||||
mrb_raise(mrb, E_RUNTIME_ERROR, "something went wrong");
|
||||
mrb_raisef(mrb, E_ARGUMENT_ERROR, "expected %d, got %d", expected, actual);
|
||||
mrb_raise(mrb, E_TYPE_ERROR, "wrong type");
|
||||
```
|
||||
|
||||
Common exception classes: `E_RUNTIME_ERROR`, `E_TYPE_ERROR`,
|
||||
`E_ARGUMENT_ERROR`, `E_RANGE_ERROR`, `E_NAME_ERROR`,
|
||||
`E_NOMETHOD_ERROR`, `E_NOTIMP_ERROR`, `E_KEY_ERROR`.
|
||||
|
||||
### Catching Exceptions
|
||||
|
||||
```c
|
||||
/* Check after mrb_load_string or mrb_funcall */
|
||||
mrb_value result = mrb_load_string(mrb, code);
|
||||
if (mrb->exc) {
|
||||
mrb_print_error(mrb);
|
||||
mrb->exc = NULL; /* clear exception */
|
||||
}
|
||||
```
|
||||
|
||||
### Protected Execution
|
||||
|
||||
`mrb_protect()` executes a function under protection. If an
|
||||
exception is raised, it is captured as a return value instead of
|
||||
propagating:
|
||||
|
||||
```c
|
||||
static mrb_value
|
||||
safe_operation(mrb_state *mrb, mrb_value data)
|
||||
{
|
||||
/* This function might raise an exception */
|
||||
return mrb_funcall(mrb, data, "do_something", 0);
|
||||
}
|
||||
|
||||
mrb_bool error;
|
||||
mrb_value result = mrb_protect(mrb, safe_operation, data, &error);
|
||||
if (error) {
|
||||
/* result contains the exception object; mrb->exc is cleared */
|
||||
mrb_value inspect = mrb_inspect(mrb, result);
|
||||
fprintf(stderr, "Error: %s\n", mrb_str_to_cstr(mrb, inspect));
|
||||
}
|
||||
```
|
||||
|
||||
**Note:** `mrb_protect` clears `mrb->exc` after catching the
|
||||
exception. The exception is returned as `result`. Do not use
|
||||
`mrb_print_error()` after `mrb_protect` — it reads `mrb->exc`
|
||||
which is already `NULL`.
|
||||
|
||||
For lower-level protection with a `void*` callback:
|
||||
|
||||
```c
|
||||
static mrb_value
|
||||
body(mrb_state *mrb, void *userdata)
|
||||
{
|
||||
/* ... */
|
||||
}
|
||||
|
||||
mrb_bool error;
|
||||
mrb_value result = mrb_protect_error(mrb, body, userdata, &error);
|
||||
```
|
||||
|
||||
### Rescue
|
||||
|
||||
`mrb_rescue()` catches `StandardError` (like Ruby's `rescue`):
|
||||
|
||||
```c
|
||||
static mrb_value
|
||||
body_func(mrb_state *mrb, mrb_value body_data)
|
||||
{
|
||||
return mrb_funcall(mrb, body_data, "risky_method", 0);
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
rescue_func(mrb_state *mrb, mrb_value rescue_data)
|
||||
{
|
||||
/* handle error, rescue_data is the data passed in */
|
||||
return mrb_nil_value();
|
||||
}
|
||||
|
||||
mrb_value result = mrb_rescue(mrb, body_func, body_data,
|
||||
rescue_func, rescue_data);
|
||||
```
|
||||
|
||||
To rescue specific exception classes:
|
||||
|
||||
```c
|
||||
struct RClass *classes[] = {
|
||||
E_ARGUMENT_ERROR,
|
||||
mrb_class_get(mrb, "IOError")
|
||||
};
|
||||
mrb_value result = mrb_rescue_exceptions(mrb, body_func, body_data,
|
||||
rescue_func, rescue_data,
|
||||
2, classes);
|
||||
```
|
||||
|
||||
### Ensure
|
||||
|
||||
`mrb_ensure()` guarantees cleanup runs regardless of exceptions
|
||||
(like Ruby's `ensure`):
|
||||
|
||||
```c
|
||||
static mrb_value
|
||||
body_func(mrb_state *mrb, mrb_value data)
|
||||
{
|
||||
return mrb_funcall(mrb, data, "process", 0);
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
cleanup_func(mrb_state *mrb, mrb_value data)
|
||||
{
|
||||
mrb_funcall(mrb, data, "close", 0);
|
||||
return mrb_nil_value();
|
||||
}
|
||||
|
||||
mrb_value result = mrb_ensure(mrb, body_func, body_data,
|
||||
cleanup_func, cleanup_data);
|
||||
```
|
||||
|
||||
The ensure function always executes. If the body raises an
|
||||
exception, the ensure runs and then the exception is re-raised.
|
||||
|
||||
### Error State Management
|
||||
|
||||
```c
|
||||
mrb_bool mrb_check_error(mrb_state *mrb); /* check and clear mrb->exc */
|
||||
void mrb_clear_error(mrb_state *mrb); /* clear mrb->exc */
|
||||
```
|
||||
|
||||
## Method Visibility
|
||||
|
||||
```c
|
||||
/* Public (default) */
|
||||
mrb_define_method(mrb, klass, "name", func, MRB_ARGS_NONE());
|
||||
|
||||
/* Private - only callable without explicit receiver */
|
||||
mrb_define_private_method(mrb, klass, "name", func, MRB_ARGS_NONE());
|
||||
|
||||
/* Class method (singleton method on the class object) */
|
||||
mrb_define_class_method(mrb, klass, "name", func, MRB_ARGS_NONE());
|
||||
|
||||
/* Module function (both module method and private instance method) */
|
||||
mrb_define_module_function(mrb, mod, "name", func, MRB_ARGS_NONE());
|
||||
|
||||
/* Singleton method on a specific object */
|
||||
mrb_define_singleton_method(mrb, obj, "name", func, MRB_ARGS_NONE());
|
||||
|
||||
/* Method alias: alias new_name old_name */
|
||||
mrb_define_alias(mrb, klass, "new_name", "old_name");
|
||||
|
||||
/* Remove a method */
|
||||
mrb_undef_method(mrb, klass, "name");
|
||||
mrb_undef_class_method(mrb, klass, "name");
|
||||
```
|
||||
|
||||
All `_method` variants have `_id` counterparts that accept
|
||||
`mrb_sym` instead of `const char*` for better performance.
|
||||
|
||||
## Proc and Block Handling
|
||||
|
||||
### Creating Procs from C Functions
|
||||
|
||||
```c
|
||||
/* Simple C function proc */
|
||||
struct RProc *proc = mrb_proc_new_cfunc(mrb, my_func);
|
||||
|
||||
/* C closure with captured local variables */
|
||||
struct RProc *proc = mrb_closure_new_cfunc(mrb, my_func, nlocals);
|
||||
```
|
||||
|
||||
### C Functions with Environment (requires mruby-proc-ext)
|
||||
|
||||
Store values in a proc's environment, accessible from the C
|
||||
function:
|
||||
|
||||
```c
|
||||
mrb_value env_values[] = { mrb_fixnum_value(42) };
|
||||
struct RProc *proc = mrb_proc_new_cfunc_with_env(mrb, my_func, 1, env_values);
|
||||
|
||||
/* Inside my_func, retrieve environment values */
|
||||
static mrb_value my_func(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
mrb_value val = mrb_proc_cfunc_env_get(mrb, 0); /* index 0 */
|
||||
return val;
|
||||
}
|
||||
```
|
||||
|
||||
## Fiber API
|
||||
|
||||
```c
|
||||
#include <mruby.h> /* fiber types and functions */
|
||||
```
|
||||
|
||||
### Creating and Using Fibers
|
||||
|
||||
```c
|
||||
/* Create a fiber from a proc */
|
||||
mrb_value fiber = mrb_fiber_new(mrb, proc);
|
||||
|
||||
/* Resume the fiber with arguments */
|
||||
mrb_value args[] = { mrb_fixnum_value(1) };
|
||||
mrb_value result = mrb_fiber_resume(mrb, fiber, 1, args);
|
||||
|
||||
/* Check if fiber is still alive */
|
||||
mrb_bool alive = mrb_test(mrb_fiber_alive_p(mrb, fiber));
|
||||
```
|
||||
|
||||
### Yielding from C
|
||||
|
||||
`mrb_fiber_yield()` can only be used as the return value of a C
|
||||
function — no code may execute after it:
|
||||
|
||||
```c
|
||||
static mrb_value
|
||||
my_yield_method(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
mrb_value yield_args[] = { mrb_str_new_lit(mrb, "yielded") };
|
||||
return mrb_fiber_yield(mrb, 1, yield_args); /* must be returned directly */
|
||||
}
|
||||
```
|
||||
|
||||
### Fiber States
|
||||
|
||||
| State | Meaning |
|
||||
| ----- | ------- |
|
||||
| `MRB_FIBER_CREATED` | Created but not yet resumed |
|
||||
| `MRB_FIBER_RUNNING` | Currently executing |
|
||||
| `MRB_FIBER_RESUMED` | Resumed another fiber |
|
||||
| `MRB_FIBER_SUSPENDED` | Yielded, waiting to resume |
|
||||
| `MRB_FIBER_TRANSFERRED` | Transferred via `Fiber#transfer` |
|
||||
| `MRB_FIBER_TERMINATED` | Finished execution |
|
||||
|
||||
**Limitation:** fibers cannot yield across C function boundaries.
|
||||
You cannot call `mrb_fiber_yield` from within a C-implemented
|
||||
method, except via `mrb_fiber_yield` at function return.
|
||||
|
||||
## Compilation Contexts
|
||||
|
||||
For advanced compilation control, use `mrb_ccontext`:
|
||||
|
||||
```c
|
||||
#include <mruby/compile.h>
|
||||
|
||||
mrb_ccontext *cxt = mrb_ccontext_new(mrb);
|
||||
|
||||
/* Set source filename for error messages and debug info */
|
||||
mrb_ccontext_filename(mrb, cxt, "my_script.rb");
|
||||
|
||||
/* Compile and execute with context */
|
||||
mrb_value result = mrb_load_string_cxt(mrb, "1 + 2", cxt);
|
||||
|
||||
/* Clean up */
|
||||
mrb_ccontext_free(mrb, cxt);
|
||||
```
|
||||
|
||||
### Context Options
|
||||
|
||||
The `mrb_ccontext` structure provides several flags:
|
||||
|
||||
| Field | Purpose |
|
||||
| ----- | ------- |
|
||||
| `capture_errors` | Collect parse errors instead of raising |
|
||||
| `no_exec` | Compile without executing (get RProc) |
|
||||
| `no_optimize` | Disable peephole optimizations |
|
||||
| `no_ext_ops` | Disable extended operand instructions |
|
||||
| `keep_lv` | Preserve local variables across loads |
|
||||
|
||||
### Loading with Context
|
||||
|
||||
```c
|
||||
mrb_load_string_cxt(mrb, code, cxt); /* string + context */
|
||||
mrb_load_nstring_cxt(mrb, code, len, cxt); /* with explicit length */
|
||||
mrb_load_file_cxt(mrb, fp, cxt); /* file + context */
|
||||
mrb_load_detect_file_cxt(mrb, fp, cxt); /* auto-detect .mrb or .rb */
|
||||
```
|
||||
|
||||
## Precompiled Bytecode
|
||||
|
||||
Load `.mrb` files compiled by `mrbc`:
|
||||
|
||||
```c
|
||||
#include <mruby/irep.h>
|
||||
|
||||
/* From byte array (generated by mrbc -B) */
|
||||
mrb_value result = mrb_load_irep(mrb, bytecode);
|
||||
|
||||
/* From buffer with explicit size (safer, bounds-checked) */
|
||||
mrb_value result = mrb_load_irep_buf(mrb, buf, size);
|
||||
|
||||
/* From file */
|
||||
FILE *fp = fopen("script.mrb", "rb");
|
||||
mrb_value result = mrb_load_irep_file(mrb, fp);
|
||||
fclose(fp);
|
||||
|
||||
/* Load without executing (returns irep for inspection) */
|
||||
mrb_irep *irep = mrb_read_irep(mrb, bytecode);
|
||||
```
|
||||
|
||||
All `_irep` loading functions have `_cxt` variants that accept
|
||||
a compilation context.
|
||||
|
||||
### Deployment Pattern
|
||||
|
||||
Ahead-of-time compilation eliminates the need for the compiler gem
|
||||
at runtime:
|
||||
|
||||
```shell
|
||||
# Compile to C array
|
||||
mrbc -Bscript_bytecode script.rb
|
||||
|
||||
# This generates a C header with:
|
||||
# const uint8_t script_bytecode[];
|
||||
```
|
||||
|
||||
```c
|
||||
#include "script.mrb.h"
|
||||
|
||||
mrb_state *mrb = mrb_open_core(); /* no compiler needed */
|
||||
mrb_load_irep(mrb, script_bytecode);
|
||||
```
|
||||
|
||||
**Important:** wrap bytecode loading in arena save/restore when
|
||||
loading multiple scripts:
|
||||
|
||||
```c
|
||||
int ai = mrb_gc_arena_save(mrb);
|
||||
mrb_load_irep(mrb, script1);
|
||||
mrb_gc_arena_restore(mrb, ai);
|
||||
|
||||
ai = mrb_gc_arena_save(mrb);
|
||||
mrb_load_irep(mrb, script2);
|
||||
mrb_gc_arena_restore(mrb, ai);
|
||||
```
|
||||
|
||||
## Symbols
|
||||
|
||||
```c
|
||||
/* Create symbol from string */
|
||||
mrb_sym sym = mrb_intern_lit(mrb, "name"); /* from literal */
|
||||
mrb_sym sym = mrb_intern_cstr(mrb, cstr); /* from C string */
|
||||
mrb_sym sym = mrb_intern(mrb, ptr, len); /* from pointer + length */
|
||||
|
||||
/* Symbol to string */
|
||||
const char *name = mrb_sym_name(mrb, sym);
|
||||
mrb_int len;
|
||||
const char *name = mrb_sym_name_len(mrb, sym, &len);
|
||||
```
|
||||
|
||||
## Instance Variables
|
||||
|
||||
```c
|
||||
/* Get/set instance variables on an object */
|
||||
mrb_iv_get(mrb, obj, mrb_intern_lit(mrb, "@x"));
|
||||
mrb_iv_set(mrb, obj, mrb_intern_lit(mrb, "@x"), val);
|
||||
mrb_iv_defined(mrb, obj, mrb_intern_lit(mrb, "@x"));
|
||||
mrb_iv_remove(mrb, obj, mrb_intern_lit(mrb, "@x"));
|
||||
```
|
||||
|
||||
## Global Variables
|
||||
|
||||
```c
|
||||
mrb_gv_get(mrb, mrb_intern_lit(mrb, "$verbose"));
|
||||
mrb_gv_set(mrb, mrb_intern_lit(mrb, "$verbose"), mrb_true_value());
|
||||
```
|
||||
|
||||
## Class Variables
|
||||
|
||||
```c
|
||||
mrb_cv_get(mrb, klass, mrb_intern_lit(mrb, "@@count"));
|
||||
mrb_cv_set(mrb, klass, mrb_intern_lit(mrb, "@@count"), mrb_fixnum_value(0));
|
||||
```
|
||||
|
||||
## Loading and Executing Code
|
||||
|
||||
```c
|
||||
/* Load and execute a string (requires mruby-compiler gem) */
|
||||
mrb_value result = mrb_load_string(mrb, "1 + 2");
|
||||
|
||||
/* Load and execute a file */
|
||||
FILE *f = fopen("script.rb", "r");
|
||||
mrb_value result = mrb_load_file(mrb, f);
|
||||
fclose(f);
|
||||
|
||||
/* Load precompiled bytecode (no compiler needed) */
|
||||
mrb_value result = mrb_load_irep(mrb, bytecode_array);
|
||||
```
|
||||
|
||||
## GC Arena
|
||||
|
||||
When creating many temporary Ruby objects in C, use the GC arena to
|
||||
prevent them from being collected prematurely:
|
||||
|
||||
```c
|
||||
int ai = mrb_gc_arena_save(mrb);
|
||||
/* create temporary objects here */
|
||||
mrb_gc_arena_restore(mrb, ai);
|
||||
```
|
||||
|
||||
See [gc-arena-howto.md](gc-arena-howto.md) for details.
|
||||
|
||||
## Memory Allocation
|
||||
|
||||
```c
|
||||
void *p = mrb_malloc(mrb, size); /* raises on failure */
|
||||
void *p = mrb_calloc(mrb, nmemb, size); /* zero-initialized */
|
||||
void *p = mrb_realloc(mrb, ptr, size); /* resize */
|
||||
mrb_free(mrb, p); /* free */
|
||||
|
||||
/* NULL-returning variants (for custom error handling) */
|
||||
void *p = mrb_malloc_simple(mrb, size);
|
||||
void *p = mrb_realloc_simple(mrb, ptr, size);
|
||||
```
|
||||
|
||||
## Type Conversion
|
||||
|
||||
```c
|
||||
mrb_obj_as_string(mrb, val) /* to_s */
|
||||
mrb_inspect(mrb, val) /* inspect */
|
||||
mrb_any_to_s(mrb, val) /* default to_s */
|
||||
mrb_str_to_integer(mrb, str, base, badcheck) /* String to Integer */
|
||||
mrb_str_to_dbl(mrb, str, badcheck) /* String to Float */
|
||||
mrb_ensure_float_type(mrb, val) /* ensure Float */
|
||||
```
|
||||
|
||||
## Object Comparison
|
||||
|
||||
```c
|
||||
mrb_equal(mrb, a, b) /* Ruby == */
|
||||
mrb_eql(mrb, a, b) /* Ruby eql? */
|
||||
mrb_obj_eq(mrb, a, b) /* Ruby equal? (identity) */
|
||||
mrb_cmp(mrb, a, b) /* Ruby <=> (returns mrb_int) */
|
||||
```
|
||||
|
||||
## Object Inspection
|
||||
|
||||
```c
|
||||
mrb_obj_classname(mrb, obj) /* class name as C string */
|
||||
mrb_obj_class(mrb, obj) /* class as RClass* */
|
||||
mrb_obj_is_kind_of(mrb, obj, klass) /* is_a? / kind_of? */
|
||||
mrb_obj_respond_to(mrb, klass, mid) /* respond_to? */
|
||||
mrb_obj_id(obj) /* object_id */
|
||||
mrb_obj_freeze(mrb, obj) /* freeze */
|
||||
mrb_obj_dup(mrb, obj) /* dup */
|
||||
```
|
||||
|
||||
## Compile-Time Flags
|
||||
|
||||
When compiling C code that uses mruby, you must use the same flags as
|
||||
the library was built with. Use `mruby-config` to get them:
|
||||
|
||||
```console
|
||||
$ build/host/bin/mruby-config --cflags # compiler flags
|
||||
$ build/host/bin/mruby-config --ldflags # linker flags
|
||||
$ build/host/bin/mruby-config --libs # libraries
|
||||
```
|
||||
|
||||
Key macros that affect ABI:
|
||||
|
||||
| Macro | Effect |
|
||||
| ----- | ------ |
|
||||
| `MRB_NO_BOXING` | Struct-based values (larger, debuggable) |
|
||||
| `MRB_WORD_BOXING` | Single-word values (fast, 32-bit safe) |
|
||||
| `MRB_NAN_BOXING` | NaN-tagged values (default on 32-bit) |
|
||||
| `MRB_NO_FLOAT` | Disable Float support |
|
||||
| `MRB_INT64` | 64-bit integers |
|
||||
| `MRB_USE_FLOAT32` | 32-bit floats |
|
||||
|
||||
Mismatching these between library and application causes silent
|
||||
data corruption.
|
||||
@@ -261,9 +261,12 @@ end
|
||||
|
||||
### Preallocated Symbols
|
||||
|
||||
Preallocated symbols are always enabled. Symbol IDs used in C source code
|
||||
(via `MRB_SYM()` etc.) are resolved to compile-time constants during the
|
||||
build process.
|
||||
By far, preallocated symbols are highly compatible with the previous versions, so
|
||||
we expect you won't see any problem with them. But just in case you face any
|
||||
issue, you can disable preallocated symbols by specifying `conf.disable_presym`.
|
||||
|
||||
In the build process, `mrbc` under cross compiling environment will be compiled
|
||||
with this configuration.
|
||||
|
||||
### Mrbgems
|
||||
|
||||
|
||||
@@ -96,31 +96,54 @@ In mruby, C function calls are surrounded by this save/restore, but we
|
||||
can further optimize memory usage by surrounding save/restore, and can
|
||||
avoid creating arena overflow bugs.
|
||||
|
||||
Let's take a real example. Here is the source code of `Array#inspect`
|
||||
(from `src/array.c`):
|
||||
Let's take a real example. Here is the source code of `Array#inspect`:
|
||||
|
||||
```c
|
||||
static mrb_value
|
||||
mrb_ary_to_s(mrb_state *mrb, mrb_value self)
|
||||
inspect_ary(mrb_state *mrb, mrb_value ary, mrb_value list)
|
||||
{
|
||||
mrb->c->ci->mid = MRB_SYM(inspect);
|
||||
mrb_value ret = mrb_str_new_lit(mrb, "[");
|
||||
int ai = mrb_gc_arena_save(mrb);
|
||||
if (MRB_RECURSIVE_UNARY_P(mrb, MRB_SYM(inspect), self)) {
|
||||
mrb_str_cat_lit(mrb, ret, "...]");
|
||||
return ret;
|
||||
mrb_int i;
|
||||
mrb_value s, arystr;
|
||||
char head[] = { '[' };
|
||||
char sep[] = { ',', ' ' };
|
||||
char tail[] = { ']' };
|
||||
|
||||
/* check recursive */
|
||||
for (i=0; i<RARRAY_LEN(list); i++) {
|
||||
if (mrb_obj_equal(mrb, ary, RARRAY_PTR(list)[i])) {
|
||||
return mrb_str_new(mrb, "[...]", 5);
|
||||
}
|
||||
}
|
||||
for (mrb_int i=0; i<RARRAY_LEN(self); i++) {
|
||||
if (i>0) mrb_str_cat_lit(mrb, ret, ", ");
|
||||
mrb_str_cat_str(mrb, ret, mrb_inspect(mrb, RARRAY_PTR(self)[i]));
|
||||
|
||||
mrb_ary_push(mrb, list, ary);
|
||||
|
||||
arystr = mrb_str_new_capa(mrb, 64);
|
||||
mrb_str_cat(mrb, arystr, head, sizeof(head));
|
||||
|
||||
for (i=0; i<RARRAY_LEN(ary); i++) {
|
||||
int ai = mrb_gc_arena_save(mrb);
|
||||
|
||||
if (i > 0) {
|
||||
mrb_str_cat(mrb, arystr, sep, sizeof(sep));
|
||||
}
|
||||
if (mrb_array_p(RARRAY_PTR(ary)[i])) {
|
||||
s = inspect_ary(mrb, RARRAY_PTR(ary)[i], list);
|
||||
}
|
||||
else {
|
||||
s = mrb_inspect(mrb, RARRAY_PTR(ary)[i]);
|
||||
}
|
||||
mrb_str_cat(mrb, arystr, RSTRING_PTR(s), RSTRING_LEN(s));
|
||||
mrb_gc_arena_restore(mrb, ai);
|
||||
}
|
||||
mrb_str_cat_lit(mrb, ret, "]");
|
||||
|
||||
return ret;
|
||||
mrb_str_cat(mrb, arystr, tail, sizeof(tail));
|
||||
mrb_ary_pop(mrb, list);
|
||||
|
||||
return arystr;
|
||||
}
|
||||
```
|
||||
|
||||
This is a real example, so slightly complicated, but bear with me.
|
||||
The essence of `Array#inspect` is that after stringifying each element
|
||||
of array using `inspect` method, we join them together so that we can
|
||||
get `inspect` representation of the entire array.
|
||||
@@ -129,7 +152,7 @@ After the `inspect` representation is created, we no longer require the
|
||||
individual string representation. This means that we don't have to register
|
||||
these temporal objects into GC arena.
|
||||
|
||||
Therefore, in order to keep the arena size small; the function
|
||||
Therefore, in order to keep the arena size small; the `ary_inspect()` function
|
||||
will do the following:
|
||||
|
||||
- save the position of the stack top using `mrb_gc_arena_save()`.
|
||||
@@ -143,7 +166,7 @@ required temporal object may be deleted by GC.
|
||||
|
||||
We may have an usecase where after creating many temporal objects, we'd
|
||||
like to keep some of them. In this case, we cannot use the same idea
|
||||
in `mrb_ary_to_s()` like appending objects to existing one.
|
||||
in `ary_inspect()` like appending objects to existing one.
|
||||
Instead, after `mrb_gc_arena_restore()`, we must re-register the objects we
|
||||
want to keep in the arena using `mrb_gc_protect(mrb, obj)`.
|
||||
Use `mrb_gc_protect()` with caution because it could also lead to an "arena
|
||||
|
||||
@@ -1,283 +0,0 @@
|
||||
<!-- summary: Getting Started with mruby -->
|
||||
|
||||
# Getting Started with mruby
|
||||
|
||||
This guide walks you through building mruby, running your first Ruby program,
|
||||
and embedding mruby in a C application.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
You need:
|
||||
|
||||
- C compiler (`gcc` or `clang`)
|
||||
- Ruby 2.5 or later (for the build system)
|
||||
- `rake` (bundled with Ruby)
|
||||
- `git` (optional, for cloning the source)
|
||||
|
||||
## Building mruby
|
||||
|
||||
Clone the repository and build:
|
||||
|
||||
```console
|
||||
$ git clone https://github.com/mruby/mruby.git
|
||||
$ cd mruby
|
||||
$ rake
|
||||
```
|
||||
|
||||
This compiles the default configuration and produces:
|
||||
|
||||
- `bin/mruby` — Ruby script interpreter
|
||||
- `bin/mirb` — interactive Ruby shell
|
||||
- `bin/mrbc` — bytecode compiler
|
||||
- `build/host/lib/libmruby.a` — library for embedding
|
||||
|
||||
## Running Ruby Code
|
||||
|
||||
### Interactive shell
|
||||
|
||||
```console
|
||||
$ bin/mirb
|
||||
mirb - Pair interactive mruby
|
||||
> puts "Hello, mruby!"
|
||||
Hello, mruby!
|
||||
=> nil
|
||||
> 1 + 2
|
||||
=> 3
|
||||
```
|
||||
|
||||
### Running a script file
|
||||
|
||||
Create `hello.rb`:
|
||||
|
||||
```ruby
|
||||
puts "Hello from mruby!"
|
||||
```
|
||||
|
||||
Run it:
|
||||
|
||||
```console
|
||||
$ bin/mruby hello.rb
|
||||
Hello from mruby!
|
||||
```
|
||||
|
||||
### One-liner
|
||||
|
||||
```console
|
||||
$ bin/mruby -e 'puts "Hello!"'
|
||||
Hello!
|
||||
```
|
||||
|
||||
## Compiling to Bytecode
|
||||
|
||||
mruby can compile Ruby scripts to bytecode (`.mrb` files) for faster
|
||||
loading and deployment without source code:
|
||||
|
||||
```console
|
||||
$ bin/mrbc hello.rb # produces hello.mrb
|
||||
$ bin/mruby -b hello.mrb # run bytecode
|
||||
Hello from mruby!
|
||||
```
|
||||
|
||||
You can also generate C source from Ruby scripts:
|
||||
|
||||
```console
|
||||
$ bin/mrbc -Bhello_code hello.rb # produces hello.c with byte array
|
||||
```
|
||||
|
||||
This generates a C file with a `const uint8_t hello_code[]` array that
|
||||
can be loaded with `mrb_load_irep()` in your C application.
|
||||
|
||||
## Embedding mruby in C
|
||||
|
||||
The primary use case of mruby is embedding in C/C++ applications.
|
||||
|
||||
### Minimal example
|
||||
|
||||
Create `embed.c`:
|
||||
|
||||
```c
|
||||
#include <mruby.h>
|
||||
#include <mruby/compile.h>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
mrb_state *mrb = mrb_open();
|
||||
if (!mrb) return 1;
|
||||
|
||||
mrb_load_string(mrb, "puts 'Hello from embedded mruby!'");
|
||||
if (mrb->exc) {
|
||||
mrb_print_error(mrb);
|
||||
}
|
||||
|
||||
mrb_close(mrb);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### Compile and link
|
||||
|
||||
Use `mruby-config` to get the correct compiler and linker flags:
|
||||
|
||||
```console
|
||||
$ gcc -I include `build/host/bin/mruby-config --cflags` embed.c \
|
||||
`build/host/bin/mruby-config --ldflags --libs` -o embed
|
||||
$ ./embed
|
||||
Hello from embedded mruby!
|
||||
```
|
||||
|
||||
**Important**: Always use `mruby-config --cflags` when compiling code
|
||||
that uses mruby. The build configuration may define macros (such as
|
||||
`MRB_NO_BOXING` or `MRB_USE_BIGINT`) that change the internal data
|
||||
layout. Compiling without these flags causes silent data corruption.
|
||||
|
||||
### Calling Ruby from C
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
#include <mruby.h>
|
||||
#include <mruby/compile.h>
|
||||
#include <mruby/string.h>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
mrb_state *mrb = mrb_open();
|
||||
|
||||
/* Define a Ruby method */
|
||||
mrb_load_string(mrb, "def greet(name) \"Hello, #{name}!\" end");
|
||||
|
||||
/* Call it from C */
|
||||
mrb_value result = mrb_funcall(mrb, mrb_top_self(mrb),
|
||||
"greet", 1, mrb_str_new_lit(mrb, "World"));
|
||||
printf("%s\n", mrb_str_to_cstr(mrb, result));
|
||||
|
||||
mrb_close(mrb);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### Defining C functions callable from Ruby
|
||||
|
||||
```c
|
||||
#include <mruby.h>
|
||||
#include <mruby/compile.h>
|
||||
|
||||
static mrb_value
|
||||
my_add(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
mrb_int a, b;
|
||||
mrb_get_args(mrb, "ii", &a, &b);
|
||||
return mrb_fixnum_value(a + b);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
mrb_state *mrb = mrb_open();
|
||||
|
||||
/* Define method on Kernel (available everywhere) */
|
||||
mrb_define_method(mrb, mrb->kernel_module, "my_add",
|
||||
my_add, MRB_ARGS_REQ(2));
|
||||
|
||||
mrb_load_string(mrb, "puts my_add(3, 4)"); /* prints 7 */
|
||||
|
||||
mrb_close(mrb);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## Loading Precompiled Bytecode
|
||||
|
||||
For deployment without the compiler gem, precompile your Ruby code:
|
||||
|
||||
```console
|
||||
$ bin/mrbc -Bruby_code app.rb
|
||||
```
|
||||
|
||||
Then load in C:
|
||||
|
||||
```c
|
||||
#include <mruby.h>
|
||||
#include <mruby/irep.h>
|
||||
#include "app.c" /* contains ruby_code[] */
|
||||
|
||||
int main(void)
|
||||
{
|
||||
mrb_state *mrb = mrb_open();
|
||||
mrb_load_irep(mrb, ruby_code);
|
||||
if (mrb->exc) {
|
||||
mrb_print_error(mrb);
|
||||
}
|
||||
mrb_close(mrb);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
This approach does not require the `mruby-compiler` gem, resulting in
|
||||
a smaller binary.
|
||||
|
||||
## Customizing the Build
|
||||
|
||||
mruby's functionality is controlled by the build configuration file.
|
||||
The default is `build_config/default.rb`.
|
||||
|
||||
### Using a custom configuration
|
||||
|
||||
```console
|
||||
$ MRUBY_CONFIG=build_config/minimal.rb rake
|
||||
```
|
||||
|
||||
### Selecting gems
|
||||
|
||||
Gems add features to mruby. A minimal configuration:
|
||||
|
||||
```ruby
|
||||
MRuby::Build.new do |conf|
|
||||
conf.toolchain :gcc
|
||||
|
||||
# Core language extensions
|
||||
conf.gem core: 'mruby-array-ext'
|
||||
conf.gem core: 'mruby-string-ext'
|
||||
conf.gem core: 'mruby-hash-ext'
|
||||
|
||||
# Tools
|
||||
conf.gem core: 'mruby-bin-mruby' # mruby command
|
||||
conf.gem core: 'mruby-bin-mirb' # interactive shell
|
||||
conf.gem core: 'mruby-bin-mrbc' # bytecode compiler
|
||||
|
||||
# Compiler (needed for mrb_load_string)
|
||||
conf.gem core: 'mruby-compiler'
|
||||
end
|
||||
```
|
||||
|
||||
### Using a gembox
|
||||
|
||||
Gemboxes are predefined collections of gems:
|
||||
|
||||
```ruby
|
||||
MRuby::Build.new do |conf|
|
||||
conf.toolchain :gcc
|
||||
conf.gembox 'default' # standard set of gems
|
||||
end
|
||||
```
|
||||
|
||||
## Amalgamation (Single-File Build)
|
||||
|
||||
For the simplest integration, use amalgamation to combine all mruby
|
||||
source into a single `mruby.c` and `mruby.h`:
|
||||
|
||||
```console
|
||||
$ rake amalgam
|
||||
$ gcc -I build/host/amalgam your_app.c build/host/amalgam/mruby.c -o your_app -lm
|
||||
```
|
||||
|
||||
See [amalgamation.md](amalgamation.md) for details.
|
||||
|
||||
## What's Next
|
||||
|
||||
- [Language Features](language.md) — Ruby subset supported by mruby
|
||||
- [C API Reference](capi.md) — values, classes, methods, error handling
|
||||
- [Compile](compile.md) — full build system reference
|
||||
- [mrbgems](mrbgems.md) — creating and using gems
|
||||
- [Linking](link.md) — linking `libmruby` to applications
|
||||
- [Build-time Configurations](mrbconf.md) — compile-time options
|
||||
- [GC Arena](gc-arena-howto.md) — managing GC arena in C extensions
|
||||
- [Limitations](../limitations.md) — differences from CRuby
|
||||
@@ -1,430 +0,0 @@
|
||||
<!-- summary: mruby Language Features and Ruby Compatibility -->
|
||||
|
||||
# mruby Language Features
|
||||
|
||||
This guide describes the Ruby language features supported by mruby 4.0.
|
||||
mruby implements a subset of the Ruby language, optimized for embedded use.
|
||||
For a list of specific behavioral differences, see
|
||||
[limitations.md](../limitations.md).
|
||||
|
||||
**If you are coming from CRuby**, note these major differences upfront:
|
||||
|
||||
- No `require` or `load` — all code is linked at build time
|
||||
- No `defined?` keyword — use `respond_to?`, `const_defined?`, etc.
|
||||
- No refinements (`refine`, `using`)
|
||||
- No `Encoding` class — UTF-8 opt-in via `MRB_UTF8_STRING`
|
||||
- Fibers cannot yield across C function boundaries
|
||||
- Integer size varies by platform and boxing mode
|
||||
- Operators cannot be overridden by user code
|
||||
|
||||
See [Key Differences from CRuby](#key-differences-from-cruby) for
|
||||
the full list.
|
||||
|
||||
## Syntax
|
||||
|
||||
### Keywords
|
||||
|
||||
mruby supports the following keywords:
|
||||
|
||||
`BEGIN`, `END`, `alias`, `and`, `begin`, `break`, `case`, `class`,
|
||||
`def`, `do`, `else`, `elsif`, `end`, `ensure`, `false`, `for`, `if`,
|
||||
`in`, `module`, `next`, `nil`, `not`, `or`, `redo`, `rescue`,
|
||||
`retry`, `return`, `self`, `super`, `then`, `true`, `undef`,
|
||||
`unless`, `until`, `when`, `while`, `yield`
|
||||
|
||||
Magic variables: `__FILE__`, `__LINE__`, `__ENCODING__`, `__method__`
|
||||
|
||||
**Not supported:** `defined?` (use `respond_to?`, `const_defined?`,
|
||||
etc. instead), `refinements` (`using`, `refine`).
|
||||
|
||||
### Classes and Modules
|
||||
|
||||
```ruby
|
||||
class Animal
|
||||
attr_accessor :name
|
||||
|
||||
def initialize(name)
|
||||
@name = name
|
||||
end
|
||||
|
||||
def speak
|
||||
"..."
|
||||
end
|
||||
end
|
||||
|
||||
class Dog < Animal
|
||||
def speak
|
||||
"Woof!"
|
||||
end
|
||||
end
|
||||
|
||||
module Greetable
|
||||
def greet
|
||||
"Hello, I'm #{name}"
|
||||
end
|
||||
end
|
||||
|
||||
class Dog
|
||||
include Greetable
|
||||
end
|
||||
```
|
||||
|
||||
All standard class and module features are supported: inheritance,
|
||||
`include`, `prepend`, `extend`, `attr_reader`/`attr_writer`/
|
||||
`attr_accessor`, `public`/`private`/`protected` visibility, class
|
||||
variables (`@@var`), class methods, and `super`.
|
||||
|
||||
### Methods
|
||||
|
||||
```ruby
|
||||
# Required, optional, rest, post-rest, keyword, and block arguments
|
||||
def example(a, b = 1, *rest, last, key:, opt_key: nil, **kwargs, &block)
|
||||
end
|
||||
|
||||
# Endless method definition
|
||||
def double(x) = x * 2
|
||||
```
|
||||
|
||||
### Blocks and Procs
|
||||
|
||||
```ruby
|
||||
[1, 2, 3].each { |n| puts n }
|
||||
|
||||
[1, 2, 3].each do |n|
|
||||
puts n
|
||||
end
|
||||
|
||||
square = Proc.new { |x| x * x }
|
||||
square = proc { |x| x * x }
|
||||
double = lambda { |x| x * 2 }
|
||||
double = ->(x) { x * 2 }
|
||||
```
|
||||
|
||||
### Control Flow
|
||||
|
||||
```ruby
|
||||
# if/unless (both statement and modifier forms)
|
||||
if condition
|
||||
# ...
|
||||
elsif other
|
||||
# ...
|
||||
else
|
||||
# ...
|
||||
end
|
||||
|
||||
result = value if condition
|
||||
result = value unless condition
|
||||
|
||||
# case/when
|
||||
case obj
|
||||
when String then "string"
|
||||
when Integer then "integer"
|
||||
else "other"
|
||||
end
|
||||
|
||||
# Loops
|
||||
while condition
|
||||
# ...
|
||||
end
|
||||
|
||||
until condition
|
||||
# ...
|
||||
end
|
||||
|
||||
for item in collection
|
||||
# ...
|
||||
end
|
||||
|
||||
# Loop control
|
||||
break # exit loop
|
||||
next # skip to next iteration
|
||||
redo # restart current iteration
|
||||
retry # restart begin/rescue block
|
||||
```
|
||||
|
||||
### Exception Handling
|
||||
|
||||
```ruby
|
||||
begin
|
||||
risky_operation
|
||||
rescue ArgumentError => e
|
||||
handle_arg_error(e)
|
||||
rescue StandardError => e
|
||||
handle_error(e)
|
||||
ensure
|
||||
cleanup
|
||||
end
|
||||
|
||||
raise "something went wrong"
|
||||
raise ArgumentError, "bad argument"
|
||||
```
|
||||
|
||||
**Note:** `raise` without arguments in a `rescue` clause does not
|
||||
re-raise the current exception. Capture and re-raise explicitly:
|
||||
|
||||
```ruby
|
||||
begin
|
||||
risky_operation
|
||||
rescue => e
|
||||
log(e)
|
||||
raise e # explicit re-raise required
|
||||
end
|
||||
```
|
||||
|
||||
### Strings
|
||||
|
||||
```ruby
|
||||
"double-quoted with #{interpolation}"
|
||||
'single-quoted literal'
|
||||
heredoc = <<~HEREDOC
|
||||
indented heredoc
|
||||
with #{interpolation}
|
||||
HEREDOC
|
||||
```
|
||||
|
||||
### Regular Expressions
|
||||
|
||||
Regular expressions require an external gem such as `mruby-regexp-pcre`
|
||||
or `mruby-onig-regexp`. Without a regexp gem, `Regexp` literals
|
||||
(`/pattern/`) are not available.
|
||||
|
||||
### Pattern Matching (Limited)
|
||||
|
||||
Only rightward assignment with simple variable binding is supported:
|
||||
|
||||
```ruby
|
||||
expr => var # assigns expr to var
|
||||
```
|
||||
|
||||
`case/in` syntax, array/hash patterns, guard clauses, pin operator,
|
||||
find patterns, and alternative patterns are **not** supported.
|
||||
|
||||
## Numeric Types
|
||||
|
||||
mruby's numeric type sizes depend on the boxing mode and platform.
|
||||
|
||||
### Integer
|
||||
|
||||
| Configuration | Range |
|
||||
| ------------- | ----- |
|
||||
| 64-bit word boxing (default on 64-bit) | roughly +/- 2^62 |
|
||||
| 32-bit word boxing (default on 32-bit) | roughly +/- 2^30 |
|
||||
| NaN boxing (64-bit only) | -2^31 to 2^31-1 |
|
||||
|
||||
Integer overflow raises a `RangeError` unless the `mruby-bigint` gem
|
||||
is included, in which case integers automatically promote to
|
||||
arbitrary precision.
|
||||
|
||||
### Float
|
||||
|
||||
By default, `Float` uses 64-bit `double`. Compile-time options:
|
||||
|
||||
- `MRB_USE_FLOAT32`: use 32-bit `float` instead
|
||||
- `MRB_NO_FLOAT`: disable floating-point entirely
|
||||
|
||||
With word boxing on 64-bit, many float values are stored inline
|
||||
(without heap allocation) using a rotation encoding.
|
||||
|
||||
### Additional Numeric Types (via gems)
|
||||
|
||||
- **Rational** (`mruby-rational`): exact rational arithmetic
|
||||
- **Complex** (`mruby-complex`): complex number support
|
||||
- **Bigint** (`mruby-bigint`): arbitrary-precision integers
|
||||
|
||||
## Core Classes
|
||||
|
||||
These classes are always available in mruby (no gem required):
|
||||
|
||||
| Class | Notes |
|
||||
| ----- | ----- |
|
||||
| Object | Base class for all objects |
|
||||
| Module | Module definition and mixin |
|
||||
| Class | Class definition and instantiation |
|
||||
| NilClass | Singleton `nil` |
|
||||
| TrueClass | Singleton `true` |
|
||||
| FalseClass | Singleton `false` |
|
||||
| Integer | Fixed-precision integer |
|
||||
| Float | Floating-point (unless `MRB_NO_FLOAT`) |
|
||||
| Symbol | Interned identifier |
|
||||
| String | Mutable byte string |
|
||||
| Array | Ordered collection |
|
||||
| Hash | Key-value mapping |
|
||||
| Range | Interval representation |
|
||||
| Proc | Closure / callable object |
|
||||
| Exception | Exception hierarchy root |
|
||||
| StandardError | Common error base |
|
||||
|
||||
### Core Modules
|
||||
|
||||
| Module | Notes |
|
||||
| ------ | ----- |
|
||||
| Kernel | Core methods (`puts`, `p`, `raise`, etc.) |
|
||||
| Comparable | Comparison operators via `<=>` |
|
||||
| Enumerable | Collection iteration methods |
|
||||
|
||||
## Standard Library (via gemboxes)
|
||||
|
||||
mruby's standard library is organized into gemboxes. The `default`
|
||||
gembox includes all of the below. Use this table to find which
|
||||
gembox provides the class or feature you need:
|
||||
|
||||
### Classes and Modules
|
||||
|
||||
| Class/Module | Gembox | Gem |
|
||||
| ------------ | ------ | --- |
|
||||
| Fiber | stdlib | mruby-fiber |
|
||||
| Enumerator | stdlib | mruby-enumerator |
|
||||
| Enumerator::Lazy | stdlib | mruby-enum-lazy |
|
||||
| Set | stdlib | mruby-set |
|
||||
| ObjectSpace | stdlib | mruby-objectspace |
|
||||
| Time | stdlib-ext | mruby-time |
|
||||
| Struct | stdlib-ext | mruby-struct |
|
||||
| Data | stdlib-ext | mruby-data |
|
||||
| Random | stdlib-ext | mruby-random |
|
||||
| IO, File | stdlib-io | mruby-io |
|
||||
| Socket | stdlib-io | mruby-socket |
|
||||
| Dir | stdlib-io | mruby-dir |
|
||||
| Errno | stdlib-io | mruby-errno |
|
||||
| Math | math | mruby-math |
|
||||
| Rational | math | mruby-rational |
|
||||
| Complex | math | mruby-complex |
|
||||
| Bigint | math | mruby-bigint |
|
||||
| Method, UnboundMethod | metaprog | mruby-method |
|
||||
|
||||
### Methods and Features
|
||||
|
||||
| Feature | Gembox | Gem |
|
||||
| ------- | ------ | --- |
|
||||
| `catch`/`throw` | stdlib | mruby-catch |
|
||||
| `Kernel#sprintf`, `String#%` | stdlib-ext | mruby-sprintf |
|
||||
| `Array#pack`, `String#unpack` | stdlib-ext | mruby-pack |
|
||||
| `Kernel#rand` | stdlib-ext | mruby-random |
|
||||
| `Kernel#eval` | metaprog | mruby-eval |
|
||||
| `Kernel#binding` | metaprog | mruby-binding |
|
||||
| `Proc#binding` | metaprog | mruby-proc-binding |
|
||||
| Runtime compiler | metaprog | mruby-compiler |
|
||||
|
||||
### Core Class Extensions
|
||||
|
||||
The `stdlib` gembox also extends built-in classes with additional
|
||||
methods. These are included by default:
|
||||
|
||||
| Extension | Examples |
|
||||
| --------- | -------- |
|
||||
| Array extensions | `#dig`, `#union`, `#difference` |
|
||||
| Hash extensions | `#dig`, `#transform_keys`, `#transform_values` |
|
||||
| String extensions | `#encode`, `#bytes`, `#chars` |
|
||||
| Numeric extensions | `Integer#digits`, `Integer#pow` |
|
||||
| Comparable extensions | `#clamp` |
|
||||
| Enumerable extensions | `#sort_by`, `#min_by`, `#max_by`, `#tally` |
|
||||
| Range extensions | `#size`, `#cover?` |
|
||||
| Proc extensions | `#<<`, `#>>` (composition) |
|
||||
| Symbol extensions | `#to_proc` |
|
||||
| Object extensions | `#then`, `#yield_self` |
|
||||
| Kernel extensions | `#__method__` |
|
||||
| Class/Module extensions | `Module#name` |
|
||||
|
||||
### Gembox Summary
|
||||
|
||||
| Gembox | Contents | Notes |
|
||||
| ------ | -------- | ----- |
|
||||
| `stdlib` | Core class extensions, Fiber, Enumerator, Set | Works with `MRB_NO_STDIO` and `MRB_NO_FLOAT` |
|
||||
| `stdlib-ext` | Time, Struct, Data, Random, sprintf, pack | Works with `MRB_NO_STDIO` and `MRB_NO_FLOAT` |
|
||||
| `stdlib-io` | IO, File, Dir, Socket, Errno | Requires stdio |
|
||||
| `math` | Math, Rational, Complex, Bigint | Works with `MRB_NO_STDIO` |
|
||||
| `metaprog` | eval, binding, Method, compiler | Works with `MRB_NO_STDIO` and `MRB_NO_FLOAT` |
|
||||
| `default` | All of the above + CLI tools | Full installation |
|
||||
|
||||
## Key Differences from CRuby
|
||||
|
||||
### No Runtime Loading
|
||||
|
||||
mruby has no `require` or `load`. All code (gems, libraries) is
|
||||
linked at build time. To add functionality, include the appropriate
|
||||
gem in your build configuration:
|
||||
|
||||
```ruby
|
||||
MRuby::Build.new do |conf|
|
||||
conf.gem :core => "mruby-time"
|
||||
end
|
||||
```
|
||||
|
||||
### No `defined?` Keyword
|
||||
|
||||
The `defined?` keyword raises `NameError` instead of returning a
|
||||
type string or `nil`. Use alternatives:
|
||||
|
||||
```ruby
|
||||
# Instead of: defined?(Foo)
|
||||
Object.const_defined?(:Foo)
|
||||
|
||||
# Instead of: defined?(@var)
|
||||
instance_variable_defined?(:@var)
|
||||
|
||||
# Instead of: defined?(method_name)
|
||||
respond_to?(:method_name)
|
||||
```
|
||||
|
||||
### Fiber Limitations
|
||||
|
||||
Fibers cannot cross C function boundaries. You cannot yield from a
|
||||
fiber inside a C-implemented method. Only `mrb_fiber_yield` at
|
||||
function return is supported.
|
||||
|
||||
### Array and String Subclasses
|
||||
|
||||
`Array` and `String` do not support instance variables to reduce memory.
|
||||
This means subclassing `Array` or `String` and adding `@fields` will raise an error.
|
||||
|
||||
### Operator Overriding
|
||||
|
||||
Operators of primitive classes cannot be overridden by user code.
|
||||
Redefining `String#+` has no effect on the behavior of the `+` __operator__.
|
||||
|
||||
### Module Loading Hooks
|
||||
|
||||
`include`/`prepend`/`extend` do not call `append_features`/
|
||||
`prepend_features`/`extend_object` hooks. The module is included
|
||||
directly.
|
||||
|
||||
### Small Hash Optimization
|
||||
|
||||
For small hashes, `#hash` is not called on keys. Custom `#hash`
|
||||
methods may not execute for small hash tables.
|
||||
|
||||
### No Refinements
|
||||
|
||||
Module refinements (`refine`, `using`) are not supported.
|
||||
|
||||
### No Encoding Class
|
||||
|
||||
There is no `Encoding` class. String encoding is either pure bytes
|
||||
or UTF-8 (opt-in via `MRB_UTF8_STRING` compile flag).
|
||||
|
||||
### `nil?` in Conditionals
|
||||
|
||||
Redefining `nil?` has no effect on conditional expressions.
|
||||
The VM uses direct nil checks for performance.
|
||||
|
||||
### Integer Precision
|
||||
|
||||
Integer size varies by boxing mode (see [Numeric Types](#numeric-types)
|
||||
above). Code relying on 64-bit integer precision may behave
|
||||
differently on 32-bit or NaN boxing configurations.
|
||||
|
||||
## Build-Time Configuration
|
||||
|
||||
Key compile-time macros that affect language behavior:
|
||||
|
||||
| Macro | Effect |
|
||||
| ----- | ------ |
|
||||
| `MRB_NO_FLOAT` | Remove all float support |
|
||||
| `MRB_USE_FLOAT32` | Use 32-bit float instead of double |
|
||||
| `MRB_UTF8_STRING` | Enable UTF-8 string handling |
|
||||
| `MRB_INT32` | Force 32-bit integer |
|
||||
| `MRB_INT64` | Force 64-bit integer |
|
||||
| `MRB_STR_LENGTH_MAX` | Max string length (default 1MB) |
|
||||
| `MRB_ARY_LENGTH_MAX` | Max array length (default 2^17) |
|
||||
|
||||
See [mrbconf.md](mrbconf.md) for the complete list of configuration
|
||||
macros.
|
||||
+5
-96
@@ -1,4 +1,4 @@
|
||||
<!-- summary: About Memory Allocator Customization and Heap Regions -->
|
||||
<!-- summary: About Memory Allocator Customization -->
|
||||
|
||||
# Memory Allocation
|
||||
|
||||
@@ -50,6 +50,7 @@ Implement mrb_basic_alloc_func() in your code, and mruby will invoke it for ever
|
||||
If you are moving from the old API:
|
||||
|
||||
1. **Removal of `mrb_open_allocf()`**
|
||||
|
||||
- \_Old:
|
||||
|
||||
```c
|
||||
@@ -64,9 +65,11 @@ If you are moving from the old API:
|
||||
```
|
||||
|
||||
2. **`mrb_open_core()` takes no arguments**
|
||||
|
||||
- Simply drop any allocf or user-data arguments, and redefine `mrb_basic_alloc_func` as you need.
|
||||
|
||||
3. **No more `mrb_allocf` type**
|
||||
|
||||
- Definitions using the `mrb_allocf` typedef can be removed; implement `mrb_basic_alloc_func()` with the signature below:
|
||||
|
||||
```c
|
||||
@@ -74,6 +77,7 @@ If you are moving from the old API:
|
||||
```
|
||||
|
||||
4. **`mrb_basic_alloc_func` signature change**
|
||||
|
||||
- _Old:_
|
||||
|
||||
```c
|
||||
@@ -114,98 +118,3 @@ If you are moving from the old API:
|
||||
|
||||
mrb_state *mrb = mrb_open_core();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Heap Regions: Contiguous Memory for GC
|
||||
|
||||
By default, mruby allocates GC heap pages individually via `malloc()`.
|
||||
On embedded targets with multiple memory banks (e.g., STM32 CCM+SRAM,
|
||||
ESP32 PSRAM+IRAM), you may want to place heap pages in a specific
|
||||
memory region. `mrb_gc_add_region()` lets you provide a contiguous
|
||||
buffer that mruby carves into heap pages.
|
||||
|
||||
### API
|
||||
|
||||
```c
|
||||
#include <mruby/gc.h>
|
||||
|
||||
int mrb_gc_add_region(mrb_state *mrb, void *start, size_t size);
|
||||
```
|
||||
|
||||
- **`start`**: pointer to a contiguous memory buffer.
|
||||
- **`size`**: size of the buffer in bytes.
|
||||
- **Returns**: number of heap pages carved from the buffer, or 0 if
|
||||
the buffer is too small.
|
||||
|
||||
The buffer is aligned internally to pointer size. Each page is
|
||||
approximately 40 KB on 64-bit systems (24 KB on 32-bit). The caller
|
||||
retains ownership of the buffer and must keep it valid for the
|
||||
lifetime of the `mrb_state`.
|
||||
|
||||
### Example: Static buffer
|
||||
|
||||
```c
|
||||
#include <mruby.h>
|
||||
#include <mruby/gc.h>
|
||||
|
||||
/* 256 KB static buffer -- about 6 pages on 64-bit */
|
||||
static char heap_buf[256 * 1024];
|
||||
|
||||
int main(void)
|
||||
{
|
||||
mrb_state *mrb = mrb_open();
|
||||
int pages = mrb_gc_add_region(mrb, heap_buf, sizeof(heap_buf));
|
||||
/* pages are immediately available for object allocation */
|
||||
|
||||
/* ... use mrb ... */
|
||||
|
||||
mrb_close(mrb); /* region pages are cleaned up; buffer is not freed */
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### Example: MCU with multiple RAM banks
|
||||
|
||||
```c
|
||||
/* STM32 with 64 KB CCM and 128 KB SRAM */
|
||||
extern char __ccm_start[], __ccm_end[]; /* linker symbols */
|
||||
extern char __sram_start[], __sram_end[];
|
||||
|
||||
mrb_state *mrb = mrb_open();
|
||||
mrb_gc_add_region(mrb, __ccm_start, __ccm_end - __ccm_start);
|
||||
mrb_gc_add_region(mrb, __sram_start, __sram_end - __sram_start);
|
||||
```
|
||||
|
||||
### How it works
|
||||
|
||||
When `mrb_gc_add_region()` is called, mruby:
|
||||
|
||||
1. Aligns the buffer start to pointer size.
|
||||
2. Divides the buffer into `mrb_heap_page`-sized chunks.
|
||||
3. Initializes each page's freelist and links it into the GC heap.
|
||||
4. Records the region in a descriptor for O(1) pointer-to-page mapping.
|
||||
|
||||
Region pages participate in the normal GC cycle (mark-and-sweep) like
|
||||
any other heap page. The only differences are:
|
||||
|
||||
- **Never freed**: the GC will not call `free()` on region pages, even
|
||||
if all objects on a page are dead. The page stays in the heap with an
|
||||
empty freelist, ready for reuse.
|
||||
- **Fallback**: when all region pages are full, mruby falls back to
|
||||
`malloc()` for new pages as usual.
|
||||
- **Cleanup**: `mrb_close()` frees the internal region descriptor but
|
||||
does not free the buffer itself.
|
||||
|
||||
### Sizing
|
||||
|
||||
The page size is controlled by `MRB_HEAP_PAGE_SIZE` (default: 1024 slots).
|
||||
Each page occupies:
|
||||
|
||||
| Platform | Slot size | Page size (approx) |
|
||||
|----------|-----------|---------------------|
|
||||
| 64-bit | 40 bytes | ~41 KB |
|
||||
| 32-bit | 24 bytes | ~25 KB |
|
||||
|
||||
To estimate pages for a given buffer: `pages = buffer_size / sizeof(mrb_heap_page)`.
|
||||
Each page provides `MRB_HEAP_PAGE_SIZE` object slots.
|
||||
|
||||
+19
-63
@@ -61,12 +61,12 @@ end
|
||||
`MRB_STACK_EXTEND_DOUBLING`
|
||||
|
||||
- If defined doubles the stack size when extending it.
|
||||
- Otherwise extends stack with 1.5x growth (minimum `MRB_STACK_GROWTH`).
|
||||
- Otherwise extends stack with `MRB_STACK_GROWTH`.
|
||||
|
||||
`MRB_STACK_GROWTH`
|
||||
|
||||
- Default value is `128`.
|
||||
- Minimum stack growth size when extending.
|
||||
- Used in stack extending.
|
||||
- Ignored when `MRB_STACK_EXTEND_DOUBLING` is defined.
|
||||
|
||||
`MRB_STACK_MAX`
|
||||
@@ -95,7 +95,6 @@ end
|
||||
|
||||
- When defined, or `MRB_INT32` are not defined on 64-bit CPU mode, `mrb_int` will be defined as `int64_t`.
|
||||
- Conflicts with `MRB_INT32`.
|
||||
- On 32-bit platforms, `MRB_INT64` requires `MRB_NO_BOXING` because heap-allocated `RInteger` needs 8-byte alignment that the GC heap may not guarantee with word or NaN boxing.
|
||||
|
||||
## Garbage collector configuration
|
||||
|
||||
@@ -126,6 +125,11 @@ end
|
||||
|
||||
- Default value is `1024`.
|
||||
- Specifies number of `RBasic` per each heap page.
|
||||
- To calculate the number of bytes per heap page, it is "(size of management data per heap page) + (size per object) \* `MRB_HEAP_PAGE_SIZE`".
|
||||
In mruby 3.1.0, the "size of management data per heap page" is 6 words, also "size per object" is 6 words.
|
||||
For a 32-bit CPU, `(6 * 4) + (6 * 4) * MRB_HEAP_PAGE_SIZE` gives the bytes of size per heap page.
|
||||
Conversely, for example, to keep the size per heap page to 4 Ki bytes,
|
||||
calculate `(4096 - (6 * 4)) / (6 * 4)` to specify `MRB_HEAP_PAGE_SIZE=169`.
|
||||
|
||||
## Memory pool configuration
|
||||
|
||||
@@ -169,24 +173,8 @@ end
|
||||
|
||||
`MRB_WORD_BOXING`
|
||||
|
||||
- If defined represent `mrb_value` as a word (natural unit of data for the processor).
|
||||
- Default boxing mode when none is specified.
|
||||
- On 64-bit platforms, floats are inlined using rotation encoding.
|
||||
- On 32-bit platforms, floats are heap-allocated as `RFloat` objects.
|
||||
|
||||
`MRB_NO_BOXING`
|
||||
|
||||
- If defined represent `mrb_value` as a C struct (occupies 2 words).
|
||||
- Most portable but least memory-efficient representation.
|
||||
- Required for `MRB_INT64` on 32-bit platforms.
|
||||
- Default for `host-debug` configuration.
|
||||
|
||||
`MRB_WORDBOX_NO_INLINE_FLOAT`
|
||||
|
||||
- If defined disables inline float values in word boxing.
|
||||
- All floats are heap-allocated as `RFloat` objects.
|
||||
- Automatically defined on 32-bit platforms (64-bit `double` cannot fit in a 32-bit word).
|
||||
- Only meaningful with `MRB_WORD_BOXING`.
|
||||
- If defined represent `mrb_value` as a word.
|
||||
- If defined `Float` will be a mruby object with `RBasic`.
|
||||
|
||||
## Reduce heap memory configuration
|
||||
|
||||
@@ -194,7 +182,7 @@ end
|
||||
|
||||
- Use `etext` and `edata` section addresses defined by the linker to detect read-only data.
|
||||
- Those addresses are widely available, but not portable, nor standardized.
|
||||
- Defined by default on User-mode Linux.
|
||||
- This macro is defined by default on User-mode Linux.
|
||||
|
||||
`MRB_NO_DEFAULT_RO_DATA_P`
|
||||
|
||||
@@ -202,15 +190,16 @@ end
|
||||
|
||||
`MRB_USE_CUSTOM_RO_DATA_P`
|
||||
|
||||
- Define to provide your own `mrb_ro_data_p()` implementation.
|
||||
- Please try if `MRB_USE_LINK_TIME_RO_DATA_P` is not available.
|
||||
- The `mrb_ro_data_p()` function is implemented by the user in an arbitrary file.
|
||||
- The prototype declaration is `mrb_bool mrb_ro_data_p(const char *ptr)`.
|
||||
- Return `TRUE` if `ptr` is in the read-only section, otherwise return `FALSE`.
|
||||
|
||||
## Other configuration
|
||||
|
||||
`MRB_USE_MALLOC_TRIM`
|
||||
`MRB_MALLOC_TRIM`
|
||||
|
||||
- Call `malloc_trim(0)` for each `mrb_full_gc()` call.
|
||||
- call `malloc_trim(0)` for each `mrb_full_gc()` call
|
||||
|
||||
`MRB_UTF8_STRING`
|
||||
|
||||
@@ -219,13 +208,13 @@ end
|
||||
|
||||
`MRB_STR_LENGTH_MAX`
|
||||
|
||||
- The maximum length of strings (default 1048576).
|
||||
- Set this value to zero to skip the check.
|
||||
- The maximum length of strings (default 1MB)
|
||||
- set this value to zero to skip the check
|
||||
|
||||
`MRB_ARY_LENGTH_MAX`
|
||||
|
||||
- The maximum length of arrays (default 131072).
|
||||
- Set this value to zero to skip the check.
|
||||
- The maximum length of arrays (default 1MB)
|
||||
- set this value to zero to skip the check
|
||||
|
||||
`MRB_FUNCALL_ARGC_MAX`
|
||||
|
||||
@@ -256,37 +245,4 @@ end
|
||||
|
||||
`MRB_USE_VM_SWITCH_DISPATCH`
|
||||
|
||||
- Turn on switch dispatch in VM loop.
|
||||
- Otherwise, computed goto (direct threading) is used when supported by the compiler.
|
||||
|
||||
`MRB_SYMBOL_LINEAR_THRESHOLD`
|
||||
|
||||
- Default value is `256`.
|
||||
- Threshold for switching symbol table from linear search to hash table.
|
||||
|
||||
## Tuning profiles
|
||||
|
||||
Predefined profiles adjust several macros together for specific
|
||||
deployment targets. Define one of the following:
|
||||
|
||||
`MRB_CONSTRAINED_BASELINE_PROFILE`
|
||||
|
||||
- For micro controllers.
|
||||
- Enables `MRB_NO_METHOD_CACHE`, reduces `KHASH_DEFAULT_SIZE` to `16`,
|
||||
and `MRB_HEAP_PAGE_SIZE` to `256`.
|
||||
|
||||
`MRB_BASELINE_PROFILE`
|
||||
|
||||
- Default mruby profile. No additional changes.
|
||||
|
||||
`MRB_MAIN_PROFILE`
|
||||
|
||||
- For desktop computers or workstations.
|
||||
- Increases `MRB_METHOD_CACHE_SIZE` to `1024` and `MRB_HEAP_PAGE_SIZE`
|
||||
to `4096`.
|
||||
|
||||
`MRB_HIGH_PROFILE`
|
||||
|
||||
- For long-lived server processes.
|
||||
- Increases `MRB_METHOD_CACHE_SIZE` to `4096` and `MRB_HEAP_PAGE_SIZE`
|
||||
to `4096`.
|
||||
- Turn on switch dispatch in VM loop
|
||||
|
||||
+3
-25
@@ -256,7 +256,7 @@ When more than one version requirements is passed, the dependency must satisfy a
|
||||
|
||||
You can have default gem to use as dependency when it's not defined in your build configuration.
|
||||
When the last argument of `add_dependency` call is `Hash`, it will be treated as default gem information.
|
||||
Its format is same as argument of method `MRuby::Build#gem`, except that it can't be treated as path gem location.
|
||||
Its format is same as argument of method `MRuby::Build#gem`, expect that it can't be treated as path gem location.
|
||||
|
||||
When a special version of dependency is required,
|
||||
use `MRuby::Build#gem` in the build configuration to override default gem.
|
||||
@@ -304,34 +304,12 @@ Your GEM can export include paths to another GEMs that depends on your GEM.
|
||||
By default, `/...absolute path.../{GEM_NAME}/include` will be exported.
|
||||
So it is recommended not to put GEM's local header files on include/.
|
||||
|
||||
These exports are transitive.
|
||||
These exports are retroactive.
|
||||
For example: when B depends on C and A depends on B, A will get include paths exported by C.
|
||||
|
||||
Exported `include_paths` are automatically appended to GEM local `include_paths` by rake.
|
||||
Exported include_paths are automatically appended to GEM local include_paths by rake.
|
||||
You can use `spec.export_include_paths` accessor if you want more complex build.
|
||||
|
||||
### Settings for GEM build commands/tasks
|
||||
|
||||
When the block argument passed to `MRuby::Gem::Specification.new` is executed,
|
||||
the GEM build commands/tasks for the `MRuby::Build` instance may not yet be finalized.
|
||||
In most cases, modifying the GEM build commands/tasks within the block passed to
|
||||
`MRuby::Gem::Specification.new` is not a problem.
|
||||
|
||||
However, you may need to perform GEM build commands/tasks after the GEM build
|
||||
commands/tasks for the `MRuby::Build` instance have been finalized.
|
||||
In such cases, you can achieve this by passing a block argument to
|
||||
`MRuby::Gem::Specification#build_settings` within the block passed to
|
||||
`MRuby::Gem::Specification.new`.
|
||||
|
||||
```ruby
|
||||
spec.build_settings do
|
||||
spec.cc.flags << "-any_flags"
|
||||
end
|
||||
```
|
||||
|
||||
**NOTE**: Using the `build_settings` method will cause GEM's all build command settings
|
||||
directly written in the block passed to `MRuby::Gem::Specification.new` to be ignored.
|
||||
|
||||
## C Extension
|
||||
|
||||
mruby can be extended with C. This is possible by using the C API to
|
||||
|
||||
@@ -1,414 +0,0 @@
|
||||
<!-- summary: ROM Method Tables for Memory-Efficient Method Registration -->
|
||||
|
||||
# ROM Method Tables
|
||||
|
||||
ROM method tables allow C methods to be registered using static data
|
||||
stored in ROM (read-only memory) rather than heap-allocated RAM. This
|
||||
saves significant memory on embedded systems where RAM is scarce.
|
||||
|
||||
## Motivation
|
||||
|
||||
In a default mruby build, `mrb_open()` builds ~40 classes with ~700+
|
||||
method entries at startup. Each method entry is heap-allocated via
|
||||
individual `mrb_define_method_id()` calls. On a constrained MCU, this
|
||||
consumes ~14KB of RAM for method table metadata alone.
|
||||
|
||||
ROM method tables eliminate this cost by placing method metadata in
|
||||
static `const` data at compile time. Only runtime mutations (e.g.,
|
||||
reopening a class to add methods) trigger heap allocation.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Chained Layers
|
||||
|
||||
Each class has a method table (`mt`) pointer to a linked list of
|
||||
`mrb_mt_tbl` layers:
|
||||
|
||||
```
|
||||
String.mt -> [mutable layer] -> [string_ext ROM] -> [string_core ROM] -> NULL
|
||||
```
|
||||
|
||||
**Lookup** walks the chain front-to-back, returning the first match.
|
||||
The method cache makes repeated lookups O(1), so the chain walk
|
||||
only occurs on cache misses.
|
||||
|
||||
**Mutation** uses copy-on-write (COW): if the top layer is read-only,
|
||||
a new mutable layer is created in front of it. The ROM data is never
|
||||
modified.
|
||||
|
||||
```
|
||||
Before: String.mt -> [string_ext ROM] -> [string_core ROM] -> NULL
|
||||
|
||||
After String.define_method(:foo):
|
||||
String.mt -> [mutable: foo] -> [string_ext ROM] -> [string_core ROM] -> NULL
|
||||
```
|
||||
|
||||
### Memory Layout
|
||||
|
||||
Each `mrb_mt_tbl` stores method entries as an array of `mrb_mt_entry`
|
||||
structs, each combining a function pointer, a symbol key, and flags:
|
||||
|
||||
```
|
||||
ptr -> [ entry[0] | entry[1] | ... | entry[N-1] ]
|
||||
|<-- mrb_mt_entry: { val, key, flags } -->|
|
||||
```
|
||||
|
||||
Values are `union mrb_mt_ptr` (function pointer or proc pointer). Keys
|
||||
are pure `mrb_sym` (no flag encoding). Flags are a separate `uint32_t`
|
||||
field that stores visibility, func/proc type, and argument spec.
|
||||
|
||||
Entries are searched linearly, so source code order does not matter.
|
||||
The method cache makes repeated lookups O(1), so the linear scan
|
||||
only occurs on cache misses.
|
||||
|
||||
### Per-State Wrappers
|
||||
|
||||
The `const mrb_mt_entry[]` arrays are truly static and shared across
|
||||
the process. However, the `mrb_mt_tbl` wrapper (which carries the
|
||||
`next` pointer for chaining) is heap-allocated per `mrb_state` by
|
||||
`MRB_MT_INIT_ROM()`. This allows multiple `mrb_state` instances in
|
||||
the same process to each have independent method table chains, even
|
||||
when linking to the same const entries.
|
||||
|
||||
## How to Define a ROM Method Table
|
||||
|
||||
### Step 1: Define the Static Data
|
||||
|
||||
Include `<mruby/class.h>` (which provides `mrb_mt_entry`,
|
||||
`MRB_MT_ENTRY()`, and flag constants) and define the ROM entries:
|
||||
|
||||
```c
|
||||
#include <mruby/class.h>
|
||||
#include <mruby/presym.h>
|
||||
|
||||
static const mrb_mt_entry my_rom_entries[] = {
|
||||
MRB_MT_ENTRY(my_method_a, MRB_SYM(method_a), MRB_ARGS_REQ(1)),
|
||||
MRB_MT_ENTRY(my_method_b, MRB_SYM(method_b), MRB_ARGS_NONE()),
|
||||
MRB_MT_ENTRY(my_method_eq, MRB_OPSYM(eq), MRB_ARGS_REQ(1)),
|
||||
};
|
||||
```
|
||||
|
||||
### Step 2: Register in the Init Function
|
||||
|
||||
Replace `mrb_define_method_id()` calls with a single
|
||||
`MRB_MT_INIT_ROM()` call:
|
||||
|
||||
```c
|
||||
void
|
||||
mrb_mruby_mygem_gem_init(mrb_state *mrb)
|
||||
{
|
||||
struct RClass *c = mrb_define_class_id(mrb, MRB_SYM(MyClass), mrb->object_class);
|
||||
MRB_MT_INIT_ROM(mrb, c, my_rom_entries);
|
||||
}
|
||||
```
|
||||
|
||||
`MRB_MT_INIT_ROM()` allocates a per-state wrapper and pushes the ROM
|
||||
layer onto the class's method table chain.
|
||||
|
||||
### Step 3: Verify
|
||||
|
||||
Build and run the test suite. ROM tables are semantically transparent
|
||||
to Ruby code.
|
||||
|
||||
## Reference
|
||||
|
||||
### Data Types
|
||||
|
||||
Defined in `include/mruby/class.h`:
|
||||
|
||||
```c
|
||||
union mrb_mt_ptr {
|
||||
const struct RProc *proc;
|
||||
mrb_func_t func;
|
||||
};
|
||||
|
||||
typedef struct mrb_mt_entry {
|
||||
union mrb_mt_ptr val;
|
||||
mrb_sym key; /* pure symbol ID (no flags packed) */
|
||||
uint32_t flags; /* method flags + aspec */
|
||||
} mrb_mt_entry;
|
||||
|
||||
typedef struct mrb_mt_tbl {
|
||||
int size;
|
||||
int alloc; /* bit 30: MRB_MT_READONLY_BIT */
|
||||
mrb_mt_entry *ptr;
|
||||
struct mrb_mt_tbl *next; /* next (lower-priority) layer, or NULL */
|
||||
} mrb_mt_tbl;
|
||||
```
|
||||
|
||||
### Macros
|
||||
|
||||
```c
|
||||
/* ROM table entry: 3rd param is MRB_ARGS_*() optionally OR'd with
|
||||
MRB_MT_PRIVATE. The macro OR's in MRB_MT_FUNC automatically. */
|
||||
#define MRB_MT_ENTRY(fn, sym, flags) \
|
||||
{ { .func = (fn) }, (sym), (flags) | MRB_MT_FUNC }
|
||||
|
||||
/* Extract aspec from combined flags */
|
||||
#define MRB_MT_ASPEC(flags) ((mrb_aspec)((flags) & 0xffffff))
|
||||
|
||||
/* Allocate a per-state ROM wrapper and push onto class method chain */
|
||||
#define MRB_MT_INIT_ROM(mrb, cls, entries) \
|
||||
mrb_mt_init_rom(mrb, cls, entries, \
|
||||
(int)(sizeof(entries)/sizeof(entries[0])))
|
||||
```
|
||||
|
||||
### Flags
|
||||
|
||||
| Flag | Value | Description |
|
||||
| ---------------- | ------- | ----------------------------------- |
|
||||
| `MRB_MT_FUNC` | (1<<24) | C function (auto-set by macro) |
|
||||
| `MRB_MT_PUBLIC` | 0 | Public visibility (default) |
|
||||
| `MRB_MT_PRIVATE` | (1<<25) | Private visibility (in entry param) |
|
||||
|
||||
The third parameter to `MRB_MT_ENTRY()` is an `MRB_ARGS_*()`
|
||||
expression optionally OR'd with `MRB_MT_PRIVATE`. The aspec value
|
||||
occupies bits 0-23 and the visibility flag occupies bit 25; these
|
||||
ranges do not overlap, so the values are simply OR'd together.
|
||||
`MRB_MT_FUNC` is set automatically. The no-arg optimization is
|
||||
derived at runtime from `aspec == 0` (`MRB_ARGS_NONE()`).
|
||||
|
||||
**How to write entries:**
|
||||
|
||||
- **`MRB_MT_ENTRY(fn, sym, MRB_ARGS_*(...))`**: Public method.
|
||||
- **`MRB_MT_ENTRY(fn, sym, MRB_ARGS_*(...) | MRB_MT_PRIVATE)`**:
|
||||
Private method.
|
||||
- Use the same `MRB_ARGS_*()` macros as `mrb_define_method_id()`.
|
||||
|
||||
### Symbol Macros
|
||||
|
||||
Use the presym macros for keys. See `doc/guides/symbol.md` for the
|
||||
full list:
|
||||
|
||||
```c
|
||||
MRB_SYM(size) /* size */
|
||||
MRB_SYM_B(chomp) /* chomp! */
|
||||
MRB_SYM_Q(frozen) /* frozen? */
|
||||
MRB_SYM_E(name) /* name= */
|
||||
MRB_OPSYM(add) /* + */
|
||||
MRB_OPSYM(eq) /* == */
|
||||
MRB_OPSYM(aref) /* [] */
|
||||
MRB_OPSYM(aset) /* []= */
|
||||
MRB_OPSYM(cmp) /* <=> */
|
||||
MRB_IVSYM(name) /* @name */
|
||||
```
|
||||
|
||||
### API
|
||||
|
||||
```c
|
||||
void mrb_mt_init_rom(mrb_state *mrb, struct RClass *c,
|
||||
const mrb_mt_entry *entries, int size);
|
||||
```
|
||||
|
||||
Allocates a per-state `mrb_mt_tbl` wrapper for the const entries and
|
||||
pushes it onto the class's method table chain. The wrapper is tracked
|
||||
in `mrb->rom_mt` and freed at `mrb_close()`. Use the `MRB_MT_INIT_ROM`
|
||||
macro to auto-compute the size. Multiple calls push additional layers,
|
||||
which is how extension gems add methods to core classes.
|
||||
|
||||
## Entry Correspondence
|
||||
|
||||
Each `MRB_MT_ENTRY()` bundles a function pointer with its method name
|
||||
and flags in a single line. Their order in the source code does not
|
||||
matter, but keeping related methods
|
||||
together improves readability.
|
||||
|
||||
**Method aliases** (two names for the same function) are expressed as
|
||||
separate entries sharing the same function pointer:
|
||||
|
||||
```c
|
||||
static const mrb_mt_entry str_rom_entries[] = {
|
||||
MRB_MT_ENTRY(mrb_str_size, MRB_SYM(size), MRB_ARGS_NONE()),
|
||||
MRB_MT_ENTRY(mrb_str_size, MRB_SYM(length), MRB_ARGS_NONE()),
|
||||
};
|
||||
```
|
||||
|
||||
## Conditional Methods
|
||||
|
||||
Methods that depend on build configuration (e.g., `MRB_NO_FLOAT`) can
|
||||
use `#ifdef` directly inside the ROM entries array. The `sizeof` in
|
||||
`MRB_MT_INIT_ROM()` automatically adjusts to the number of entries
|
||||
that survive preprocessing:
|
||||
|
||||
```c
|
||||
static const mrb_mt_entry integer_rom_entries[] = {
|
||||
MRB_MT_ENTRY(int_to_s, MRB_SYM(to_s), MRB_ARGS_OPT(1)),
|
||||
MRB_MT_ENTRY(int_add, MRB_OPSYM(add), MRB_ARGS_REQ(1)),
|
||||
#ifndef MRB_NO_FLOAT
|
||||
MRB_MT_ENTRY(int_to_f, MRB_SYM(to_f), MRB_ARGS_NONE()),
|
||||
#endif
|
||||
};
|
||||
```
|
||||
|
||||
For conditional methods on a **different class**, use a separate ROM
|
||||
table wrapped in the `#ifdef`:
|
||||
|
||||
```c
|
||||
#ifndef MRB_NO_FLOAT
|
||||
static const mrb_mt_entry float_rom_entries[] = { ... };
|
||||
#endif
|
||||
|
||||
void mrb_init_numeric(mrb_state *mrb) {
|
||||
MRB_MT_INIT_ROM(mrb, integer, integer_rom_entries);
|
||||
#ifndef MRB_NO_FLOAT
|
||||
MRB_MT_INIT_ROM(mrb, fl, float_rom_entries);
|
||||
#endif
|
||||
}
|
||||
```
|
||||
|
||||
## Extension Gems
|
||||
|
||||
Extension gems use exactly the same pattern. Since gems are
|
||||
initialized after core, calling `MRB_MT_INIT_ROM()` pushes the gem's
|
||||
ROM layer in front of the core ROM layer:
|
||||
|
||||
```c
|
||||
/* mrbgems/mruby-string-ext/src/string.c */
|
||||
|
||||
static const mrb_mt_entry string_ext_rom_entries[] = { ... };
|
||||
|
||||
void mrb_mruby_string_ext_gem_init(mrb_state *mrb)
|
||||
{
|
||||
struct RClass *s = mrb->string_class;
|
||||
MRB_MT_INIT_ROM(mrb, s, string_ext_rom_entries);
|
||||
}
|
||||
```
|
||||
|
||||
After initialization, String's method table chain looks like:
|
||||
|
||||
```
|
||||
String.mt -> [string_ext ROM, 53 methods]
|
||||
-> [string_core ROM, 46 methods]
|
||||
-> NULL
|
||||
```
|
||||
|
||||
A gem may also define ROM tables for multiple classes:
|
||||
|
||||
```c
|
||||
void mrb_mruby_mygem_gem_init(mrb_state *mrb)
|
||||
{
|
||||
MRB_MT_INIT_ROM(mrb, mrb->string_class, string_mygem_rom_entries);
|
||||
MRB_MT_INIT_ROM(mrb, mrb->integer_class, integer_mygem_rom_entries);
|
||||
}
|
||||
```
|
||||
|
||||
## Methods That Cannot Use ROM Tables
|
||||
|
||||
Some methods must remain as `mrb_define_method_id()` calls:
|
||||
|
||||
- **Class methods** (`mrb_define_class_method_id()`): ROM tables
|
||||
register instance methods only.
|
||||
- **Module functions** (`mrb_define_module_function_id()`): Same
|
||||
reason.
|
||||
- **Methods requiring `mrb_state*` at definition time**: For example,
|
||||
methods that create frozen RProc objects during init.
|
||||
- **Methods on dynamically created classes**: Classes created at
|
||||
init time (not stored in `mrb->xxx_class`) that require
|
||||
`mrb_define_class()` to obtain the class pointer.
|
||||
- **Cross-class methods** (methods on a class the gem does not own):
|
||||
Each ROM table adds a 16-byte `mrb_mt_tbl` layer to the target
|
||||
class's chain. For 1-2 methods, this overhead exceeds the savings.
|
||||
Use `mrb_define_method_id()` instead -- cross-class methods share
|
||||
the target class's existing mutable layer.
|
||||
|
||||
These methods are added after `MRB_MT_INIT_ROM()` and go into the
|
||||
mutable layer that sits in front of the ROM chain.
|
||||
|
||||
## Runtime Behavior
|
||||
|
||||
### Open Classes (COW)
|
||||
|
||||
Ruby's open classes work transparently. When a Ruby program or C code
|
||||
adds a method to a class with a ROM table, the COW mechanism creates a
|
||||
mutable layer:
|
||||
|
||||
```ruby
|
||||
class String
|
||||
def my_custom_method
|
||||
42
|
||||
end
|
||||
end
|
||||
"hello".my_custom_method #=> 42
|
||||
"hello".size #=> 5 (still found in ROM layer)
|
||||
```
|
||||
|
||||
### Method Removal
|
||||
|
||||
`remove_method` works on ROM methods using a tombstone marker. When a
|
||||
method in a ROM layer is removed, a special entry (`MRB_MT_FUNC` flag with
|
||||
`func=NULL`) is inserted into the mutable layer. The `mt_get()` lookup
|
||||
treats this marker as "not found" and stops searching the chain,
|
||||
effectively hiding the ROM entry. Unlike `undef_method` (which blocks
|
||||
superclass lookup), `remove_method`'s tombstone allows the superclass
|
||||
method to be found.
|
||||
|
||||
`undef_method` uses a different tombstone (`proc=NULL` without
|
||||
`MRB_MT_FUNC`), which is returned by `mt_get()` so the caller raises
|
||||
NoMethodError without searching the superclass.
|
||||
|
||||
### Class Duplication
|
||||
|
||||
`Class.dup` shares the ROM chain. The duplicated class gets an empty
|
||||
mutable layer pointing to the same ROM layers as the original. No ROM
|
||||
data is copied.
|
||||
|
||||
### Garbage Collection
|
||||
|
||||
ROM layers are skipped during GC mark and sweep phases. Only mutable
|
||||
layers are scanned for live RProc references and freed when the class
|
||||
is collected. ROM wrappers are freed at `mrb_close()` via the
|
||||
`mrb->rom_mt` tracking list.
|
||||
|
||||
### Memory Measurement
|
||||
|
||||
`mrb_class_mt_memsize()` reports only mutable layer memory. ROM
|
||||
wrappers are tracked separately and not counted per-class.
|
||||
|
||||
## Converting Existing Code
|
||||
|
||||
To convert existing `mrb_define_method_id()` calls to a ROM table:
|
||||
|
||||
1. **Count** the number of method definitions that can be converted.
|
||||
|
||||
2. **Create** the ROM entries array using `MRB_MT_ENTRY()`.
|
||||
|
||||
3. **Move** each `mrb_define_method_id()` call into the entries:
|
||||
- `MRB_MT_ENTRY(func, sym, aspec)` where:
|
||||
- `func` is the function pointer
|
||||
- `sym` is the symbol macro (e.g., `MRB_SYM(name)`)
|
||||
- `aspec` is the original `MRB_ARGS_*()` macro
|
||||
- For private methods, OR `MRB_MT_PRIVATE` into the aspec:
|
||||
`MRB_MT_ENTRY(func, sym, aspec | MRB_MT_PRIVATE)`
|
||||
|
||||
4. **Replace** the calls with `MRB_MT_INIT_ROM(mrb, c, entries)`.
|
||||
|
||||
5. **Keep** any methods that cannot be converted (see above) as
|
||||
individual `mrb_define_method_id()` calls after the ROM init.
|
||||
|
||||
6. **Build and test**: `rake CONFIG=host-debug -j24 all test:run:serial`
|
||||
|
||||
### Before
|
||||
|
||||
```c
|
||||
void mrb_mruby_foo_gem_init(mrb_state *mrb) {
|
||||
struct RClass *foo = mrb_define_class_id(mrb, MRB_SYM(Foo), mrb->object_class);
|
||||
mrb_define_method_id(mrb, foo, MRB_SYM(bar), foo_bar, MRB_ARGS_REQ(1));
|
||||
mrb_define_method_id(mrb, foo, MRB_SYM(baz), foo_baz, MRB_ARGS_NONE());
|
||||
mrb_define_method_id(mrb, foo, MRB_OPSYM(eq), foo_eq, MRB_ARGS_REQ(1));
|
||||
}
|
||||
```
|
||||
|
||||
### After
|
||||
|
||||
```c
|
||||
static const mrb_mt_entry foo_rom_entries[] = {
|
||||
MRB_MT_ENTRY(foo_bar, MRB_SYM(bar), MRB_ARGS_REQ(1)),
|
||||
MRB_MT_ENTRY(foo_baz, MRB_SYM(baz), MRB_ARGS_NONE()),
|
||||
MRB_MT_ENTRY(foo_eq, MRB_OPSYM(eq), MRB_ARGS_REQ(1)),
|
||||
};
|
||||
|
||||
void mrb_mruby_foo_gem_init(mrb_state *mrb) {
|
||||
struct RClass *foo = mrb_define_class_id(mrb, MRB_SYM(Foo), mrb->object_class);
|
||||
MRB_MT_INIT_ROM(mrb, foo, foo_rom_entries);
|
||||
}
|
||||
```
|
||||
+11
-4
@@ -73,7 +73,14 @@ For `MRB_OPSYM()`, specify the names corresponding to operators (see
|
||||
can be specified for it). Other than that, describe only word characters
|
||||
excluding leading and ending punctuation.
|
||||
|
||||
These macros are converted to static symbol IDs at compile time.
|
||||
The `_2` suffix variants (e.g., `MRB_SYM_2`) are kept for backward
|
||||
compatibility only; they accept an explicit `mrb_state*` parameter
|
||||
but ignore it. New code should use the standard macros above.
|
||||
These macros are converted to static symbol IDs at compile time, unless
|
||||
preallocate symbols are disabled by `conf.disable_presym`. In that case,
|
||||
these macros are expanded to `mrb_intern_lit` calls, therefore the mruby state
|
||||
variable is required. The above macros assume the variable name is `mrb`. If
|
||||
its name is not `mrb`, you need to use macros with `_2` suffix, such as
|
||||
`MRB_SYM_2` to specify `mrb_state*` variable.
|
||||
|
||||
### Disabling Preallocated Symbols
|
||||
|
||||
You can disable preallocated symbols by specifying `conf.disable_presym` in the
|
||||
configuration file.
|
||||
|
||||
@@ -1,182 +0,0 @@
|
||||
<!-- summary: About mruby Architecture -->
|
||||
|
||||
# mruby Architecture
|
||||
|
||||
This document provides a map of mruby's internals for developers who
|
||||
want to understand, debug, or contribute to the codebase.
|
||||
|
||||
## Overview
|
||||
|
||||
mruby's execution pipeline:
|
||||
|
||||
```text
|
||||
Ruby source → Parser → AST → Code Generator → Bytecode (irep)
|
||||
↓
|
||||
VM → Result
|
||||
```
|
||||
|
||||
The design priority is **memory > performance > readability**.
|
||||
|
||||
## Object Model
|
||||
|
||||
All heap-allocated Ruby objects share a common header (`MRB_OBJECT_HEADER`):
|
||||
|
||||
```text
|
||||
struct RBasic (8 bytes on 64-bit)
|
||||
┌──────────────┬─────┬──────────┬────────┬───────┐
|
||||
│ RClass *c │ tt │ gc_color │ frozen │ flags │
|
||||
│ (class ptr) │ 8b │ 3b │ 1b │ 20b │
|
||||
└──────────────┴─────┴──────────┴────────┴───────┘
|
||||
```
|
||||
|
||||
All object structs embed this header via `MRB_OBJECT_HEADER`:
|
||||
|
||||
| Struct | Ruby Type | Extra Fields |
|
||||
| ------ | --------- | ------------ |
|
||||
| `RObject` | Object instances | `iv` (instance variables) |
|
||||
| `RClass` | Class/Module | `iv`, `mt` (method table), `super` |
|
||||
| `RString` | String | embedded or heap buffer, length |
|
||||
| `RArray` | Array | embedded or heap buffer, length |
|
||||
| `RHash` | Hash | hash table or k-v array |
|
||||
| `RProc` | Proc/Lambda | `irep` or C function, environment |
|
||||
| `RData` | C data wrapper | `void *data`, `mrb_data_type` |
|
||||
| `RFiber` | Fiber | `mrb_context` |
|
||||
| `RException` | Exception | `iv` |
|
||||
|
||||
Immediate values (Integer, Symbol, `true`, `false`, `nil`) are encoded
|
||||
directly in `mrb_value` without heap allocation. The encoding depends on
|
||||
the boxing mode (see [boxing.md](boxing.md)).
|
||||
|
||||
Objects must fit within 5 words (`mrb_static_assert_object_size`).
|
||||
|
||||
## Virtual Machine
|
||||
|
||||
The VM is register-based, using two stacks: a **value stack** for
|
||||
registers (locals, temporaries, arguments) and a **call info stack**
|
||||
for tracking method/block call frames. Each method call pushes a
|
||||
`mrb_callinfo` frame with the method symbol, proc, PC, and argument
|
||||
counts.
|
||||
|
||||
The dispatch loop in `mrb_vm_run()` decodes opcodes and operates on
|
||||
registers. Method dispatch looks up the receiver's class method table
|
||||
(with a per-state method cache), then either calls a C function
|
||||
directly or pushes a new call frame for Ruby methods.
|
||||
|
||||
Exception handling uses `setjmp`/`longjmp` (or C++ exceptions if
|
||||
configured). Rescue/ensure handler tables are stored in each irep
|
||||
and searched during stack unwinding.
|
||||
|
||||
See [vm.md](vm.md) for detailed VM internals, [opcode.md](opcode.md)
|
||||
for the full instruction set.
|
||||
|
||||
## Garbage Collector
|
||||
|
||||
The GC uses **tri-color incremental mark-and-sweep** with an optional
|
||||
**generational mode**. Objects are colored white (unmarked), gray
|
||||
(marked, children pending), black (fully marked), or red (static/ROM).
|
||||
|
||||
The three-phase cycle (root scan, incremental marking, sweep) runs
|
||||
in small steps between VM instructions to avoid long pauses. Write
|
||||
barriers (`mrb_field_write_barrier`, `mrb_write_barrier`) maintain
|
||||
correctness during incremental marking.
|
||||
|
||||
The GC arena protects newly created objects in C code. Heap regions
|
||||
(`mrb_gc_add_region`) support embedded systems with fixed memory banks.
|
||||
|
||||
See [gc.md](gc.md) for detailed GC internals,
|
||||
[../guides/gc-arena-howto.md](../guides/gc-arena-howto.md) for arena
|
||||
usage patterns, [../guides/memory.md](../guides/memory.md) for memory
|
||||
management.
|
||||
|
||||
## Compiler Pipeline
|
||||
|
||||
The compiler transforms Ruby source code through three stages:
|
||||
|
||||
1. **Parser** (`parse.y`): Lrama/Bison grammar produces an AST of
|
||||
`mrb_ast_node` structures, tracking lexer state and local scopes.
|
||||
2. **Code Generator** (`codegen.c`): walks the AST and emits bytecode
|
||||
into `mrb_irep` structures (instruction sequence, literal pool,
|
||||
symbol table, child ireps).
|
||||
3. **Execution**: the irep is wrapped in an `RProc` and executed by
|
||||
the VM, or serialized to `.mrb` binary format.
|
||||
|
||||
Alternative loading paths include `mrb_load_string()` (compile and
|
||||
run), `mrb_load_irep()` (load precompiled bytecode), and `mrbc`
|
||||
(ahead-of-time compilation).
|
||||
|
||||
See [compiler.md](compiler.md) for detailed compiler internals,
|
||||
[opcode.md](opcode.md) for the instruction set.
|
||||
|
||||
## Source File Map
|
||||
|
||||
### Core (`src/`)
|
||||
|
||||
| File | Responsibility |
|
||||
| ---- | -------------- |
|
||||
| `vm.c` | Bytecode dispatch loop, method invocation |
|
||||
| `state.c` | `mrb_state` init/close, irep management |
|
||||
| `gc.c` | Garbage collector (mark-sweep, incremental) |
|
||||
| `class.c` | Class/module definition, method tables |
|
||||
| `object.c` | Core object operations |
|
||||
| `variable.c` | Instance/class/global variables, object shapes |
|
||||
| `proc.c` | Proc/Lambda/closure handling |
|
||||
| `array.c` | Array implementation |
|
||||
| `string.c` | String implementation (embedded, shared, heap) |
|
||||
| `hash.c` | Hash implementation (open addressing) |
|
||||
| `numeric.c` | Integer/Float arithmetic |
|
||||
| `symbol.c` | Symbol table and interning |
|
||||
| `range.c` | Range implementation |
|
||||
| `error.c` | Exception creation, raise, backtrace |
|
||||
| `kernel.c` | Kernel module methods |
|
||||
| `load.c` | `.mrb` bytecode loading |
|
||||
| `dump.c` | Bytecode serialization (write `.mrb`) |
|
||||
| `print.c` | Print/puts/p output |
|
||||
| `backtrace.c` | Stack trace generation |
|
||||
|
||||
### Compiler (`mrbgems/mruby-compiler/core/`)
|
||||
|
||||
| File | Responsibility |
|
||||
| ---- | -------------- |
|
||||
| `parse.y` | Yacc grammar → AST |
|
||||
| `y.tab.c` | Generated parser (from parse.y) |
|
||||
| `codegen.c` | AST → bytecode (irep) |
|
||||
| `node.h` | AST node type definitions |
|
||||
|
||||
### Key Headers (`include/mruby/`)
|
||||
|
||||
| Header | Contents |
|
||||
| ------ | -------- |
|
||||
| `mruby.h` | `mrb_state`, core API declarations |
|
||||
| `value.h` | `mrb_value`, type enums, value macros |
|
||||
| `object.h` | `RBasic`, `RObject`, object header |
|
||||
| `class.h` | `RClass`, method table types |
|
||||
| `string.h` | `RString`, string macros |
|
||||
| `array.h` | `RArray`, array macros |
|
||||
| `hash.h` | `RHash`, hash API |
|
||||
| `data.h` | `RData`, C data wrapping |
|
||||
| `irep.h` | `mrb_irep`, bytecode structures |
|
||||
| `compile.h` | Compiler context, `mrb_load_string` |
|
||||
| `boxing_*.h` | Value boxing implementations |
|
||||
|
||||
## mrbgems System
|
||||
|
||||
Gems are the module system for mruby. Each gem lives in
|
||||
`mrbgems/mruby-*/` and contains:
|
||||
|
||||
```text
|
||||
mruby-example/
|
||||
├── mrbgem.rake gem specification (name, deps, bins)
|
||||
├── src/ C source files
|
||||
├── mrblib/ Ruby source files (compiled to bytecode)
|
||||
├── include/ C headers
|
||||
├── test/ mrbtest test files
|
||||
└── bintest/ binary test files (CRuby)
|
||||
```
|
||||
|
||||
At build time, gem Ruby files are compiled with `mrbc` and linked into
|
||||
`libmruby.a`. Gem initialization runs in dependency order via
|
||||
`gem_init.c` (auto-generated).
|
||||
|
||||
GemBoxes (`mrbgems/*.gembox`) define named collections of gems
|
||||
(e.g., `default.gembox` includes `stdlib`, `stdlib-ext`, `stdlib-io`,
|
||||
`math`, `metaprog`, and binary tools).
|
||||
+2
-44
@@ -26,30 +26,8 @@ The Word boxing packing bit patterns are like following:
|
||||
| undef | `00000000 00000000 00000000 00010100` |
|
||||
| symbol | `xxxxxxxx xxxxxxxx xxxxxxxx xxxxxx10` |
|
||||
|
||||
### Inline Float (64-bit)
|
||||
|
||||
On 64-bit platforms, `double` values are packed into the word using
|
||||
rotation encoding. The IEEE 754 exponent field is rotated so that
|
||||
common exponent values (those not colliding with the pointer/tag
|
||||
patterns above) fit directly in a word. This encoding is lossless
|
||||
for most float values; only a small set of exotic exponents require
|
||||
heap allocation as `RFloat`.
|
||||
|
||||
To disable inline float and heap-allocate all floats, define
|
||||
`MRB_WORDBOX_NO_INLINE_FLOAT`.
|
||||
|
||||
### 32-bit Considerations
|
||||
|
||||
On 32-bit platforms with 64-bit `double` (the common case),
|
||||
`MRB_WORDBOX_NO_INLINE_FLOAT` is automatically defined because a
|
||||
64-bit double cannot fit in a 32-bit word. All floats are
|
||||
heap-allocated as `RFloat` objects.
|
||||
|
||||
The `RFloat` struct uses a `char[]` buffer instead of a `double`
|
||||
field to avoid alignment issues, since GC heap slots (RVALUE) on
|
||||
32-bit have only 4-byte alignment but `double` requires 8-byte.
|
||||
Accessor functions `mrb_rfloat_value()` and `mrb_rfloat_set()` use
|
||||
`memcpy` for safe access.
|
||||
On 64-bit platforms (unless `MRB_WORDBOX_NO_FLOAT_TRUNCATE`), float values are also packed in the `mrb_value`. In that case, we drop least significant 2 bits from mantissa.
|
||||
If you need full precision for floating-point numbers, define `MRB_WORDBOX_NO_FLOAT_TRUNCATE`.
|
||||
|
||||
## NaN Boxing
|
||||
|
||||
@@ -74,23 +52,3 @@ The object values appear far more frequently than floating-point numbers, so we
|
||||
## No Boxing
|
||||
|
||||
No boxing represents `mrb_value` by the C struct with `type` and the value union. This is the most portable (but inefficient) representation. No boxing can be specified by `MRB_NO_BOXING`, and it's default for debugging configuration (e.g. `host-debug`).
|
||||
|
||||
## Comparison
|
||||
|
||||
| Property | Word Boxing | NaN Boxing | No Boxing |
|
||||
| ---------------------- | ------------------ | ------------------ | -------------------- |
|
||||
| `mrb_value` size | 1 word (4/8 byte) | 8 bytes | 2 words (8/16 bytes) |
|
||||
| Default on | most platforms | (manual opt-in) | `host-debug` |
|
||||
| Macro | `MRB_WORD_BOXING` | `MRB_NAN_BOXING` | `MRB_NO_BOXING` |
|
||||
| Inline integers | yes (31/63 bit) | yes (32 bit) | yes (full width) |
|
||||
| Inline floats (64-bit) | yes (rotation) | yes (native) | yes (struct field) |
|
||||
| Inline floats (32-bit) | no (heap RFloat) | yes (native) | yes (struct field) |
|
||||
| Pointer size limit | none | 48 bits | none |
|
||||
| Debugger friendly | no | no | yes |
|
||||
|
||||
## ABI Compatibility
|
||||
|
||||
The boxing mode changes the layout of `mrb_value`. Code compiled with
|
||||
one boxing mode **cannot** be linked against a library built with a
|
||||
different mode. Always use `mruby-config --cflags` to get the correct
|
||||
compiler flags.
|
||||
|
||||
@@ -1,323 +0,0 @@
|
||||
<!-- summary: Compiler Pipeline Internals -->
|
||||
|
||||
# Compiler Pipeline Internals
|
||||
|
||||
This document describes mruby's compilation pipeline for developers
|
||||
working on the parser, code generator, or bytecode format.
|
||||
|
||||
**Read this if you are:** adding new syntax or modifying the parser,
|
||||
debugging codegen issues (wrong registers, missing opcodes),
|
||||
working with the `.mrb` binary format, or understanding how Ruby
|
||||
constructs map to bytecode.
|
||||
|
||||
## Pipeline Overview
|
||||
|
||||
```text
|
||||
Ruby source
|
||||
|
|
||||
v
|
||||
Lexer/Parser (parse.y)
|
||||
|
|
||||
v
|
||||
AST (mrb_ast_node)
|
||||
|
|
||||
v
|
||||
Code Generator (codegen.c)
|
||||
|
|
||||
v
|
||||
Bytecode (mrb_irep)
|
||||
|
|
||||
v
|
||||
VM execution -or- .mrb binary file
|
||||
```
|
||||
|
||||
## Stage 1: Lexer and Parser
|
||||
|
||||
The lexer and parser are combined in a single Lrama/Bison grammar
|
||||
file: `mrbgems/mruby-compiler/core/parse.y`.
|
||||
|
||||
### Parser State
|
||||
|
||||
The parser maintains extensive state in `mrb_parser_state`:
|
||||
|
||||
- **lstate**: current lexer state (EXPR\_BEG, EXPR\_END, EXPR\_ARG,
|
||||
EXPR\_DOT, EXPR\_FNAME, etc.). Controls how tokens like `+`/`-`
|
||||
are interpreted (sign vs operator) and whether newlines are
|
||||
significant.
|
||||
- **locals**: stack of local variable lists (one per scope), stored
|
||||
as cons-lists of symbols.
|
||||
- **lex\_strterm**: string/heredoc parsing state for handling nested
|
||||
interpolation.
|
||||
- **cond\_stack**, **cmdarg\_stack**: bit stacks tracking
|
||||
conditional and command argument contexts.
|
||||
- **tree**: root AST node after successful parse.
|
||||
- **error\_buffer**: accumulated parse errors.
|
||||
|
||||
### AST Nodes
|
||||
|
||||
The parser produces an AST using two node types:
|
||||
|
||||
- **Cons-list nodes**: traditional binary tree pairs (car/cdr)
|
||||
- **Variable-sized nodes**: have a header with `node_type`, `lineno`,
|
||||
and `filename_index`
|
||||
|
||||
Key node types include `NODE_SCOPE` (new variable scope),
|
||||
`NODE_STMTS` (statement sequence), `NODE_IF`, `NODE_WHILE`,
|
||||
`NODE_CALL` (method call), `NODE_DEF` (method definition),
|
||||
`NODE_CLASS`, `NODE_RESCUE`, `NODE_ENSURE`, etc. See
|
||||
`mrbgems/mruby-compiler/core/node.h` for the complete list.
|
||||
|
||||
### Local Variable Tracking
|
||||
|
||||
Local variables are tracked per-scope during parsing:
|
||||
|
||||
- `local_add(sym)`: register a new local variable in current scope
|
||||
- `local_var_p(sym)`: check if a symbol is a local variable (affects
|
||||
whether an identifier is parsed as a method call or variable
|
||||
reference)
|
||||
|
||||
## Stage 2: Code Generator
|
||||
|
||||
The code generator (`mrbgems/mruby-compiler/core/codegen.c`) walks
|
||||
the AST and emits bytecode into `mrb_irep` structures.
|
||||
|
||||
### Codegen Scope
|
||||
|
||||
Each lexical scope (method, block, class body) has its own
|
||||
`codegen_scope`:
|
||||
|
||||
```text
|
||||
codegen_scope
|
||||
+-- sp current register index (stack pointer)
|
||||
+-- pc current instruction count
|
||||
+-- nlocals number of local variables
|
||||
+-- nregs maximum register index used
|
||||
+-- lv local variable list
|
||||
+-- iseq[] instruction sequence (grows dynamically)
|
||||
+-- pool[] literal pool (strings, numbers)
|
||||
+-- syms[] symbol table (method/variable names)
|
||||
+-- reps[] child ireps (nested methods/blocks)
|
||||
+-- catch_table[] exception handler entries
|
||||
+-- loop current loop context stack
|
||||
+-- prev parent scope
|
||||
+-- mscope true if method/module/class scope
|
||||
```
|
||||
|
||||
Scopes nest for blocks, method definitions, and class/module bodies.
|
||||
Each scope produces one `mrb_irep`.
|
||||
|
||||
### Register Allocation
|
||||
|
||||
The code generator uses a simple stack-based register allocator:
|
||||
|
||||
- Register 0 is always `self`
|
||||
- Registers 1..nlocals-1 are local variables (in declaration order)
|
||||
- Registers nlocals..nregs-1 are temporaries
|
||||
|
||||
`push()` increments `sp` and tracks the high-water mark in `nregs`.
|
||||
`pop()` decrements `sp`. The allocator is linear - it does not
|
||||
reuse temporaries within an expression.
|
||||
|
||||
### Instruction Emission
|
||||
|
||||
Instructions are emitted via helper functions:
|
||||
|
||||
- `genop_0(opcode)`: no operands
|
||||
- `genop_1(opcode, a)`: one operand (auto-extends with OP\_EXT1
|
||||
if a > 255)
|
||||
- `genop_2(opcode, a, b)`: two operands (auto-extends with
|
||||
OP\_EXT1/2/3 as needed)
|
||||
- `genop_3(opcode, a, b, c)`: three operands
|
||||
- `genop_W(opcode, a)`: 24-bit operand
|
||||
- `genop_2S(opcode, a, b)`: one 8-bit + one 16-bit operand
|
||||
|
||||
### Peephole Optimization
|
||||
|
||||
The code generator performs limited peephole optimizations, such as
|
||||
removing redundant `OP_MOVE` instructions and combining consecutive
|
||||
literal loads. Optimization is disabled at jump targets and when
|
||||
`no_optimize` is set in the compilation context.
|
||||
|
||||
### Loop Context
|
||||
|
||||
Loop constructs (`while`, `until`, `for`, blocks) push a
|
||||
`loopinfo` structure that tracks jump destinations:
|
||||
|
||||
- `pc0`: destination for `next`
|
||||
- `pc1`: destination for `redo`
|
||||
- `pc2`: destination for `break`
|
||||
|
||||
Loop types (`LOOP_NORMAL`, `LOOP_BLOCK`, `LOOP_FOR`, `LOOP_BEGIN`,
|
||||
`LOOP_RESCUE`) determine how `break`/`next`/`redo` behave.
|
||||
|
||||
## IRep Structure
|
||||
|
||||
The compiled bytecode is stored in `mrb_irep` (Instruction
|
||||
REPresentation):
|
||||
|
||||
```text
|
||||
mrb_irep
|
||||
+-- iseq[] instruction sequence (mrb_code array)
|
||||
+-- pool[] literal pool (mrb_irep_pool entries)
|
||||
+-- syms[] symbol table (mrb_sym array)
|
||||
+-- reps[] child ireps (nested scopes)
|
||||
+-- lv[] local variable names (for debugging)
|
||||
+-- nlocals local variable count
|
||||
+-- nregs register count (locals + temporaries)
|
||||
+-- ilen instruction count
|
||||
+-- plen pool entry count
|
||||
+-- slen symbol count
|
||||
+-- rlen child irep count
|
||||
+-- clen catch handler count
|
||||
+-- debug_info source file/line mapping
|
||||
```
|
||||
|
||||
### Literal Pool
|
||||
|
||||
Pool entries store constants referenced by instructions:
|
||||
|
||||
| Type | Tag | Description |
|
||||
| ---- | --- | ----------- |
|
||||
| `IREP_TT_STR` | 0 | Dynamic string (heap allocated) |
|
||||
| `IREP_TT_SSTR` | 2 | Static string (read-only) |
|
||||
| `IREP_TT_INT32` | 1 | 32-bit integer |
|
||||
| `IREP_TT_INT64` | 3 | 64-bit integer |
|
||||
| `IREP_TT_FLOAT` | 5 | Floating-point number |
|
||||
| `IREP_TT_BIGINT` | 7 | Arbitrary-precision integer |
|
||||
|
||||
The code generator deduplicates pool entries: identical strings
|
||||
and equal numeric values share the same pool index.
|
||||
|
||||
### Catch Handler Table
|
||||
|
||||
Exception handler entries are appended after the instruction
|
||||
sequence in memory:
|
||||
|
||||
```text
|
||||
mrb_irep_catch_handler
|
||||
+-- type MRB_CATCH_RESCUE (0) or MRB_CATCH_ENSURE (1)
|
||||
+-- begin[4] start PC of protected range
|
||||
+-- end[4] end PC of protected range
|
||||
+-- target[4] jump target when handler fires
|
||||
```
|
||||
|
||||
During exception unwinding, handlers are searched in reverse order
|
||||
(last to first) for the current PC.
|
||||
|
||||
## Operand Encoding
|
||||
|
||||
Standard instructions use 8-bit operands. When a value exceeds
|
||||
255, extension prefixes widen operands to 16 bits:
|
||||
|
||||
| Prefix | Effect |
|
||||
| ------ | ------ |
|
||||
| `OP_EXT1` | First operand (a) becomes 16-bit |
|
||||
| `OP_EXT2` | Second operand (b) becomes 16-bit |
|
||||
| `OP_EXT3` | Both a and b become 16-bit |
|
||||
|
||||
Instruction formats:
|
||||
|
||||
| Format | Layout | Size |
|
||||
| ------ | ------ | ---- |
|
||||
| Z | opcode only | 1 byte |
|
||||
| B | opcode + a(8) | 2 bytes |
|
||||
| BB | opcode + a(8) + b(8) | 3 bytes |
|
||||
| BBB | opcode + a(8) + b(8) + c(8) | 4 bytes |
|
||||
| BS | opcode + a(8) + b(16) | 4 bytes |
|
||||
| BSS | opcode + a(8) + b(16) + c(16) | 6 bytes |
|
||||
| S | opcode + a(16) | 3 bytes |
|
||||
| W | opcode + a(24) | 4 bytes |
|
||||
|
||||
See [opcode.md](opcode.md) for the full instruction table.
|
||||
|
||||
## OP\_ENTER: Argument Specification
|
||||
|
||||
`OP_ENTER` encodes a method's argument layout in a 24-bit value
|
||||
(W format). The bit fields are defined by the `MRB_ARGS_*` macros:
|
||||
|
||||
```text
|
||||
Bits 23 no-block flag
|
||||
Bits 18-22 required argument count (5 bits, 0-31)
|
||||
Bits 13-17 optional argument count (5 bits, 0-31)
|
||||
Bit 12 rest argument flag (*args)
|
||||
Bits 7-11 post-rest argument count (5 bits, 0-31)
|
||||
Bits 2-6 keyword argument count (5 bits, 0-31)
|
||||
Bit 1 keyword rest flag (**kwargs)
|
||||
Bit 0 block argument flag (&block)
|
||||
```
|
||||
|
||||
Example: `def foo(a, b=1, *rest, &block)` produces an aspec with
|
||||
1 required, 1 optional, rest flag set, and block flag set.
|
||||
|
||||
## Presym: Compile-Time Symbols
|
||||
|
||||
The presym system pre-allocates symbol IDs at build time for
|
||||
frequently used method names and operators. This avoids runtime
|
||||
string interning for common symbols.
|
||||
|
||||
Generated by `lib/mruby/presym.rb`, the presym table maps symbol
|
||||
names to compile-time constants:
|
||||
|
||||
| Macro | Example | Symbol |
|
||||
| ----- | ------- | ------ |
|
||||
| `MRB_SYM(name)` | `MRB_SYM(initialize)` | `:initialize` |
|
||||
| `MRB_SYM_B(name)` | `MRB_SYM_B(map)` | `:map!` |
|
||||
| `MRB_SYM_Q(name)` | `MRB_SYM_Q(nil)` | `:nil?` |
|
||||
| `MRB_SYM_E(name)` | `MRB_SYM_E(name)` | `:name=` |
|
||||
| `MRB_OPSYM(op)` | `MRB_OPSYM(add)` | `:+` |
|
||||
| `MRB_IVSYM(name)` | `MRB_IVSYM(name)` | `:@name` |
|
||||
| `MRB_CVSYM(name)` | `MRB_CVSYM(count)` | `:@@count` |
|
||||
| `MRB_GVSYM(name)` | `MRB_GVSYM(stdout)` | `:$stdout` |
|
||||
|
||||
## Binary Format (.mrb)
|
||||
|
||||
Precompiled bytecode is stored in the RITE binary format:
|
||||
|
||||
```text
|
||||
Header: "RITE" magic + version ("0400") + CRC + size
|
||||
Section IREP: instruction sequences, pools, symbols
|
||||
Section DBG: debug info (optional, filename/line mapping)
|
||||
Section LVAR: local variable names (optional)
|
||||
Footer: "END\0"
|
||||
```
|
||||
|
||||
Loading functions:
|
||||
|
||||
- `mrb_load_irep(mrb, bin)`: load and execute from byte array
|
||||
- `mrb_load_irep_buf(mrb, buf, len)`: load with explicit size
|
||||
(safer)
|
||||
- `mrb_read_irep(mrb, bin)`: load without executing (returns
|
||||
`mrb_irep*`)
|
||||
- `mrb_load_irep_file(mrb, fp)`: load from file
|
||||
|
||||
The `mrbc` command-line tool performs ahead-of-time compilation:
|
||||
|
||||
```shell
|
||||
mrbc -o output.mrb source.rb # binary format
|
||||
mrbc -Boutput source.rb # C array format
|
||||
```
|
||||
|
||||
## Compilation Limits
|
||||
|
||||
| Limit | Value |
|
||||
| ----- | ----- |
|
||||
| Max nesting depth | 256 (`MRB_CODEGEN_LEVEL_MAX`) |
|
||||
| Max local variables | 255 (uint16 `nlocals`) |
|
||||
| Max symbols per irep | 65535 |
|
||||
| Max operand (standard) | 255 (8-bit) |
|
||||
| Max operand (extended) | 65535 (16-bit) |
|
||||
|
||||
## Source Files
|
||||
|
||||
| File | Contents |
|
||||
| ---- | -------- |
|
||||
| `mrbgems/mruby-compiler/core/parse.y` | Lrama/Bison grammar |
|
||||
| `mrbgems/mruby-compiler/core/y.tab.c` | Generated parser |
|
||||
| `mrbgems/mruby-compiler/core/codegen.c` | Code generator |
|
||||
| `mrbgems/mruby-compiler/core/node.h` | AST node types |
|
||||
| `include/mruby/irep.h` | IRep structure definition |
|
||||
| `include/mruby/compile.h` | Compiler context API |
|
||||
| `include/mruby/ops.h` | Opcode definitions |
|
||||
| `src/load.c` | Binary format loader |
|
||||
| `src/dump.c` | Binary format writer |
|
||||
| `lib/mruby/presym.rb` | Presym table generator |
|
||||
@@ -1,357 +0,0 @@
|
||||
<!-- summary: Garbage Collector Internals -->
|
||||
|
||||
# Garbage Collector Internals
|
||||
|
||||
This document describes the internals of mruby's garbage collector
|
||||
for developers working on `src/gc.c` and related code.
|
||||
|
||||
**Read this if you are:** modifying core data structures that hold
|
||||
object references (and need to add write barriers), debugging
|
||||
memory leaks or GC-related crashes, tuning GC performance for an
|
||||
embedded target, or working on the GC code itself.
|
||||
|
||||
**For user-facing GC docs**, see
|
||||
[gc-arena-howto.md](../guides/gc-arena-howto.md) (arena usage in C
|
||||
extensions) and [memory.md](../guides/memory.md) (heap regions).
|
||||
|
||||
## Overview
|
||||
|
||||
mruby uses a **tri-color incremental mark-and-sweep** garbage collector
|
||||
with an optional **generational mode**. The collector runs in small
|
||||
incremental steps between VM instruction execution, avoiding long
|
||||
pauses.
|
||||
|
||||
## Tri-Color Model
|
||||
|
||||
Every heap-allocated object has a color stored in
|
||||
`RBasic::gc_color` (3 bits):
|
||||
|
||||
| Color | Value | Meaning |
|
||||
| ----- | ----- | ------- |
|
||||
| White (A or B) | 1 or 2 | Unmarked, candidate for collection |
|
||||
| Gray | 0 | Marked, but children not yet scanned |
|
||||
| Black | 4 | Fully marked and scanned |
|
||||
| Red | 7 | Static/ROM object, never collected |
|
||||
|
||||
The GC uses two white types (A and B) in a flip-flop scheme. At the
|
||||
start of each GC cycle, the meaning of "current white" is flipped by
|
||||
XORing the white bits. This avoids recoloring all live objects at
|
||||
cycle boundaries, which is an O(1) operation instead of O(n).
|
||||
|
||||
```c
|
||||
#define is_dead(s, o) \
|
||||
(((o)->gc_color & other_white_part(s) & GC_WHITES) || \
|
||||
(o)->tt == MRB_TT_FREE)
|
||||
```
|
||||
|
||||
An object is dead if it still carries the previous cycle's white color.
|
||||
|
||||
## Heap Structure
|
||||
|
||||
### Heap Pages
|
||||
|
||||
Objects are allocated from fixed-size heap pages:
|
||||
|
||||
```text
|
||||
mrb_heap_page
|
||||
+-- freelist linked list of free slots
|
||||
+-- next next page in heap list
|
||||
+-- free_next next page with free slots
|
||||
+-- old old generation flag (generational mode)
|
||||
+-- region true if carved from a contiguous region
|
||||
+-- objects[MRB_HEAP_PAGE_SIZE] RVALUE array (default 1024)
|
||||
```
|
||||
|
||||
Each page holds `MRB_HEAP_PAGE_SIZE` objects (default 1024). On
|
||||
64-bit systems, a page is approximately 40 KB (40 bytes per slot).
|
||||
|
||||
### RVALUE Union
|
||||
|
||||
All mruby object types share the same slot size via a C union:
|
||||
|
||||
```text
|
||||
RVALUE = union of {
|
||||
RBasic, RObject, RClass, RString, RArray, RHash,
|
||||
RRange, RData, RProc, REnv, RFiber, RException, ...
|
||||
struct { RBasic header; RVALUE *next; } (free slot)
|
||||
}
|
||||
```
|
||||
|
||||
Free slots use the union space for a freelist pointer.
|
||||
|
||||
### Freelist
|
||||
|
||||
Each page maintains a singly-linked freelist of available slots.
|
||||
Allocation pops from the freelist; deallocation during sweep
|
||||
prepends to the freelist. The GC tracks pages with free slots in
|
||||
`gc->free_heaps` for fast allocation.
|
||||
|
||||
### Heap Regions
|
||||
|
||||
For embedded systems with fixed memory banks, `mrb_gc_add_region()`
|
||||
carves heap pages from a user-provided contiguous buffer:
|
||||
|
||||
```c
|
||||
static uint8_t heap_buf[64 * 1024];
|
||||
mrb_gc_add_region(mrb, heap_buf, sizeof(heap_buf));
|
||||
```
|
||||
|
||||
Region pages are never freed by the GC (even if all objects die).
|
||||
When region pages are exhausted, allocation falls back to `malloc()`.
|
||||
|
||||
## GC Phases
|
||||
|
||||
The GC operates as a three-state machine:
|
||||
|
||||
```text
|
||||
GC_STATE_ROOT --> GC_STATE_MARK --> GC_STATE_SWEEP --> GC_STATE_ROOT
|
||||
```
|
||||
|
||||
### Root Scan (GC\_STATE\_ROOT)
|
||||
|
||||
Marks objects directly reachable from the VM:
|
||||
|
||||
1. Global variables (`mrb_gc_mark_gv`)
|
||||
2. GC arena (`gc->arena[0..arena_idx-1]`)
|
||||
3. All built-in classes (Object, Class, Module, etc.)
|
||||
4. Top-level self (`mrb->top_self`)
|
||||
5. Current exception (`mrb->exc`)
|
||||
6. Execution contexts (VM stacks, call info stacks, active fibers)
|
||||
7. Task queues (if `MRB_USE_TASK_SCHEDULER` is defined)
|
||||
|
||||
After root scanning, the white color is flipped.
|
||||
|
||||
### Incremental Marking (GC\_STATE\_MARK)
|
||||
|
||||
Gray objects are popped from the gray stack and their children
|
||||
marked. Each step processes a limited number of objects:
|
||||
|
||||
```text
|
||||
limit = (GC_STEP_SIZE / 100) * step_ratio
|
||||
```
|
||||
|
||||
With default `step_ratio = 200` and `GC_STEP_SIZE = 1024`, the
|
||||
limit is 2048 objects per step.
|
||||
|
||||
When the gray stack is exhausted, the final marking phase re-marks
|
||||
the arena and global variables to catch objects created during
|
||||
marking, then transitions to sweep.
|
||||
|
||||
### Sweep (GC\_STATE\_SWEEP)
|
||||
|
||||
Iterates through heap pages. For each object:
|
||||
|
||||
- If dead (previous cycle's white): call `obj_free()`, return
|
||||
slot to freelist
|
||||
- If alive: paint with current white for the next cycle
|
||||
|
||||
Sweep is also incremental: `gc->sweeps` tracks the current page
|
||||
position between steps.
|
||||
|
||||
## Gray Stack
|
||||
|
||||
The gray stack is a fixed-size array of object pointers:
|
||||
|
||||
```c
|
||||
struct RBasic *gray_stack[MRB_GRAY_STACK_SIZE]; /* default 1024 */
|
||||
size_t gray_stack_top;
|
||||
mrb_bool gray_overflow;
|
||||
```
|
||||
|
||||
When the stack overflows, `gray_overflow` is set to `TRUE`. During
|
||||
marking, `gc_gray_rescan()` scans the entire heap to find any gray
|
||||
objects that could not be pushed. This guarantees correctness at the
|
||||
cost of a full heap scan.
|
||||
|
||||
## Write Barriers
|
||||
|
||||
During incremental marking, a black (fully marked) object storing
|
||||
a reference to a white (unmarked) object creates a dangerous edge
|
||||
that could lead to premature collection. Write barriers prevent this.
|
||||
|
||||
### Field Write Barrier
|
||||
|
||||
Used when assigning a specific field:
|
||||
|
||||
```c
|
||||
mrb_field_write_barrier(mrb, parent, child);
|
||||
```
|
||||
|
||||
If `parent` is black and `child` is white:
|
||||
|
||||
- During marking or generational mode: paint `child` gray (add to
|
||||
gray stack for scanning)
|
||||
- During sweep: paint `parent` with current white (demote it for
|
||||
next cycle)
|
||||
|
||||
### General Write Barrier
|
||||
|
||||
Used when an object has been modified but the specific child is
|
||||
not known:
|
||||
|
||||
```c
|
||||
mrb_write_barrier(mrb, obj);
|
||||
```
|
||||
|
||||
Paints `obj` gray and pushes it onto the gray stack for re-scanning.
|
||||
|
||||
## GC Arena
|
||||
|
||||
The arena protects newly created objects from collection before
|
||||
they are stored in a reachable location. Every `mrb_obj_alloc()`
|
||||
automatically pushes the new object onto the arena.
|
||||
|
||||
C extensions must save and restore the arena index when creating
|
||||
many temporary objects to prevent arena overflow:
|
||||
|
||||
```c
|
||||
int ai = mrb_gc_arena_save(mrb);
|
||||
/* create temporary objects */
|
||||
mrb_gc_arena_restore(mrb, ai);
|
||||
```
|
||||
|
||||
### Fixed vs Dynamic Arena
|
||||
|
||||
- **Dynamic** (default): arena grows by 1.5x when full. Risk of
|
||||
unbounded growth if arena is not properly managed.
|
||||
- **Fixed** (`MRB_GC_FIXED_ARENA`): raises an exception on overflow.
|
||||
Arena size is `MRB_GC_ARENA_SIZE` (default 100).
|
||||
|
||||
### Permanent Registration
|
||||
|
||||
For long-lived C objects that must survive indefinitely:
|
||||
|
||||
```c
|
||||
mrb_gc_register(mrb, obj); /* add to permanent root */
|
||||
mrb_gc_unregister(mrb, obj); /* remove from root */
|
||||
```
|
||||
|
||||
These store objects in a global array that is always marked as
|
||||
part of the root set.
|
||||
|
||||
See [gc-arena-howto.md](../guides/gc-arena-howto.md) for detailed
|
||||
usage patterns.
|
||||
|
||||
## Generational Mode
|
||||
|
||||
When enabled (default, unless `MRB_GC_TURN_OFF_GENERATIONAL` is
|
||||
defined), the GC classifies objects into young and old generations.
|
||||
|
||||
### Minor GC
|
||||
|
||||
Only processes young objects. Pages where all objects are old are
|
||||
marked with `page->old = TRUE` and skipped entirely during sweep.
|
||||
Minor GC always runs to completion in a single step.
|
||||
|
||||
### Major GC
|
||||
|
||||
A full mark-and-sweep cycle that processes all objects. Triggered
|
||||
when `gc->live > gc->oldgen_threshold`. Major GC runs
|
||||
incrementally, like the non-generational collector.
|
||||
|
||||
After a major GC completes, the collector reverts to minor GC mode.
|
||||
The old-generation threshold is recalculated:
|
||||
|
||||
```text
|
||||
oldgen_threshold = live_after_mark * MAJOR_GC_INC_RATIO / 100
|
||||
```
|
||||
|
||||
With `MAJOR_GC_INC_RATIO = 120`, a major GC triggers when live
|
||||
objects exceed 120% of the last major GC's survivors.
|
||||
|
||||
### Transitioning Between Modes
|
||||
|
||||
```c
|
||||
mrb_gc_generational_mode_set(mrb, TRUE); /* enable */
|
||||
mrb_gc_generational_mode_set(mrb, FALSE); /* disable */
|
||||
```
|
||||
|
||||
From Ruby: `GC.generational_mode = true/false`.
|
||||
|
||||
## Object Allocation
|
||||
|
||||
`mrb_obj_alloc()` is the core allocation function:
|
||||
|
||||
1. If `MRB_GC_STRESS` is defined, run a full GC
|
||||
2. If `gc->live >= gc->threshold`, run `mrb_incremental_gc()`
|
||||
3. Ensure arena has space (`gc_arena_keep`)
|
||||
4. Pop an object from the freelist of `gc->free_heaps`
|
||||
5. If no free pages, allocate a new page (`add_heap`)
|
||||
6. Initialize the object (zero fill, set type and class)
|
||||
7. Paint with current white color
|
||||
8. Push onto arena (`gc_protect`)
|
||||
9. Increment `gc->live`
|
||||
|
||||
## Object Freeing
|
||||
|
||||
`obj_free()` performs type-specific cleanup:
|
||||
|
||||
- **Objects/Exceptions**: free instance variable tables
|
||||
- **Classes**: free method tables and instance variable tables
|
||||
- **Arrays**: free heap buffer (if not embedded/shared)
|
||||
- **Hashes**: free hash table
|
||||
- **Strings**: free heap buffer (if not embedded/shared)
|
||||
- **Data objects**: call user-provided `dfree` callback
|
||||
- **Procs**: decrement irep reference count
|
||||
- **Fibers**: free context (stacks)
|
||||
|
||||
The object's type is set to `MRB_TT_FREE` after freeing.
|
||||
|
||||
## Triggering GC
|
||||
|
||||
### Automatic
|
||||
|
||||
GC runs automatically when `gc->live >= gc->threshold` during
|
||||
object allocation. After each cycle:
|
||||
|
||||
```text
|
||||
threshold = (live_after_mark / 100) * interval_ratio
|
||||
minimum: GC_STEP_SIZE (1024)
|
||||
```
|
||||
|
||||
With default `interval_ratio = 200`, GC triggers when live objects
|
||||
roughly double.
|
||||
|
||||
### Manual
|
||||
|
||||
```c
|
||||
mrb_full_gc(mrb); /* force complete GC cycle */
|
||||
mrb_garbage_collect(mrb); /* public API wrapper */
|
||||
```
|
||||
|
||||
From Ruby: `GC.start`.
|
||||
|
||||
## Configuration
|
||||
|
||||
### Compile-Time
|
||||
|
||||
| Macro | Default | Description |
|
||||
| ----- | ------- | ----------- |
|
||||
| `MRB_HEAP_PAGE_SIZE` | 1024 | Objects per heap page |
|
||||
| `MRB_GRAY_STACK_SIZE` | 1024 | Gray stack capacity |
|
||||
| `MRB_GC_ARENA_SIZE` | 100 | Arena size (fixed mode) or initial size |
|
||||
| `MRB_GC_FIXED_ARENA` | off | Use fixed-size arena |
|
||||
| `MRB_GC_TURN_OFF_GENERATIONAL` | off | Disable generational mode |
|
||||
| `MRB_GC_STRESS` | off | Full GC on every allocation (debug) |
|
||||
| `MRB_USE_MALLOC_TRIM` | off | Call `malloc_trim()` after full GC |
|
||||
|
||||
### Runtime
|
||||
|
||||
From Ruby code:
|
||||
|
||||
```ruby
|
||||
GC.interval_ratio = 200 # threshold = live * ratio / 100
|
||||
GC.step_ratio = 200 # objects per incremental step
|
||||
GC.generational_mode = true
|
||||
GC.start # force full GC
|
||||
GC.enable # re-enable GC
|
||||
GC.disable # disable GC
|
||||
```
|
||||
|
||||
## Source Files
|
||||
|
||||
| File | Contents |
|
||||
| ---- | -------- |
|
||||
| `src/gc.c` | GC implementation (~1400 lines) |
|
||||
| `include/mruby/gc.h` | `mrb_gc` structure, public GC API |
|
||||
| `include/mruby.h` | Arena save/restore macros |
|
||||
+127
-195
@@ -1,206 +1,138 @@
|
||||
<!-- summary: About mruby Virtual Machine Instructions -->
|
||||
|
||||
# mruby Bytecode
|
||||
# The new bytecode
|
||||
|
||||
mruby uses 8-bit instruction opcodes. Each instruction is a single byte,
|
||||
allowing up to 256 opcodes. Instructions can take 0 to 3 operands.
|
||||
We will reimplement the VM to use 8bit instruction code. By
|
||||
bytecode, we mean real byte code. The whole purpose is
|
||||
reducing the memory consumption of mruby VM.
|
||||
|
||||
## Operands
|
||||
# Instructions
|
||||
|
||||
The size of operands can be either 8 bits, 16 bits, or 24 bits.
|
||||
In the instruction table below, the operand type field describes
|
||||
the size of each operand.
|
||||
Instructions are bytes. There can be 256 instructions. Currently, we
|
||||
have 106 instructions. Instructions can take 0 to 3 operands.
|
||||
|
||||
- `Z`: no operand
|
||||
- `B`: 8 bits
|
||||
- `S`: 16 bits
|
||||
- `W`: 24 bits
|
||||
## operands
|
||||
|
||||
If the first and second operands are of type `B` (8 bits), they may be
|
||||
extended to 16 bits by the operand extension instruction immediately
|
||||
The size of operands can be either 8bits, 16bits or 24bits.
|
||||
In the table.1 below, the third field describes the size
|
||||
of operands.
|
||||
|
||||
- B: 8bit
|
||||
- S: 16bit
|
||||
- W: 24bit
|
||||
|
||||
If the first and second operands are of type `B` (8bits), they may be
|
||||
extended to 16bits by the operand extension instruction immediately
|
||||
preceding them.
|
||||
See also `OP_EXT1`, `OP_EXT2` and `OP_EXT3`.
|
||||
|
||||
## Instruction Table
|
||||
## table.1 Instruction Table
|
||||
|
||||
| No. | Instruction Name | Operand type | Semantics |
|
||||
| --: | ---------------- | ------------ | ----------------------------------------------------------------- |
|
||||
| 0 | `OP_NOP` | `Z` | no operation |
|
||||
| 1 | `OP_MOVE` | `BB` | `R[a] = R[b]` |
|
||||
| 2 | `OP_LOADL` | `BB` | `R[a] = Pool[b]` |
|
||||
| 3 | `OP_LOADI8` | `BB` | `R[a] = mrb_int(b)` |
|
||||
| 4 | `OP_LOADINEG` | `BB` | `R[a] = mrb_int(-b)` |
|
||||
| 5 | `OP_LOADI__1` | `B` | `R[a] = mrb_int(-1)` |
|
||||
| 6 | `OP_LOADI_0` | `B` | `R[a] = mrb_int(0)` |
|
||||
| 7 | `OP_LOADI_1` | `B` | `R[a] = mrb_int(1)` |
|
||||
| 8 | `OP_LOADI_2` | `B` | `R[a] = mrb_int(2)` |
|
||||
| 9 | `OP_LOADI_3` | `B` | `R[a] = mrb_int(3)` |
|
||||
| 10 | `OP_LOADI_4` | `B` | `R[a] = mrb_int(4)` |
|
||||
| 11 | `OP_LOADI_5` | `B` | `R[a] = mrb_int(5)` |
|
||||
| 12 | `OP_LOADI_6` | `B` | `R[a] = mrb_int(6)` |
|
||||
| 13 | `OP_LOADI_7` | `B` | `R[a] = mrb_int(7)` |
|
||||
| 14 | `OP_LOADI16` | `BS` | `R[a] = mrb_int(b)` |
|
||||
| 15 | `OP_LOADI32` | `BSS` | `R[a] = mrb_int((b<<16)+c)` |
|
||||
| 16 | `OP_LOADSYM` | `BB` | `R[a] = Syms[b]` |
|
||||
| 17 | `OP_LOADNIL` | `B` | `R[a] = nil` |
|
||||
| 18 | `OP_LOADSELF` | `B` | `R[a] = self` |
|
||||
| 19 | `OP_LOADTRUE` | `B` | `R[a] = true` |
|
||||
| 20 | `OP_LOADFALSE` | `B` | `R[a] = false` |
|
||||
| 21 | `OP_GETGV` | `BB` | `R[a] = getglobal(Syms[b])` |
|
||||
| 22 | `OP_SETGV` | `BB` | `setglobal(Syms[b], R[a])` |
|
||||
| 23 | `OP_GETSV` | `BB` | `R[a] = Special[Syms[b]]` |
|
||||
| 24 | `OP_SETSV` | `BB` | `Special[Syms[b]] = R[a]` |
|
||||
| 25 | `OP_GETIV` | `BB` | `R[a] = ivget(Syms[b])` |
|
||||
| 26 | `OP_SETIV` | `BB` | `ivset(Syms[b],R[a])` |
|
||||
| 27 | `OP_GETCV` | `BB` | `R[a] = cvget(Syms[b])` |
|
||||
| 28 | `OP_SETCV` | `BB` | `cvset(Syms[b],R[a])` |
|
||||
| 29 | `OP_GETCONST` | `BB` | `R[a] = constget(Syms[b])` |
|
||||
| 30 | `OP_SETCONST` | `BB` | `constset(Syms[b],R[a])` |
|
||||
| 31 | `OP_GETMCNST` | `BB` | `R[a] = R[a]::Syms[b]` |
|
||||
| 32 | `OP_SETMCNST` | `BB` | `R[a+1]::Syms[b] = R[a]` |
|
||||
| 33 | `OP_GETUPVAR` | `BBB` | `R[a] = uvget(b,c)` |
|
||||
| 34 | `OP_SETUPVAR` | `BBB` | `uvset(b,c,R[a])` |
|
||||
| 35 | `OP_GETIDX` | `B` | `R[a] = R[a][R[a+1]]` |
|
||||
| 36 | `OP_GETIDX0` | `BB` | `R[a] = R[b][0]` |
|
||||
| 37 | `OP_SETIDX` | `B` | `R[a][R[a+1]] = R[a+2]` |
|
||||
| 38 | `OP_JMP` | `S` | `pc += a` |
|
||||
| 39 | `OP_JMPIF` | `BS` | `if R[a] pc += b` |
|
||||
| 40 | `OP_JMPNOT` | `BS` | `if !R[a] pc += b` |
|
||||
| 41 | `OP_JMPNIL` | `BS` | `if R[a]==nil pc += b` |
|
||||
| 42 | `OP_JMPUW` | `S` | `unwind_and_jump_to(a)` |
|
||||
| 43 | `OP_EXCEPT` | `B` | `R[a] = exc` |
|
||||
| 44 | `OP_RESCUE` | `BB` | `R[b] = R[a].isa?(R[b])` |
|
||||
| 45 | `OP_RAISEIF` | `B` | `raise(R[a]) if R[a]` |
|
||||
| 46 | `OP_MATCHERR` | `B` | `raise NoMatchingPatternError unless R[a]` |
|
||||
| 47 | `OP_SSEND` | `BBB` | `R[a] = self.send(Syms[b],R[a+1]..,R[a+n+1]:R[a+n+2]..) (c=n\ |
|
||||
| 48 | `OP_SSEND0` | `BB` | `R[a] = self.send(Syms[b])` (no args) |
|
||||
| 49 | `OP_SSENDB` | `BBB` | `R[a] = self.send(Syms[b],R[a+1]..,&R[a+n+2k+1])` |
|
||||
| 50 | `OP_SEND` | `BBB` | `R[a] = R[a].send(Syms[b],R[a+1]..,R[a+n+1]:R[a+n+2]..) (c=n\ |
|
||||
| 51 | `OP_SEND0` | `BB` | `R[a] = R[a].send(Syms[b])` (no args) |
|
||||
| 52 | `OP_SENDB` | `BBB` | `R[a] = R[a].send(Syms[b],R[a+1]..,&R[a+n+2k+1])` |
|
||||
| 53 | `OP_CALL` | `Z` | `self.call(*, **, &)` (tailcall) |
|
||||
| 54 | `OP_BLKCALL` | `BB` | `R[a] = R[a].call(R[a+1],...,R[a+b])` (direct block call) |
|
||||
| 55 | `OP_SUPER` | `BB` | `R[a] = super(R[a+1],...,R[a+b+1])` |
|
||||
| 56 | `OP_ARGARY` | `BS` | `R[a] = argument array (16=m5:r1:m5:d1:lv4)` |
|
||||
| 57 | `OP_ENTER` | `W` | `arg setup according to flags (24=n1:m5:o5:r1:m5:k5:d1:b1)` |
|
||||
| 58 | `OP_KEY_P` | `BB` | `R[a] = kdict.key?(Syms[b])` |
|
||||
| 59 | `OP_KEYEND` | `Z` | `raise unless kdict.empty?` |
|
||||
| 60 | `OP_KARG` | `BB` | `R[a] = kdict[Syms[b]]; kdict.delete(Syms[b])` |
|
||||
| 61 | `OP_RETURN` | `B` | `return R[a]` (normal) |
|
||||
| 62 | `OP_RETURN_BLK` | `B` | `return R[a]` (in-block return) |
|
||||
| 63 | `OP_RETSELF` | `Z` | `return self` |
|
||||
| 64 | `OP_RETNIL` | `Z` | `return nil` |
|
||||
| 65 | `OP_RETTRUE` | `Z` | `return true` |
|
||||
| 66 | `OP_RETFALSE` | `Z` | `return false` |
|
||||
| 67 | `OP_BREAK` | `B` | `break R[a]` |
|
||||
| 68 | `OP_BLKPUSH` | `BS` | `R[a] = block (16=m5:r1:m5:d1:lv4)` |
|
||||
| 69 | `OP_ADD` | `B` | `R[a] = R[a] + R[a+1]` |
|
||||
| 70 | `OP_ADDI` | `BB` | `R[a] = R[a] + mrb_int(b)` |
|
||||
| 71 | `OP_SUB` | `B` | `R[a] = R[a] - R[a+1]` |
|
||||
| 72 | `OP_SUBI` | `BB` | `R[a] = R[a] - mrb_int(b)` |
|
||||
| 73 | `OP_ADDILV` | `BBB` | `R[a] = R[a] + mrb_int(c)` (with local variable fallback) |
|
||||
| 74 | `OP_SUBILV` | `BBB` | `R[a] = R[a] - mrb_int(c)` (with local variable fallback) |
|
||||
| 75 | `OP_MUL` | `B` | `R[a] = R[a] * R[a+1]` |
|
||||
| 76 | `OP_DIV` | `B` | `R[a] = R[a] / R[a+1]` |
|
||||
| 77 | `OP_EQ` | `B` | `R[a] = R[a] == R[a+1]` |
|
||||
| 78 | `OP_LT` | `B` | `R[a] = R[a] < R[a+1]` |
|
||||
| 79 | `OP_LE` | `B` | `R[a] = R[a] <= R[a+1]` |
|
||||
| 80 | `OP_GT` | `B` | `R[a] = R[a] > R[a+1]` |
|
||||
| 81 | `OP_GE` | `B` | `R[a] = R[a] >= R[a+1]` |
|
||||
| 82 | `OP_ARRAY` | `BB` | `R[a] = ary_new(R[a],R[a+1]..R[a+b])` |
|
||||
| 83 | `OP_ARRAY2` | `BBB` | `R[a] = ary_new(R[b],R[b+1]..R[b+c])` |
|
||||
| 84 | `OP_ARYCAT` | `B` | `ary_cat(R[a],R[a+1])` |
|
||||
| 85 | `OP_ARYPUSH` | `BB` | `ary_push(R[a],R[a+1]..R[a+b])` |
|
||||
| 86 | `OP_ARYSPLAT` | `B` | `R[a] = ary_splat(R[a])` |
|
||||
| 87 | `OP_AREF` | `BBB` | `R[a] = R[b][c]` |
|
||||
| 88 | `OP_ASET` | `BBB` | `R[b][c] = R[a]` |
|
||||
| 89 | `OP_APOST` | `BBB` | `*R[a],R[a+1]..R[a+c] = R[a][b..]` |
|
||||
| 90 | `OP_INTERN` | `B` | `R[a] = intern(R[a])` |
|
||||
| 91 | `OP_SYMBOL` | `BB` | `R[a] = intern(Pool[b])` |
|
||||
| 92 | `OP_STRING` | `BB` | `R[a] = str_dup(Pool[b])` |
|
||||
| 93 | `OP_STRCAT` | `B` | `str_cat(R[a],R[a+1])` |
|
||||
| 94 | `OP_HASH` | `BB` | `R[a] = hash_new(R[a],R[a+1]..R[a+b*2-1])` |
|
||||
| 95 | `OP_HASHADD` | `BB` | `hash_push(R[a],R[a+1]..R[a+b*2])` |
|
||||
| 96 | `OP_HASHCAT` | `B` | `R[a] = hash_cat(R[a],R[a+1])` |
|
||||
| 97 | `OP_LAMBDA` | `BB` | `R[a] = lambda(Irep[b],L_LAMBDA)` |
|
||||
| 98 | `OP_BLOCK` | `BB` | `R[a] = lambda(Irep[b],L_BLOCK)` |
|
||||
| 99 | `OP_METHOD` | `BB` | `R[a] = lambda(Irep[b],L_METHOD)` |
|
||||
| 100 | `OP_RANGE_INC` | `B` | `R[a] = range_new(R[a],R[a+1],FALSE)` |
|
||||
| 101 | `OP_RANGE_EXC` | `B` | `R[a] = range_new(R[a],R[a+1],TRUE)` |
|
||||
| 102 | `OP_OCLASS` | `B` | `R[a] = ::Object` |
|
||||
| 103 | `OP_CLASS` | `BB` | `R[a] = newclass(R[a],Syms[b],R[a+1])` |
|
||||
| 104 | `OP_MODULE` | `BB` | `R[a] = newmodule(R[a],Syms[b])` |
|
||||
| 105 | `OP_EXEC` | `BB` | `R[a] = blockexec(R[a],Irep[b])` |
|
||||
| 106 | `OP_DEF` | `BB` | `R[a].newmethod(Syms[b],R[a+1]); R[a] = Syms[b]` |
|
||||
| 107 | `OP_TDEF` | `BBB` | `target_class.newmethod(Syms[b],Irep[c]); R[a] = Syms[b]` |
|
||||
| 108 | `OP_SDEF` | `BBB` | `R[a].singleton_class.newmethod(Syms[b],Irep[c]); R[a] = Syms[b]` |
|
||||
| 109 | `OP_ALIAS` | `BB` | `alias_method(target_class,Syms[a],Syms[b])` |
|
||||
| 110 | `OP_UNDEF` | `B` | `undef_method(target_class,Syms[a])` |
|
||||
| 111 | `OP_SCLASS` | `B` | `R[a] = R[a].singleton_class` |
|
||||
| 112 | `OP_TCLASS` | `B` | `R[a] = target_class` |
|
||||
| 113 | `OP_DEBUG` | `BBB` | `print a,b,c` |
|
||||
| 114 | `OP_ERR` | `B` | `raise(LocalJumpError, Pool[a])` |
|
||||
| 115 | `OP_EXT1` | `Z` | make 1st operand (a) 16 bit |
|
||||
| 116 | `OP_EXT2` | `Z` | make 2nd operand (b) 16 bit |
|
||||
| 117 | `OP_EXT3` | `Z` | make 1st and 2nd operands 16 bit |
|
||||
| 118 | `OP_STOP` | `Z` | stop VM |
|
||||
|
||||
## Notes
|
||||
|
||||
### OP_SEND0 / OP_SSEND0
|
||||
|
||||
These are optimized versions of `OP_SEND` / `OP_SSEND` for zero-argument
|
||||
method calls (no operand `c` needed).
|
||||
|
||||
### OP_RETSELF / OP_RETNIL / OP_RETTRUE / OP_RETFALSE
|
||||
|
||||
These are optimized return instructions that avoid loading a value into
|
||||
a register before returning. Common patterns like `attr_reader` methods
|
||||
(`return self.@x`) and predicate methods (`return true`/`return false`)
|
||||
benefit from these specialized opcodes.
|
||||
|
||||
### OP_BLKCALL
|
||||
|
||||
Direct block invocation that bypasses method dispatch. Used when calling
|
||||
a block argument directly (e.g., `yield` or `block.call`).
|
||||
|
||||
### OP_ADDILV / OP_SUBILV
|
||||
|
||||
Optimized integer increment/decrement that keeps operands for method
|
||||
call fallback when the receiver is not a Fixnum.
|
||||
|
||||
### OP_TDEF / OP_SDEF
|
||||
|
||||
Optimized method definition. `OP_TDEF` defines a method on the
|
||||
`target_class` directly from an irep without creating an intermediate
|
||||
`RProc`. `OP_SDEF` does the same for singleton methods.
|
||||
|
||||
### OP_MATCHERR
|
||||
|
||||
Raises `NoMatchingPatternError` when a pattern match fails. Used by
|
||||
the `case`/`in` pattern matching syntax.
|
||||
|
||||
### OP_GETIDX / OP_GETIDX0 / OP_SETIDX Optimization
|
||||
|
||||
These instructions optimize `[]` and `[]=` access for Array, Hash, and String.
|
||||
|
||||
**OP_GETIDX** uses direct function calls:
|
||||
|
||||
- `Array`: `mrb_ary_entry()` (integer index only)
|
||||
- `Hash`: `mrb_hash_get()`
|
||||
- `String`: `mrb_str_aref()` (integer/string/range index)
|
||||
|
||||
**OP_GETIDX0** is a specialized variant for index 0 (e.g., `ary[0]`).
|
||||
|
||||
**OP_SETIDX** uses direct function calls:
|
||||
|
||||
- `Array`: `mrb_ary_set()` (integer index only)
|
||||
- `Hash`: `mrb_hash_set()`
|
||||
|
||||
**Fallback to method dispatch** occurs when:
|
||||
|
||||
- Object is a subclass (e.g., `MyArray < Array`)
|
||||
- Object has a singleton class (singleton methods defined)
|
||||
- Index type is not supported (e.g., non-integer for Array)
|
||||
|
||||
This allows subclasses to override `[]`/`[]=` while base classes remain optimized.
|
||||
| No. | Instruction Name | Operand type | Semantics
|
||||
| --: | ---------------- | ------------ | ---------------
|
||||
| 0 | `OP_NOP` | `-` | `no operation`
|
||||
| 1 | `OP_MOVE` | `BB` | `R(a) = R(b)`
|
||||
| 2 | `OP_LOADL` | `BB` | `R(a) = Pool(b)`
|
||||
| 3 | `OP_LOADI8` | `BB` | `R(a) = mrb_int(b)`
|
||||
| 4 | `OP_LOADINEG` | `BB` | `R(a) = mrb_int(-b)`
|
||||
| 5 | `OP_LOADI__1` | `B` | `R(a) = mrb_int(-1)`
|
||||
| 6 | `OP_LOADI_0` | `B` | `R(a) = mrb_int(0)`
|
||||
| 7 | `OP_LOADI_1` | `B` | `R(a) = mrb_int(1)`
|
||||
| 8 | `OP_LOADI_2` | `B` | `R(a) = mrb_int(2)`
|
||||
| 9 | `OP_LOADI_3` | `B` | `R(a) = mrb_int(3)`
|
||||
| 10 | `OP_LOADI_4` | `B` | `R(a) = mrb_int(4)`
|
||||
| 11 | `OP_LOADI_5` | `B` | `R(a) = mrb_int(5)`
|
||||
| 12 | `OP_LOADI_6` | `B` | `R(a) = mrb_int(6)`
|
||||
| 13 | `OP_LOADI_7` | `B` | `R(a) = mrb_int(7)`
|
||||
| 14 | `OP_LOADI16` | `BS` | `R(a) = mrb_int(b)`
|
||||
| 15 | `OP_LOADI32` | `BSS` | `R(a) = mrb_int((b<<16)+c)`
|
||||
| 16 | `OP_LOADSYM` | `BB` | `R(a) = Syms(b)`
|
||||
| 17 | `OP_LOADNIL` | `B` | `R(a) = nil`
|
||||
| 18 | `OP_LOADSELF` | `B` | `R(a) = self`
|
||||
| 19 | `OP_LOADT` | `B` | `R(a) = true`
|
||||
| 20 | `OP_LOADF` | `B` | `R(a) = false`
|
||||
| 21 | `OP_GETGV` | `BB` | `R(a) = getglobal(Syms(b))`
|
||||
| 22 | `OP_SETGV` | `BB` | `setglobal(Syms(b), R(a))`
|
||||
| 23 | `OP_GETSV` | `BB` | `R(a) = Special[Syms(b)]`
|
||||
| 24 | `OP_SETSV` | `BB` | `Special[Syms(b)] = R(a)`
|
||||
| 25 | `OP_GETIV` | `BB` | `R(a) = ivget(Syms(b))`
|
||||
| 26 | `OP_SETIV` | `BB` | `ivset(Syms(b),R(a))`
|
||||
| 27 | `OP_GETCV` | `BB` | `R(a) = cvget(Syms(b))`
|
||||
| 28 | `OP_SETCV` | `BB` | `cvset(Syms(b),R(a))`
|
||||
| 29 | `OP_GETCONST` | `BB` | `R(a) = constget(Syms(b))`
|
||||
| 30 | `OP_SETCONST` | `BB` | `constset(Syms(b),R(a))`
|
||||
| 31 | `OP_GETMCNST` | `BB` | `R(a) = R(a)::Syms(b)`
|
||||
| 32 | `OP_SETMCNST` | `BB` | `R(a+1)::Syms(b) = R(a)`
|
||||
| 33 | `OP_GETUPVAR` | `BBB` | `R(a) = uvget(b,c)`
|
||||
| 34 | `OP_SETUPVAR` | `BBB` | `uvset(b,c,R(a))`
|
||||
| 35 | `OP_GETIDX` | `B` | `R(a) = R(a)[R(a+1)]`
|
||||
| 36 | `OP_SETIDX` | `B` | `R(a)[R(a+1)] = R(a+2)`
|
||||
| 37 | `OP_JMP` | `S` | `pc+=a`
|
||||
| 38 | `OP_JMPIF` | `BS` | `if R(a) pc+=b`
|
||||
| 39 | `OP_JMPNOT` | `BS` | `if !R(a) pc+=b`
|
||||
| 40 | `OP_JMPNIL` | `BS` | `if R(a)==nil pc+=b`
|
||||
| 41 | `OP_JMPUW` | `S` | `unwind_and_jump_to(a)`
|
||||
| 42 | `OP_EXCEPT` | `B` | `R(a) = exc`
|
||||
| 43 | `OP_RESCUE` | `BB` | `R(b) = R(a).isa?(R(b))`
|
||||
| 44 | `OP_RAISEIF` | `B` | `raise(R(a)) if R(a)`
|
||||
| 45 | `OP_SSEND` | `BBB` | `R(a) = self.send(Syms(b),R(a+1)..,R(a+n+1):R(a+n+2)..) (c=n\|k<<4)`
|
||||
| 46 | `OP_SSENDB` | `BBB` | `R(a) = self.send(Syms(b),R(a+1)..,R(a+n+1):R(a+n+2)..,&R(a+n+2k+1))`
|
||||
| 47 | `OP_SEND` | `BBB` | `R(a) = R(a).send(Syms(b),R(a+1)..,R(a+n+1):R(a+n+2)..) (c=n\|k<<4)`
|
||||
| 48 | `OP_SENDB` | `BBB` | `R(a) = R(a).send(Syms(b),R(a+1)..,R(a+n+1):R(a+n+2)..,&R(a+n+2k+1))`
|
||||
| 49 | `OP_CALL` | `-` | `self.call(*, **, &) (But overlay the current call frame; tailcall)`
|
||||
| 50 | `OP_SUPER` | `BB` | `R(a) = super(R(a+1),... ,R(a+b+1))`
|
||||
| 51 | `OP_ARGARY` | `BS` | `R(a) = argument array (16=m5:r1:m5:d1:lv4)`
|
||||
| 52 | `OP_ENTER` | `W` | `arg setup according to flags (23=m5:o5:r1:m5:k5:d1:b1)`
|
||||
| 53 | `OP_KEY_P` | `BB` | `R(a) = kdict.key?(Syms(b))`
|
||||
| 54 | `OP_KEYEND` | `-` | `raise unless kdict.empty?`
|
||||
| 55 | `OP_KARG` | `BB` | `R(a) = kdict[Syms(b)]; kdict.delete(Syms(b))`
|
||||
| 56 | `OP_RETURN` | `B` | `return R(a) (normal)`
|
||||
| 57 | `OP_RETURN_BLK` | `B` | `return R(a) (in-block return)`
|
||||
| 58 | `OP_BREAK` | `B` | `break R(a)`
|
||||
| 59 | `OP_BLKPUSH` | `BS` | `R(a) = block (16=m5:r1:m5:d1:lv4)`
|
||||
| 60 | `OP_ADD` | `B` | `R(a) = R(a)+R(a+1)`
|
||||
| 61 | `OP_ADDI` | `BB` | `R(a) = R(a)+mrb_int(b)`
|
||||
| 62 | `OP_SUB` | `B` | `R(a) = R(a)-R(a+1)`
|
||||
| 63 | `OP_SUBI` | `BB` | `R(a) = R(a)-mrb_int(b)`
|
||||
| 64 | `OP_MUL` | `B` | `R(a) = R(a)*R(a+1)`
|
||||
| 65 | `OP_DIV` | `B` | `R(a) = R(a)/R(a+1)`
|
||||
| 66 | `OP_EQ` | `B` | `R(a) = R(a)==R(a+1)`
|
||||
| 67 | `OP_LT` | `B` | `R(a) = R(a)<R(a+1)`
|
||||
| 68 | `OP_LE` | `B` | `R(a) = R(a)<=R(a+1)`
|
||||
| 69 | `OP_GT` | `B` | `R(a) = R(a)>R(a+1)`
|
||||
| 70 | `OP_GE` | `B` | `R(a) = R(a)>=R(a+1)`
|
||||
| 71 | `OP_ARRAY` | `BB` | `R(a) = ary_new(R(a),R(a+1)..R(a+b))`
|
||||
| 72 | `OP_ARRAY2` | `BBB` | `R(a) = ary_new(R(b),R(b+1)..R(b+c))`
|
||||
| 73 | `OP_ARYCAT` | `B` | `ary_cat(R(a),R(a+1))`
|
||||
| 74 | `OP_ARYPUSH` | `BB` | `ary_push(R(a),R(a+1)..R(a+b))`
|
||||
| 75 | `OP_ARYSPLAT` | `B` | `R(a) = ary_splat(R(a))`
|
||||
| 76 | `OP_AREF` | `BBB` | `R(a) = R(b)[c]`
|
||||
| 77 | `OP_ASET` | `BBB` | `R(b)[c] = R(a)`
|
||||
| 78 | `OP_APOST` | `BBB` | `*R(a),R(a+1)..R(a+c) = R(a)[b..]`
|
||||
| 79 | `OP_INTERN` | `B` | `R(a) = intern(R(a))`
|
||||
| 80 | `OP_SYMBOL` | `BB` | `R(a) = intern(Pool(b))`
|
||||
| 81 | `OP_STRING` | `BB` | `R(a) = str_dup(Pool(b))`
|
||||
| 82 | `OP_STRCAT` | `B` | `str_cat(R(a),R(a+1))`
|
||||
| 83 | `OP_HASH` | `BB` | `R(a) = hash_new(R(a),R(a+1)..R(a+b*2-1))`
|
||||
| 84 | `OP_HASHADD` | `BB` | `hash_push(R(a),R(a+1)..R(a+b*2))`
|
||||
| 85 | `OP_HASHCAT` | `B` | `R(a) = hash_cat(R(a),R(a+1))`
|
||||
| 86 | `OP_LAMBDA` | `BB` | `R(a) = lambda(Irep(b),OP_L_LAMBDA)`
|
||||
| 87 | `OP_BLOCK` | `BB` | `R(a) = lambda(Irep(b),OP_L_BLOCK)`
|
||||
| 88 | `OP_METHOD` | `BB` | `R(a) = lambda(Irep(b),OP_L_METHOD)`
|
||||
| 89 | `OP_RANGE_INC` | `B` | `R(a) = range_new(R(a),R(a+1),FALSE)`
|
||||
| 90 | `OP_RANGE_EXC` | `B` | `R(a) = range_new(R(a),R(a+1),TRUE)`
|
||||
| 91 | `OP_OCLASS` | `B` | `R(a) = ::Object`
|
||||
| 92 | `OP_CLASS` | `BB` | `R(a) = newclass(R(a),Syms(b),R(a+1))`
|
||||
| 93 | `OP_MODULE` | `BB` | `R(a) = newmodule(R(a),Syms(b))`
|
||||
| 94 | `OP_EXEC` | `BB` | `R(a) = blockexec(R(a),Irep(b))`
|
||||
| 95 | `OP_DEF` | `BB` | `R(a).newmethod(Syms(b),R(a+1)); R(a) = Syms(b)`
|
||||
| 96 | `OP_ALIAS` | `BB` | `alias_method(target_class,Syms(a),Syms(b))`
|
||||
| 97 | `OP_UNDEF` | `B` | `undef_method(target_class,Syms(a))`
|
||||
| 98 | `OP_SCLASS` | `B` | `R(a) = R(a).singleton_class`
|
||||
| 99 | `OP_TCLASS` | `B` | `R(a) = target_class`
|
||||
| 100 | `OP_DEBUG` | `BBB` | `print a,b,c`
|
||||
| 101 | `OP_ERR` | `B` | `raise(LocalJumpError, Pool(a))`
|
||||
| 102 | `OP_EXT1` | `-` | `make 1st operand (a) 16bit`
|
||||
| 103 | `OP_EXT2` | `-` | `make 2nd operand (b) 16bit`
|
||||
| 104 | `OP_EXT3` | `-` | `make 1st and 2nd operands 16bit`
|
||||
| 105 | `OP_STOP` | `-` | `stop VM`
|
||||
|
||||
@@ -1,325 +0,0 @@
|
||||
<!-- summary: Virtual Machine Internals -->
|
||||
|
||||
# Virtual Machine Internals
|
||||
|
||||
This document describes mruby's virtual machine for developers
|
||||
working on `src/vm.c` and related code.
|
||||
|
||||
**Read this if you are:** debugging method dispatch or call frame
|
||||
issues, working on exception handling, implementing new opcodes,
|
||||
modifying fiber/coroutine behavior, or optimizing the dispatch loop.
|
||||
|
||||
For the instruction set, see [opcode.md](opcode.md). For the
|
||||
compiler that generates bytecode, see [compiler.md](compiler.md).
|
||||
|
||||
## Execution Model
|
||||
|
||||
mruby uses a **register-based VM**. Local variables and temporaries
|
||||
occupy fixed register slots determined at compile time. Each method
|
||||
call gets its own register window on a shared value stack.
|
||||
|
||||
## Execution Context
|
||||
|
||||
The VM state is stored in `mrb_context`:
|
||||
|
||||
```text
|
||||
mrb_context
|
||||
+-- stbase..stend value stack (mrb_value[])
|
||||
+-- cibase..ciend call info stack (mrb_callinfo[])
|
||||
+-- ci current call frame pointer
|
||||
+-- status fiber state
|
||||
+-- prev previous context (fiber chain)
|
||||
+-- vmexec VM execution state flag
|
||||
```
|
||||
|
||||
The value stack and call info stack grow independently. Each fiber
|
||||
has its own `mrb_context`.
|
||||
|
||||
### Stack Sizing
|
||||
|
||||
- Initial value stack: 128 entries (`STACK_INIT_SIZE`)
|
||||
- Initial call info stack: 32 entries (`CALLINFO_INIT_SIZE`)
|
||||
- Growth factor: 1.5x (or 2x with `MRB_STACK_EXTEND_DOUBLING`)
|
||||
- Minimum growth: 128 entries (`MRB_STACK_GROWTH`)
|
||||
- Max stack depth: `MRB_STACK_MAX` (0x40000 - 128)
|
||||
- Max call depth: `MRB_CALL_LEVEL_MAX` (512, or 128 with ASAN)
|
||||
|
||||
Exceeding either limit raises `SystemStackError`.
|
||||
|
||||
When the value stack is reallocated, all `REnv` objects and
|
||||
`mrb_callinfo` stack pointers are adjusted by the delta
|
||||
(`envadjust` function).
|
||||
|
||||
## Call Frames
|
||||
|
||||
Each method or block call pushes a `mrb_callinfo` frame:
|
||||
|
||||
```text
|
||||
mrb_callinfo
|
||||
+-- n:4 positional argument count (0-14, 15 = varargs)
|
||||
+-- nk:4 keyword argument count (0-14, 15 = varargs)
|
||||
+-- cci call context info (NONE, DIRECT, SKIP, RESUMED)
|
||||
+-- vis visibility flags (public/private/protected)
|
||||
+-- mid method symbol
|
||||
+-- proc current RProc
|
||||
+-- blk block argument (RProc*)
|
||||
+-- stack pointer into value stack
|
||||
+-- pc program counter (bytecode position)
|
||||
+-- u.env closure environment (REnv*)
|
||||
+-- u.target_class receiver's class
|
||||
```
|
||||
|
||||
### Stack Layout Per Frame
|
||||
|
||||
```text
|
||||
ci->stack:
|
||||
[0] self (receiver)
|
||||
[1..n] positional arguments
|
||||
[n+1..] keyword argument pairs (key, value, key, value, ...)
|
||||
[bidx] block argument
|
||||
[bidx+1..] local variables and temporaries
|
||||
```
|
||||
|
||||
### Argument Count Encoding
|
||||
|
||||
The `n` and `nk` fields are 4 bits each (0-15). When `n == 15`,
|
||||
positional arguments are packed into a single Array in register 1.
|
||||
When `nk == 15`, keyword arguments are packed into a single Hash.
|
||||
|
||||
The block index is calculated by `mrb_bidx(n, nk)`:
|
||||
|
||||
```text
|
||||
if n == 15: n = 1 (array)
|
||||
if nk == 15: n += 1 (hash)
|
||||
else: n += nk * 2 (key-value pairs)
|
||||
return n + 1 (skip self)
|
||||
```
|
||||
|
||||
### Call Context Info (cci)
|
||||
|
||||
| Value | Name | Meaning |
|
||||
| ----- | ---- | ------- |
|
||||
| 0 | `CINFO_NONE` | Normal VM-to-VM call |
|
||||
| 1 | `CINFO_DIRECT` | Explicit VM call (block, lambda.call) |
|
||||
| 2 | `CINFO_SKIP` | Skip frame in stack traces |
|
||||
| 3 | `CINFO_RESUMED` | Fiber resumed (stop execution) |
|
||||
|
||||
## Dispatch Loop
|
||||
|
||||
The main loop in `mrb_vm_run()` decodes and dispatches opcodes.
|
||||
Two dispatch strategies are available:
|
||||
|
||||
- **Computed goto** (default on GCC/Clang): a jump table of label
|
||||
addresses (`optable[]`) for direct dispatch. Faster due to
|
||||
better branch prediction.
|
||||
- **Switch-based** (`MRB_USE_VM_SWITCH_DISPATCH`): a standard
|
||||
`switch(insn)` statement. Default on MSVC and other compilers.
|
||||
|
||||
The dispatch loop is wrapped in `MRB_TRY`/`MRB_CATCH` for exception
|
||||
handling (see [Exception Handling](#exception-handling)).
|
||||
|
||||
## Method Dispatch
|
||||
|
||||
When `OP_SEND` (or `OP_SSEND`, `OP_SUPER`) executes:
|
||||
|
||||
### 1. Prepare Arguments
|
||||
|
||||
Determine argument layout. If argument count < 15, the fast path
|
||||
uses inline registers. Otherwise, arguments are packed into an
|
||||
Array (varargs mode).
|
||||
|
||||
### 2. Push Call Frame
|
||||
|
||||
```c
|
||||
ci = cipush(mrb, a, CINFO_DIRECT, NULL, NULL, blk, mid, argc);
|
||||
```
|
||||
|
||||
The new frame's stack starts at the previous frame's stack + `a`
|
||||
(the receiver's register index).
|
||||
|
||||
### 3. Method Lookup
|
||||
|
||||
The lookup sequence:
|
||||
|
||||
1. **Method cache check**: hash table lookup by `(class, mid)`.
|
||||
Default cache size: `MRB_METHOD_CACHE_SIZE` (256 entries).
|
||||
2. **Method table walk**: if cache misses, search the receiver's
|
||||
class method table (`mt`), then walk the superclass chain.
|
||||
3. **Cache store**: on successful lookup, store in the cache.
|
||||
|
||||
The method cache is invalidated when classes are modified
|
||||
(`mrb_mc_clear_by_class`).
|
||||
|
||||
### 4. Invoke
|
||||
|
||||
- **Ruby method** (irep-based): extend the stack to `irep->nregs`,
|
||||
set `ci->pc` to `irep->iseq`, and jump to the new bytecode.
|
||||
- **C function**: call `func(mrb, recv)` directly, then pop the
|
||||
call frame and store the return value.
|
||||
|
||||
### 5. Visibility Check
|
||||
|
||||
Private methods are only callable without an explicit receiver.
|
||||
Protected methods are callable from the same class hierarchy.
|
||||
Violations raise `NoMethodError`.
|
||||
|
||||
## Exception Handling
|
||||
|
||||
### setjmp/longjmp
|
||||
|
||||
By default, mruby uses `setjmp`/`longjmp` for exception control
|
||||
flow:
|
||||
|
||||
```c
|
||||
MRB_TRY(&c_jmp) {
|
||||
mrb->jmp = &c_jmp;
|
||||
/* dispatch loop */
|
||||
}
|
||||
MRB_CATCH(&c_jmp) {
|
||||
/* handle exception */
|
||||
}
|
||||
MRB_END_EXC(&c_jmp);
|
||||
```
|
||||
|
||||
With `MRB_USE_CXX_EXCEPTION`, C++ `try`/`catch` is used instead.
|
||||
|
||||
### Handler Table
|
||||
|
||||
Each irep contains a catch handler table (appended after iseq in
|
||||
memory) with entries for `rescue` and `ensure` blocks:
|
||||
|
||||
```text
|
||||
mrb_irep_catch_handler
|
||||
+-- type RESCUE (0) or ENSURE (1)
|
||||
+-- begin[4] start PC of protected range
|
||||
+-- end[4] end PC of protected range
|
||||
+-- target[4] jump target when handler matches
|
||||
```
|
||||
|
||||
### Unwinding Process
|
||||
|
||||
When an exception occurs:
|
||||
|
||||
1. Search the current irep's catch handler table (reverse order)
|
||||
for a handler covering the current PC
|
||||
2. If an `ensure` handler is found: execute it (may re-raise)
|
||||
3. If a `rescue` handler is found: jump to handler code
|
||||
4. If no handler: pop the call frame (`cipop`) and repeat with
|
||||
the parent frame
|
||||
5. `CINFO_DIRECT` frames are destroyed during propagation
|
||||
|
||||
## Block and Closure Handling
|
||||
|
||||
### REnv (Environment)
|
||||
|
||||
Closures capture their enclosing scope's variables through `REnv`:
|
||||
|
||||
```text
|
||||
REnv
|
||||
+-- stack pointer to captured variable values
|
||||
+-- cxt owning context (NULL if detached from stack)
|
||||
+-- mid method symbol
|
||||
+-- flags length, block index, visibility
|
||||
```
|
||||
|
||||
While the defining scope is active, `REnv::stack` points directly
|
||||
into the VM value stack (shared). This avoids copying.
|
||||
|
||||
### Environment Unsharing
|
||||
|
||||
When a closure outlives its defining scope, `mrb_env_unshare()`
|
||||
copies the captured variables from the stack to a heap-allocated
|
||||
buffer:
|
||||
|
||||
```c
|
||||
mrb_env_unshare(mrb, env, noraise);
|
||||
```
|
||||
|
||||
After unsharing, `MRB_ENV_CLOSE(env)` sets `cxt = NULL` to indicate
|
||||
the environment is detached. A write barrier is issued for GC
|
||||
correctness.
|
||||
|
||||
### Proc Types
|
||||
|
||||
| Flag | Meaning |
|
||||
| ---- | ------- |
|
||||
| `MRB_PROC_CFUNC_FL` | C function (not irep-based) |
|
||||
| `MRB_PROC_STRICT` | Lambda (strict argument check) |
|
||||
| `MRB_PROC_ORPHAN` | No environment attachment |
|
||||
| `MRB_PROC_ENVSET` | Has captured environment |
|
||||
| `MRB_PROC_SCOPE` | Defines a new variable scope |
|
||||
|
||||
## Fiber Switching
|
||||
|
||||
Fibers are lightweight coroutines. Each fiber has its own
|
||||
`mrb_context` with separate value and call info stacks.
|
||||
|
||||
### Fiber States
|
||||
|
||||
```text
|
||||
CREATED --> RUNNING --> SUSPENDED --> TERMINATED
|
||||
| ^
|
||||
+-----------+
|
||||
(yield/resume)
|
||||
TRANSFERRED (via Fiber#transfer)
|
||||
```
|
||||
|
||||
### Context Switch
|
||||
|
||||
On `Fiber#resume`:
|
||||
|
||||
1. Save current context state
|
||||
2. Set `mrb->c` to the fiber's context
|
||||
3. Push arguments onto the fiber's stack
|
||||
4. Continue execution in the fiber
|
||||
|
||||
On `Fiber.yield`:
|
||||
|
||||
1. Save fiber context
|
||||
2. Restore the parent context (`mrb->c = c->prev`)
|
||||
3. Return yield values to the parent
|
||||
|
||||
### Fiber Termination
|
||||
|
||||
When a fiber completes (`fiber_terminate`):
|
||||
|
||||
1. Unshare any environments that reference the fiber's stack
|
||||
2. Set status to `TERMINATED`
|
||||
3. Free the fiber's stacks
|
||||
4. Switch to the previous context
|
||||
|
||||
### C Function Boundary
|
||||
|
||||
Fibers cannot yield across C function boundaries. You cannot call
|
||||
`Fiber.yield` from within a C-implemented method (except via
|
||||
`mrb_fiber_yield` at return). This is because C call frames cannot
|
||||
be suspended and resumed.
|
||||
|
||||
## GC Integration
|
||||
|
||||
The VM saves the arena index at the start of the dispatch loop:
|
||||
|
||||
```c
|
||||
int ai = mrb_gc_arena_save(mrb);
|
||||
```
|
||||
|
||||
After each C function call, the arena is shrunk back:
|
||||
|
||||
```c
|
||||
mrb_gc_arena_shrink(mrb, ai);
|
||||
```
|
||||
|
||||
This prevents temporary objects created by C functions from
|
||||
accumulating in the arena.
|
||||
|
||||
Write barriers are issued when environments are detached or closed,
|
||||
ensuring the incremental GC correctly tracks live references.
|
||||
|
||||
## Source Files
|
||||
|
||||
| File | Contents |
|
||||
| ---- | -------- |
|
||||
| `src/vm.c` | Dispatch loop, method invocation (~1900 lines) |
|
||||
| `include/mruby.h` | `mrb_state`, `mrb_callinfo`, `mrb_context` |
|
||||
| `include/mruby/proc.h` | `RProc`, `REnv` structures |
|
||||
| `include/mruby/throw.h` | `MRB_TRY`/`MRB_CATCH` macros |
|
||||
+22
-99
@@ -28,11 +28,11 @@ rescue
|
||||
end
|
||||
```
|
||||
|
||||
#### CRuby
|
||||
#### Ruby [ruby 2.0.0p645 (2015-04-13 revision 50299)]
|
||||
|
||||
`ZeroDivisionError` is raised.
|
||||
|
||||
#### mruby
|
||||
#### mruby [3.1.0 (2022-05-12)]
|
||||
|
||||
`RuntimeError` is raised instead of `ZeroDivisionError`. To re-raise the exception, you have to do:
|
||||
|
||||
@@ -64,11 +64,11 @@ end
|
||||
p Liste.new "foobar"
|
||||
```
|
||||
|
||||
#### CRuby
|
||||
#### Ruby [ruby 2.0.0p645 (2015-04-13 revision 50299)]
|
||||
|
||||
`[]`
|
||||
|
||||
#### mruby
|
||||
#### mruby [3.1.0 (2022-05-12)]
|
||||
|
||||
`ArgumentError` is raised.
|
||||
|
||||
@@ -82,13 +82,13 @@ other reflection methods instead.
|
||||
defined?(Foo)
|
||||
```
|
||||
|
||||
#### CRuby
|
||||
#### Ruby [ruby 2.0.0p645 (2015-04-13 revision 50299)]
|
||||
|
||||
```
|
||||
nil
|
||||
```
|
||||
|
||||
#### mruby
|
||||
#### mruby [3.1.0 (2022-05-12)]
|
||||
|
||||
`NameError` is raised.
|
||||
|
||||
@@ -101,18 +101,17 @@ of the ISO standard.
|
||||
alias $a $__a__
|
||||
```
|
||||
|
||||
#### CRuby
|
||||
#### Ruby [ruby 2.0.0p645 (2015-04-13 revision 50299)]
|
||||
|
||||
`nil`
|
||||
|
||||
#### mruby
|
||||
#### mruby [3.1.0 (2022-05-12)]
|
||||
|
||||
Syntax error
|
||||
|
||||
## Operator modification
|
||||
|
||||
Operators on some of the primitive classes cannot be overridden, as they are
|
||||
optimized in the VM.
|
||||
An operator can't be overwritten by the user.
|
||||
|
||||
```ruby
|
||||
class String
|
||||
@@ -123,21 +122,26 @@ end
|
||||
'a' + 'b'
|
||||
```
|
||||
|
||||
#### CRuby
|
||||
#### Ruby [ruby 2.0.0p645 (2015-04-13 revision 50299)]
|
||||
|
||||
`ArgumentError` is raised.
|
||||
The re-defined `+` operator does not accept any arguments.
|
||||
|
||||
#### mruby
|
||||
#### mruby [3.1.0 (2022-05-12)]
|
||||
|
||||
`'ab'`
|
||||
Behavior of the operator wasn't changed.
|
||||
|
||||
## `Kernel#binding` is not supported without mruby-binding gem
|
||||
## `Kernel#binding` is not supported until [3.0.0 (2021-03-05)]
|
||||
|
||||
`Kernel#binding` method requires the `mruby-binding` gem (included
|
||||
in the `metaprog` gembox). Without this gem, `binding` is not
|
||||
available.
|
||||
`Kernel#binding` method is not supported.
|
||||
|
||||
#### Ruby [ruby 2.5.1p57 (2018-03-29 revision 63029)]
|
||||
|
||||
```shell
|
||||
$ ruby -e 'puts Proc.new {}.binding'
|
||||
#<Binding:0x00000e9deabb9950>
|
||||
```
|
||||
|
||||
## `nil?` redefinition in conditional expressions
|
||||
|
||||
@@ -202,91 +206,10 @@ class C
|
||||
end
|
||||
```
|
||||
|
||||
#### CRuby
|
||||
#### Ruby [ruby 3.5.0dev (2025-04-21 85bab61565))]
|
||||
|
||||
Prints `:append`.
|
||||
|
||||
#### mruby
|
||||
#### mruby [3.5.0 (2025-04-28)]
|
||||
|
||||
Nothing printed (since `include` does not call `append_features` internally).
|
||||
|
||||
## No `#hash` call for small hashes
|
||||
|
||||
For performance reasons, mruby avoids calling the `#hash` method on keys when a hash table is small. This means that custom `#hash` methods on key objects may not be executed.
|
||||
|
||||
## Pattern Matching
|
||||
|
||||
Pattern matching is only partially supported in mruby. Currently, only the rightward assignment operator (`=>`) with simple variable binding is implemented.
|
||||
|
||||
```ruby
|
||||
expr => var # Supported: assigns expr to var
|
||||
```
|
||||
|
||||
#### CRuby
|
||||
|
||||
Full pattern matching with `case/in` syntax and various pattern types:
|
||||
|
||||
```ruby
|
||||
case [1, 2, 3]
|
||||
in [a, b, c]
|
||||
puts "#{a}, #{b}, #{c}" # => "1, 2, 3"
|
||||
end
|
||||
|
||||
case {name: "Alice", age: 30}
|
||||
in {name:, age:}
|
||||
puts "#{name} is #{age}" # => "Alice is 30"
|
||||
end
|
||||
```
|
||||
|
||||
#### mruby
|
||||
|
||||
Only rightward assignment with simple variable binding:
|
||||
|
||||
```ruby
|
||||
[1, 2, 3] => x
|
||||
puts x # => [1, 2, 3]
|
||||
```
|
||||
|
||||
The following are **not supported**:
|
||||
|
||||
- `case/in` syntax
|
||||
- Array patterns: `in [a, b, c]`
|
||||
- Hash patterns: `in {name:, age:}`
|
||||
- Guard clauses: `in pattern if condition`
|
||||
- Pin operator: `in ^variable`
|
||||
- Find patterns: `in [*, x, *]`
|
||||
- Alternative patterns: `in pattern1 | pattern2`
|
||||
- Boolean pattern check: `value in pattern`
|
||||
|
||||
Note: mruby does provide `Array#deconstruct` and `Hash#deconstruct_keys` methods for future pattern matching compatibility.
|
||||
|
||||
## No Refinements
|
||||
|
||||
Module refinements (`refine`, `using`) are not supported in mruby.
|
||||
|
||||
## No `Encoding` Class
|
||||
|
||||
mruby does not have an `Encoding` class. Strings are treated as
|
||||
byte sequences by default. UTF-8 aware string operations can be
|
||||
enabled with the `MRB_UTF8_STRING` compile flag.
|
||||
|
||||
## Integer Precision Varies by Boxing Mode
|
||||
|
||||
Integer size depends on the value boxing configuration:
|
||||
|
||||
| Configuration | Integer range |
|
||||
| ------------- | ------------- |
|
||||
| Word boxing, 64-bit (default) | roughly +/- 2^62 |
|
||||
| Word boxing, 32-bit (default) | roughly +/- 2^30 |
|
||||
| NaN boxing (64-bit only) | -2^31 to 2^31-1 |
|
||||
|
||||
Code relying on 64-bit integer precision may behave differently
|
||||
across configurations. The `mruby-bigint` gem provides
|
||||
arbitrary-precision integers when included.
|
||||
|
||||
## No `ObjectSpace.each_object` by Default
|
||||
|
||||
`ObjectSpace` is only available via the `mruby-objectspace` gem
|
||||
(included in the `stdlib` gembox). Even with the gem,
|
||||
`ObjectSpace.each_object` has limited functionality compared
|
||||
to CRuby.
|
||||
|
||||
+2
-2
@@ -20,7 +20,7 @@ Keyword arguments are basically separated from ordinal arguments.
|
||||
|
||||
Some configuration macros are available:
|
||||
|
||||
- `MRB_WORDBOX_NO_INLINE_FLOAT` (formerly `MRB_WORDBOX_NO_FLOAT_TRUNCATE`): by default, float values are packed in the word if possible, but define this macro to allocate float values in the heap.
|
||||
- `MRB_WORDBOX_NO_FLOAT_TRUNCATE`: by default, float values are packed in the word if possible, but define this macro to allocate float values in the heap.
|
||||
- `MRB_USE_RO_DATA_P_ETEXT`: define this macro if `_etext` is available on your platform.
|
||||
- `MRB_NO_DEFAULT_RO_DATA_P`: define this macro to avoid using predefined `mrb_ro_data_p()` function
|
||||
|
||||
@@ -156,7 +156,7 @@ Now takes 2 operands and pushes multiple entries to an array.
|
||||
### Word Boxing
|
||||
|
||||
`MRB_WORD_BOXING` now packs floating-point numbers in the word, if the size of `mrb_float` is equal or smaller than the size of `mrb_int` by default.
|
||||
If the size of `mrb_float` and `mrb_int` are same, the last 2 bits in the `mrb_float` are trimmed and used as flags. If you need full precision, you need to define `MRB_WORDBOX_NO_INLINE_FLOAT` (formerly `MRB_WORDBOX_NO_FLOAT_TRUNCATE`) as described above.
|
||||
If the size of `mrb_float` and `mrb_int` are same, the last 2 bits in the `mrb_float` are trimmed and used as flags. If you need full precision, you need to define `MRB_WORDBOX_NO_FLOAT_TRUNCATE` as described above.
|
||||
|
||||
### NaN Boxing
|
||||
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@
|
||||
## `mruby`
|
||||
|
||||
- `-b` only specifies the script is the binary. The files loaded by `-r` are not affected by the option.
|
||||
- `mruby` now loads compiled binary if the suffix is `.mrb`.
|
||||
- `mruby` now loads complied binary if the suffix is `.mrb`.
|
||||
|
||||
## `mrbc`
|
||||
|
||||
|
||||
+2
-21
@@ -64,14 +64,8 @@
|
||||
# define MRB_WORD_BOXING
|
||||
#endif
|
||||
|
||||
/* if defined mruby does not inline float values in word boxing;
|
||||
all floats are heap-allocated as RFloat objects */
|
||||
//#define MRB_WORDBOX_NO_INLINE_FLOAT
|
||||
|
||||
/* obsolete configuration */
|
||||
#if defined(MRB_WORDBOX_NO_FLOAT_TRUNCATE)
|
||||
# define MRB_WORDBOX_NO_INLINE_FLOAT
|
||||
#endif
|
||||
/* if defined mruby allocates Float objects in the heap to keep full precision if needed */
|
||||
//#define MRB_WORDBOX_NO_FLOAT_TRUNCATE
|
||||
|
||||
/* add -DMRB_INT32 to use 32-bit integer for mrb_int; conflict with MRB_INT64;
|
||||
Default for 32-bit CPU mode. */
|
||||
@@ -92,13 +86,6 @@
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/* MRB_INT64 on 32-bit with word/NaN boxing causes alignment issues
|
||||
for heap-allocated RInteger (int64_t needs 8-byte alignment but
|
||||
GC heap slots may not guarantee it); use MRB_NO_BOXING instead */
|
||||
#if defined(MRB_INT64) && defined(MRB_32BIT) && !defined(MRB_NO_BOXING)
|
||||
#error "MRB_INT64 on 32-bit requires MRB_NO_BOXING"
|
||||
#endif
|
||||
|
||||
/* call malloc_trim(0) from mrb_full_gc() */
|
||||
//#define MRB_USE_MALLOC_TRIM
|
||||
|
||||
@@ -166,12 +153,6 @@
|
||||
//#define MRB_USE_DEBUG_HOOK /* hooks for debugger */
|
||||
//#define MRB_USE_ALL_SYMBOLS /* Symbol.all_symbols */
|
||||
|
||||
/* Symbol table configuration */
|
||||
/* Threshold for switching from linear search to hash table */
|
||||
#ifndef MRB_SYMBOL_LINEAR_THRESHOLD
|
||||
#define MRB_SYMBOL_LINEAR_THRESHOLD 256
|
||||
#endif
|
||||
|
||||
/* obsolete configurations */
|
||||
#if defined(DISABLE_STDIO) || defined(MRB_DISABLE_STDIO)
|
||||
# define MRB_NO_STDIO
|
||||
|
||||
+12
-106
@@ -155,7 +155,6 @@ typedef uint8_t mrb_code;
|
||||
typedef uint32_t mrb_aspec;
|
||||
|
||||
typedef struct mrb_irep mrb_irep;
|
||||
|
||||
struct mrb_state;
|
||||
|
||||
#ifndef MRB_FIXED_STATE_ATEXIT_STACK_SIZE
|
||||
@@ -189,10 +188,6 @@ enum mrb_fiber_state {
|
||||
MRB_FIBER_TERMINATED,
|
||||
};
|
||||
|
||||
/* Task context status aliases */
|
||||
#define MRB_TASK_CREATED MRB_FIBER_CREATED
|
||||
#define MRB_TASK_STOPPED MRB_FIBER_TERMINATED
|
||||
|
||||
struct mrb_context {
|
||||
struct mrb_context *prev;
|
||||
|
||||
@@ -227,7 +222,7 @@ mrb_static_assert_powerof2(MRB_METHOD_CACHE_SIZE);
|
||||
typedef mrb_value (*mrb_func_t)(struct mrb_state *mrb, mrb_value self);
|
||||
|
||||
typedef struct {
|
||||
uint32_t flags; /* method flags (no symbol packed) */
|
||||
uint32_t flags; /* compatible with mt keys in class.c */
|
||||
|
||||
union {
|
||||
const struct RProc *proc;
|
||||
@@ -238,7 +233,7 @@ typedef struct {
|
||||
#ifndef MRB_NO_METHOD_CACHE
|
||||
struct mrb_cache_entry {
|
||||
struct RClass *c, *c0;
|
||||
mrb_sym mid;
|
||||
/* mrb_sym mid; // mid is stored in mrb_method_t::flags */
|
||||
mrb_method_t m;
|
||||
};
|
||||
#endif
|
||||
@@ -247,19 +242,6 @@ struct mrb_jmpbuf;
|
||||
|
||||
typedef void (*mrb_atexit_func)(struct mrb_state*);
|
||||
|
||||
#ifdef MRB_USE_TASK_SCHEDULER
|
||||
struct mrb_task;
|
||||
|
||||
typedef struct mrb_task_state {
|
||||
struct mrb_task *queues[4]; /* Task queues (dormant, ready, waiting, suspended) */
|
||||
volatile uint32_t tick; /* Current tick count */
|
||||
volatile uint32_t wakeup_tick; /* Next wakeup tick */
|
||||
volatile mrb_bool switching; /* Context switch pending flag */
|
||||
struct mrb_task *main_task; /* Main task wrapper for root context */
|
||||
uint8_t scheduler_lock; /* Lock counter for synchronous execution */
|
||||
} mrb_task_state;
|
||||
#endif
|
||||
|
||||
typedef struct mrb_state {
|
||||
struct mrb_jmpbuf *jmp;
|
||||
|
||||
@@ -291,17 +273,16 @@ typedef struct mrb_state {
|
||||
|
||||
mrb_gc gc;
|
||||
|
||||
mrb_bool bootstrapping;
|
||||
|
||||
#ifndef MRB_NO_METHOD_CACHE
|
||||
struct mrb_cache_entry cache[MRB_METHOD_CACHE_SIZE];
|
||||
#endif
|
||||
|
||||
mrb_sym symidx;
|
||||
const char **symtbl;
|
||||
uint8_t *symlink;
|
||||
uint8_t *symflags;
|
||||
mrb_sym symhash[256];
|
||||
size_t symcapa;
|
||||
struct mrb_sym_hash_table *symhash;
|
||||
void *sym_pool;
|
||||
#ifndef MRB_USE_ALL_SYMBOLS
|
||||
char symbuf[8]; /* buffer for small symbol names */
|
||||
#endif
|
||||
@@ -323,10 +304,6 @@ typedef struct mrb_state {
|
||||
struct RObject *arena_err; /* pre-allocated arena overflow error */
|
||||
#endif
|
||||
|
||||
struct mrb_mt_rom_list *rom_mt; /* heap-allocated ROM wrappers (freed at close) */
|
||||
|
||||
struct mrb_iv_shape *root_shape; /* root of IV shape tree */
|
||||
|
||||
void *ud; /* auxiliary data */
|
||||
|
||||
#ifdef MRB_FIXED_STATE_ATEXIT_STACK
|
||||
@@ -335,10 +312,6 @@ typedef struct mrb_state {
|
||||
mrb_atexit_func *atexit_stack;
|
||||
#endif
|
||||
uint16_t atexit_stack_len;
|
||||
|
||||
#ifdef MRB_USE_TASK_SCHEDULER
|
||||
mrb_task_state task; /* Task scheduler state */
|
||||
#endif
|
||||
} mrb_state;
|
||||
|
||||
/**
|
||||
@@ -416,7 +389,7 @@ MRB_API void mrb_include_module(mrb_state *mrb, struct RClass *cla, struct RClas
|
||||
MRB_API void mrb_prepend_module(mrb_state *mrb, struct RClass *cla, struct RClass *prepended);
|
||||
|
||||
/**
|
||||
* Defines a global function in Ruby.
|
||||
* Defines a global function in ruby.
|
||||
*
|
||||
* If you're creating a gem it may look something like this
|
||||
*
|
||||
@@ -926,11 +899,6 @@ MRB_API struct RClass* mrb_define_module_under_id(mrb_state *mrb, struct RClass
|
||||
*/
|
||||
#define MRB_ARGS_BLOCK() ((mrb_aspec)1)
|
||||
|
||||
/**
|
||||
* Function does not accept a block (&nil)
|
||||
*/
|
||||
#define MRB_ARGS_NOBLOCK() ((mrb_aspec)(1 << 23))
|
||||
|
||||
/**
|
||||
* Function accepts any number of arguments
|
||||
*/
|
||||
@@ -965,7 +933,7 @@ MRB_API struct RClass* mrb_define_module_under_id(mrb_state *mrb, struct RClass
|
||||
* | `I` | inline struct | void *, struct RClass | `I!` gives `NULL` for `nil` |
|
||||
* | `&` | block | {mrb_value} | &! raises exception if no block given. |
|
||||
* | `*` | rest arguments | const {mrb_value} *, {mrb_int} | Receive the rest of arguments as an array; `*!` avoid copy of the stack. |
|
||||
* | `\|` | optional | | After this spec following specs would be optional. |
|
||||
* | <code>\|</code> | optional | | After this spec following specs would be optional. |
|
||||
* | `?` | optional given | {mrb_bool} | `TRUE` if preceding argument is given. Used to check optional argument is given. |
|
||||
* | `:` | keyword args | {mrb_kwargs} const | Get keyword arguments. @see mrb_kwargs |
|
||||
*
|
||||
@@ -1098,7 +1066,7 @@ MRB_API mrb_bool mrb_block_given_p(mrb_state *mrb);
|
||||
#define mrb_strlen_lit(lit) (sizeof(lit "") - 1)
|
||||
|
||||
/**
|
||||
* Call existing Ruby functions.
|
||||
* Call existing ruby functions.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
@@ -1131,7 +1099,7 @@ MRB_API mrb_bool mrb_block_given_p(mrb_state *mrb);
|
||||
MRB_API mrb_value mrb_funcall(mrb_state *mrb, mrb_value val, const char *name, mrb_int argc, ...);
|
||||
MRB_API mrb_value mrb_funcall_id(mrb_state *mrb, mrb_value val, mrb_sym mid, mrb_int argc, ...);
|
||||
/**
|
||||
* Call existing Ruby functions. This is basically the type safe version of mrb_funcall.
|
||||
* Call existing ruby functions. This is basically the type safe version of mrb_funcall.
|
||||
*
|
||||
* #include <stdio.h>
|
||||
* #include <mruby.h>
|
||||
@@ -1146,7 +1114,7 @@ MRB_API mrb_value mrb_funcall_id(mrb_state *mrb, mrb_value val, mrb_sym mid, mrb
|
||||
*
|
||||
* FILE *fp = fopen("test.rb","r");
|
||||
* mrb_value obj = mrb_load_file(mrb,fp);
|
||||
* mrb_funcall_argv(mrb, obj, MRB_SYM(method_name), 1, &obj); // Calling Ruby function from test.rb.
|
||||
* mrb_funcall_argv(mrb, obj, MRB_SYM(method_name), 1, &obj); // Calling ruby function from test.rb.
|
||||
* fclose(fp);
|
||||
* mrb_close(mrb);
|
||||
* }
|
||||
@@ -1160,7 +1128,7 @@ MRB_API mrb_value mrb_funcall_id(mrb_state *mrb, mrb_value val, mrb_sym mid, mrb
|
||||
*/
|
||||
MRB_API mrb_value mrb_funcall_argv(mrb_state *mrb, mrb_value val, mrb_sym name, mrb_int argc, const mrb_value *argv);
|
||||
/**
|
||||
* Call existing Ruby functions with a block.
|
||||
* Call existing ruby functions with a block.
|
||||
*/
|
||||
MRB_API mrb_value mrb_funcall_with_block(mrb_state *mrb, mrb_value val, mrb_sym name, mrb_int argc, const mrb_value *argv, mrb_value block);
|
||||
/**
|
||||
@@ -1279,44 +1247,6 @@ MRB_API mrb_state* mrb_open_core(void);
|
||||
* Pointer to the mrb_state to be closed.
|
||||
*/
|
||||
MRB_API void mrb_close(mrb_state *mrb);
|
||||
#ifndef MRB_NO_METHOD_CACHE
|
||||
MRB_API void mrb_method_cache_clear(mrb_state *mrb);
|
||||
#else
|
||||
#define mrb_method_cache_clear(mrb) ((void)0)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Check if mrb_open() failed
|
||||
*
|
||||
* @param mrb
|
||||
* Pointer returned from mrb_open() or mrb_open_core().
|
||||
* @return
|
||||
* Non-zero if initialization failed, 0 if succeeded.
|
||||
* @note
|
||||
* mrb_open() may return non-NULL even on failure (with mrb->exc set).
|
||||
* Use this macro to check for failure:
|
||||
* @code
|
||||
* mrb_state *mrb = mrb_open();
|
||||
* if (MRB_OPEN_FAILURE(mrb)) {
|
||||
* if (mrb) {
|
||||
* // Inspect mrb->exc for error details
|
||||
* mrb_close(mrb);
|
||||
* }
|
||||
* return EXIT_FAILURE;
|
||||
* }
|
||||
* @endcode
|
||||
*/
|
||||
#define MRB_OPEN_FAILURE(mrb) (!(mrb) || (mrb)->exc)
|
||||
|
||||
/**
|
||||
* Check if mrb_open() succeeded
|
||||
*
|
||||
* @param mrb
|
||||
* Pointer returned from mrb_open() or mrb_open_core().
|
||||
* @return
|
||||
* Non-zero if initialization succeeded, 0 if failed.
|
||||
*/
|
||||
#define MRB_OPEN_SUCCESS(mrb) (!MRB_OPEN_FAILURE(mrb))
|
||||
|
||||
/**
|
||||
* The memory allocation function. You can redefine this function for your own allocator.
|
||||
@@ -1373,25 +1303,6 @@ MRB_API mrb_bool mrb_eql(mrb_state *mrb, mrb_value obj1, mrb_value obj2);
|
||||
/* mrb_cmp(mrb, obj1, obj2): 1:0:-1; -2 for error */
|
||||
MRB_API mrb_int mrb_cmp(mrb_state *mrb, mrb_value obj1, mrb_value obj2);
|
||||
|
||||
/* recursion detection */
|
||||
MRB_API mrb_bool mrb_recursive_method_p(mrb_state *mrb, mrb_sym mid, mrb_value obj1, mrb_value obj2);
|
||||
MRB_API mrb_bool mrb_recursive_func_p(mrb_state *mrb, mrb_sym mid, mrb_value obj1, mrb_value obj2);
|
||||
|
||||
#define MRB_RECURSIVE_P(mrb, mid, obj1, obj2) \
|
||||
mrb_recursive_method_p(mrb, mid, obj1, obj2)
|
||||
|
||||
#define MRB_RECURSIVE_UNARY_P(mrb, mid, obj) \
|
||||
mrb_recursive_method_p(mrb, mid, obj, mrb_nil_value())
|
||||
|
||||
#define MRB_RECURSIVE_BINARY_P(mrb, mid, obj1, obj2) \
|
||||
mrb_recursive_method_p(mrb, mid, obj1, obj2)
|
||||
|
||||
#define MRB_RECURSIVE_FUNC_P(mrb, mid, obj) \
|
||||
mrb_recursive_func_p(mrb, mid, obj, mrb_nil_value())
|
||||
|
||||
#define MRB_RECURSIVE_BINARY_FUNC_P(mrb, mid, obj1, obj2) \
|
||||
mrb_recursive_func_p(mrb, mid, obj1, obj2)
|
||||
|
||||
#define mrb_gc_arena_save(mrb) ((mrb)->gc.arena_idx)
|
||||
#define mrb_gc_arena_restore(mrb, idx) ((mrb)->gc.arena_idx = (idx))
|
||||
|
||||
@@ -1582,8 +1493,7 @@ MRB_API mrb_value mrb_fiber_alive_p(mrb_state *mrb, mrb_value fib);
|
||||
MRB_API void mrb_stack_extend(mrb_state*, mrb_int);
|
||||
|
||||
/* temporary memory allocation, only effective while GC arena is kept */
|
||||
MRB_API void* mrb_temp_alloc(mrb_state *mrb, size_t);
|
||||
#define mrb_alloca(mrb, size) mrb_temp_alloc(mrb, size) /* for compatibility */
|
||||
MRB_API void* mrb_alloca(mrb_state *mrb, size_t);
|
||||
|
||||
MRB_API void mrb_state_atexit(mrb_state *mrb, mrb_atexit_func func);
|
||||
|
||||
@@ -1625,10 +1535,6 @@ mrbmemset(void *s, int c, size_t n)
|
||||
|
||||
#define mrb_int_hash_func(mrb,key) (uint32_t)((key)^((key)<<2)^((key)>>2))
|
||||
|
||||
#define MRB_UNIQNAME(name) MRB_UNIQNAME_1(name, __LINE__)
|
||||
#define MRB_UNIQNAME_1(name, line) MRB_UNIQNAME_2(name, line)
|
||||
#define MRB_UNIQNAME_2(name, line) name##line
|
||||
|
||||
MRB_END_DECL
|
||||
|
||||
#endif /* MRUBY_H */
|
||||
|
||||
@@ -20,7 +20,7 @@ typedef struct mrb_shared_array {
|
||||
mrb_value *ptr;
|
||||
} mrb_shared_array;
|
||||
|
||||
#if defined(MRB_32BIT) && defined(MRB_NO_BOXING) && (!defined(MRB_USE_FLOAT32) || defined(MRB_INT64)) && !defined(MRB_ARY_NO_EMBED)
|
||||
#if defined(MRB_32BIT) && defined(MRB_NO_BOXING) && !defined(MRB_USE_FLOAT32) && !defined(MRB_ARY_NO_EMBED)
|
||||
# define MRB_ARY_NO_EMBED
|
||||
#endif
|
||||
|
||||
@@ -86,8 +86,6 @@ struct RArray {
|
||||
#define ARY_UNSET_SHARED_FLAG(a) ((a)->flags &= ~MRB_ARY_SHARED)
|
||||
|
||||
MRB_API void mrb_ary_modify(mrb_state*, struct RArray*);
|
||||
MRB_API mrb_value mrb_ary_dup(mrb_state*, mrb_value ary);
|
||||
MRB_API mrb_value mrb_ary_make_shared_copy(mrb_state*, mrb_value ary);
|
||||
MRB_API mrb_value mrb_ary_new_capa(mrb_state*, mrb_int);
|
||||
|
||||
/*
|
||||
|
||||
+23
-49
@@ -7,44 +7,15 @@
|
||||
#ifndef MRUBY_BOXING_WORD_H
|
||||
#define MRUBY_BOXING_WORD_H
|
||||
|
||||
#if defined(MRB_32BIT) && !defined(MRB_USE_FLOAT32) && !defined(MRB_WORDBOX_NO_INLINE_FLOAT)
|
||||
# define MRB_WORDBOX_NO_INLINE_FLOAT
|
||||
#if defined(MRB_32BIT) && !defined(MRB_USE_FLOAT32) && !defined(MRB_WORDBOX_NO_FLOAT_TRUNCATE)
|
||||
# define MRB_WORDBOX_NO_FLOAT_TRUNCATE
|
||||
#endif
|
||||
|
||||
#ifndef MRB_NO_FLOAT
|
||||
#if !defined(MRB_NO_FLOAT) && defined(MRB_WORDBOX_NO_FLOAT_TRUNCATE)
|
||||
struct RFloat {
|
||||
MRB_OBJECT_HEADER;
|
||||
#ifdef MRB_WORDBOX_NO_INLINE_FLOAT
|
||||
/* avoid 8-byte alignment on 32-bit; use memcpy-based accessors */
|
||||
char f[sizeof(mrb_float)];
|
||||
#else
|
||||
mrb_float f;
|
||||
#endif
|
||||
};
|
||||
|
||||
#include <string.h>
|
||||
|
||||
static inline mrb_float
|
||||
mrb_rfloat_value(const struct RFloat *p)
|
||||
{
|
||||
#ifdef MRB_WORDBOX_NO_INLINE_FLOAT
|
||||
mrb_float f;
|
||||
memcpy(&f, p->f, sizeof(mrb_float));
|
||||
return f;
|
||||
#else
|
||||
return p->f;
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline void
|
||||
mrb_rfloat_set(struct RFloat *p, mrb_float f)
|
||||
{
|
||||
#ifdef MRB_WORDBOX_NO_INLINE_FLOAT
|
||||
memcpy(p->f, &f, sizeof(mrb_float));
|
||||
#else
|
||||
p->f = f;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
struct RInteger {
|
||||
@@ -79,7 +50,7 @@ enum mrb_special_consts {
|
||||
#define WORDBOX_FIXNUM_FLAG (1 << (WORDBOX_FIXNUM_BIT_POS - 1))
|
||||
#define WORDBOX_FIXNUM_MASK ((1 << WORDBOX_FIXNUM_BIT_POS) - 1)
|
||||
|
||||
#if defined(MRB_WORDBOX_NO_INLINE_FLOAT) || defined(MRB_NO_FLOAT)
|
||||
#if defined(MRB_WORDBOX_NO_FLOAT_TRUNCATE) || defined(MRB_NO_FLOAT)
|
||||
/* floats are allocated in heaps */
|
||||
#define WORDBOX_IMMEDIATE_MASK 0x03
|
||||
#define WORDBOX_SYMBOL_BIT_POS 2
|
||||
@@ -112,22 +83,27 @@ enum mrb_special_consts {
|
||||
/*
|
||||
* mrb_value representation:
|
||||
*
|
||||
* 64-bit word with inline float (rotation encoding, lossless):
|
||||
* 64-bit word with inline float:
|
||||
* nil : ...0000 0000 (all bits are 0)
|
||||
* false : ...0000 0100 (mrb_fixnum(v) != 0)
|
||||
* true : ...0000 1100
|
||||
* undef : ...0001 0100
|
||||
* symbol: ...0001 1100 (use only upper 32-bit as symbol value with MRB_64BIT)
|
||||
* fixnum: ...IIII III1
|
||||
* float : ...FFFF FF10 (rotl64(float64-ADDEND, 3); exponent [-255,+256])
|
||||
* float : ...FFFF FF10 (51 bit significands; require MRB_64BIT)
|
||||
* object: ...PPPP P000
|
||||
* (floats outside inline range are heap-allocated as RFloat)
|
||||
*
|
||||
* 64-bit word with inline float32 (MRB_USE_FLOAT32):
|
||||
* float : ...FFFF FF10 (float32 shifted left by 2)
|
||||
* (other values same as above)
|
||||
* 32-bit word with inline float:
|
||||
* nil : ...0000 0000 (all bits are 0)
|
||||
* false : ...0000 0100 (mrb_fixnum(v) != 0)
|
||||
* true : ...0000 1100
|
||||
* undef : ...0001 0100
|
||||
* symbol: ...SSS1 0100 (symbol occupies 20bits)
|
||||
* fixnum: ...IIII III1
|
||||
* float : ...FFFF FF10 (22 bit significands; require MRB_64BIT)
|
||||
* object: ...PPPP P000
|
||||
*
|
||||
* word boxing without inline float (MRB_WORDBOX_NO_INLINE_FLOAT):
|
||||
* and word boxing without inline float (MRB_WORDBOX_NO_FLOAT_TRUNCATE):
|
||||
* nil : ...0000 0000 (all bits are 0)
|
||||
* false : ...0000 0100 (mrb_fixnum(v) != 0)
|
||||
* true : ...0000 1100
|
||||
@@ -144,9 +120,10 @@ union mrb_value_ {
|
||||
void *p;
|
||||
struct RBasic *bp;
|
||||
#ifndef MRB_NO_FLOAT
|
||||
struct RFloat *fp;
|
||||
#if !defined(MRB_WORDBOX_NO_INLINE_FLOAT) && defined(MRB_USE_FLOAT32)
|
||||
#ifndef MRB_WORDBOX_NO_FLOAT_TRUNCATE
|
||||
mrb_float f;
|
||||
#else
|
||||
struct RFloat *fp;
|
||||
#endif
|
||||
#endif
|
||||
struct RInteger *ip;
|
||||
@@ -180,11 +157,11 @@ MRB_API mrb_value mrb_boxing_int_value(struct mrb_state*, mrb_int);
|
||||
#define mrb_ptr(o) mrb_val_union(o).p
|
||||
#define mrb_cptr(o) mrb_val_union(o).vp->p
|
||||
#ifndef MRB_NO_FLOAT
|
||||
#ifndef MRB_WORDBOX_NO_INLINE_FLOAT
|
||||
#ifndef MRB_WORDBOX_NO_FLOAT_TRUNCATE
|
||||
MRB_API mrb_float mrb_word_boxing_value_float(mrb_value v);
|
||||
#define mrb_float(o) mrb_word_boxing_value_float(o)
|
||||
#else
|
||||
#define mrb_float(o) mrb_rfloat_value(mrb_val_union(o).fp)
|
||||
#define mrb_float(o) mrb_val_union(o).fp->f
|
||||
#endif
|
||||
#endif
|
||||
#define mrb_fixnum(o) (mrb_int)(((intptr_t)(o).w) >> WORDBOX_FIXNUM_SHIFT)
|
||||
@@ -205,13 +182,10 @@ mrb_integer_func(mrb_value o) {
|
||||
#define mrb_false_p(o) ((o).w == MRB_Qfalse)
|
||||
#define mrb_true_p(o) ((o).w == MRB_Qtrue)
|
||||
#ifndef MRB_NO_FLOAT
|
||||
#ifdef MRB_WORDBOX_NO_INLINE_FLOAT
|
||||
#define mrb_float_p(o) WORDBOX_OBJ_TYPE_P(o, FLOAT)
|
||||
#elif defined(MRB_USE_FLOAT32) && defined(MRB_64BIT)
|
||||
#ifndef MRB_WORDBOX_NO_FLOAT_TRUNCATE
|
||||
#define mrb_float_p(o) WORDBOX_SHIFT_VALUE_P(o, FLOAT)
|
||||
#else
|
||||
/* rotation encoding: most floats inline, edge cases on heap */
|
||||
#define mrb_float_p(o) (WORDBOX_SHIFT_VALUE_P(o, FLOAT) || WORDBOX_OBJ_TYPE_P(o, FLOAT))
|
||||
#define mrb_float_p(o) WORDBOX_OBJ_TYPE_P(o, FLOAT)
|
||||
#endif
|
||||
#else
|
||||
#define mrb_float_p(o) FALSE
|
||||
|
||||
+1
-54
@@ -17,7 +17,7 @@ MRB_BEGIN_DECL
|
||||
struct RClass {
|
||||
MRB_OBJECT_HEADER;
|
||||
struct iv_tbl *iv;
|
||||
struct mrb_mt_tbl *mt;
|
||||
struct mt_tbl *mt;
|
||||
struct RClass *super;
|
||||
};
|
||||
|
||||
@@ -88,7 +88,6 @@ MRB_API mrb_method_t mrb_method_search_vm(mrb_state*, struct RClass**, mrb_sym);
|
||||
MRB_API mrb_method_t mrb_method_search(mrb_state*, struct RClass*, mrb_sym);
|
||||
|
||||
MRB_API struct RClass* mrb_class_real(struct RClass* cl);
|
||||
MRB_API struct RClass* mrb_class_outer(mrb_state *mrb, struct RClass *c);
|
||||
|
||||
#ifndef MRB_NO_METHOD_CACHE
|
||||
void mrb_mc_clear_by_class(mrb_state *mrb, struct RClass* c);
|
||||
@@ -100,58 +99,6 @@ void mrb_mc_clear_by_class(mrb_state *mrb, struct RClass* c);
|
||||
typedef int (mrb_mt_foreach_func)(mrb_state*,mrb_sym,mrb_method_t,void*);
|
||||
MRB_API void mrb_mt_foreach(mrb_state*, struct RClass*, mrb_mt_foreach_func*, void*);
|
||||
|
||||
/* ROM method table types for static method registration */
|
||||
union mrb_mt_ptr {
|
||||
const struct RProc *proc;
|
||||
mrb_func_t func;
|
||||
};
|
||||
|
||||
/* entry combining function pointer, symbol key, and flags */
|
||||
typedef struct mrb_mt_entry {
|
||||
union mrb_mt_ptr val;
|
||||
mrb_sym key; /* pure symbol ID (no flags packed) */
|
||||
uint32_t flags; /* method flags + aspec */
|
||||
} mrb_mt_entry;
|
||||
|
||||
typedef struct mrb_mt_tbl {
|
||||
int size;
|
||||
int alloc; /* bit 30: MRB_MT_READONLY_BIT, bit 29: MRB_MT_FROZEN_BIT */
|
||||
mrb_mt_entry *ptr;
|
||||
struct mrb_mt_tbl *next;
|
||||
} mrb_mt_tbl;
|
||||
|
||||
#define MRB_MT_READONLY_BIT (1 << 30)
|
||||
#define MRB_MT_FROZEN_BIT (1 << 29)
|
||||
#define MRB_MT_FUNC (1 << 24) /* MRB_METHOD_FUNC_FL */
|
||||
#define MRB_MT_PUBLIC 0
|
||||
#define MRB_MT_PRIVATE (1 << 25) /* MRB_METHOD_PRIVATE_FL */
|
||||
|
||||
/* ROM table entry: 3rd param is MRB_ARGS_*() optionally OR'd with MRB_MT_PRIVATE. */
|
||||
#define MRB_MT_ENTRY(fn, sym, flags) \
|
||||
{ { .func = (fn) }, (sym), (flags) | MRB_MT_FUNC }
|
||||
#define MRB_MT_ASPEC(flags) ((mrb_aspec)((flags) & 0xffffff))
|
||||
|
||||
/* "removed" tombstone: MRB_MT_FUNC flag set with NULL function pointer.
|
||||
This combination never occurs naturally (C functions are never NULL).
|
||||
Unlike undef (proc=NULL without MRB_MT_FUNC), a removed marker makes
|
||||
mt_get() return 0 ("not found"), blocking ROM chain walk while
|
||||
allowing superclass lookup. */
|
||||
#define MRB_MT_REMOVED_P(e) (((e).flags&MRB_MT_FUNC) && (e).val.func==NULL)
|
||||
|
||||
/* Singly-linked list node for tracking heap-allocated ROM wrappers. */
|
||||
struct mrb_mt_rom_list {
|
||||
mrb_mt_tbl *tbl;
|
||||
struct mrb_mt_rom_list *next;
|
||||
};
|
||||
|
||||
/* Allocate a per-state ROM layer wrapping the const entries array,
|
||||
and push it onto the class's method table chain. */
|
||||
void mrb_mt_init_rom(mrb_state *mrb, struct RClass *c,
|
||||
const mrb_mt_entry *entries, int size);
|
||||
#define MRB_MT_INIT_ROM(mrb, cls, entries) \
|
||||
mrb_mt_init_rom(mrb, cls, entries, \
|
||||
(int)(sizeof(entries)/sizeof(entries[0])))
|
||||
|
||||
MRB_END_DECL
|
||||
|
||||
#endif /* MRUBY_CLASS_H */
|
||||
|
||||
@@ -59,15 +59,6 @@ MRB_BEGIN_DECL
|
||||
# define mrb_deprecated
|
||||
#endif
|
||||
|
||||
/** Branch prediction hints for optimization. */
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
# define mrb_likely(x) __builtin_expect(!!(x), 1)
|
||||
# define mrb_unlikely(x) __builtin_expect(!!(x), 0)
|
||||
#else
|
||||
# define mrb_likely(x) (x)
|
||||
# define mrb_unlikely(x) (x)
|
||||
#endif
|
||||
|
||||
/** Declare a type or object as an alignment requirement. */
|
||||
#ifndef mrb_alignas
|
||||
# if defined(__cplusplus) && __cplusplus >= 201103L
|
||||
|
||||
+41
-5
@@ -33,7 +33,6 @@ typedef struct mrb_ccontext {
|
||||
mrb_bool keep_lv:1;
|
||||
mrb_bool no_optimize:1;
|
||||
mrb_bool no_ext_ops:1;
|
||||
mrb_bool no_return_value:1;
|
||||
const struct RProc *upper;
|
||||
|
||||
size_t parser_nerr;
|
||||
@@ -54,7 +53,10 @@ MRB_API void mrb_ccontext_cleanup_local_variables(mrb_ccontext *c);
|
||||
#define mrbc_cleanup_local_variables mrb_ccontext_cleanup_local_variables
|
||||
|
||||
/* AST node structure */
|
||||
typedef struct mrb_ast_node mrb_ast_node;
|
||||
typedef struct mrb_ast_node {
|
||||
struct mrb_ast_node *car, *cdr;
|
||||
uint16_t lineno, filename_index;
|
||||
} mrb_ast_node;
|
||||
|
||||
/* lexer states */
|
||||
enum mrb_lex_state_enum {
|
||||
@@ -79,6 +81,42 @@ struct mrb_parser_message {
|
||||
char* message;
|
||||
};
|
||||
|
||||
#define STR_FUNC_PARSING 0x01
|
||||
#define STR_FUNC_EXPAND 0x02
|
||||
#define STR_FUNC_REGEXP 0x04
|
||||
#define STR_FUNC_WORD 0x08
|
||||
#define STR_FUNC_SYMBOL 0x10
|
||||
#define STR_FUNC_ARRAY 0x20
|
||||
#define STR_FUNC_HEREDOC 0x40
|
||||
#define STR_FUNC_XQUOTE 0x80
|
||||
|
||||
enum mrb_string_type {
|
||||
str_not_parsing = (0),
|
||||
str_squote = (STR_FUNC_PARSING),
|
||||
str_dquote = (STR_FUNC_PARSING|STR_FUNC_EXPAND),
|
||||
str_regexp = (STR_FUNC_PARSING|STR_FUNC_REGEXP|STR_FUNC_EXPAND),
|
||||
str_sword = (STR_FUNC_PARSING|STR_FUNC_WORD|STR_FUNC_ARRAY),
|
||||
str_dword = (STR_FUNC_PARSING|STR_FUNC_WORD|STR_FUNC_ARRAY|STR_FUNC_EXPAND),
|
||||
str_ssym = (STR_FUNC_PARSING|STR_FUNC_SYMBOL),
|
||||
str_ssymbols = (STR_FUNC_PARSING|STR_FUNC_SYMBOL|STR_FUNC_ARRAY),
|
||||
str_dsymbols = (STR_FUNC_PARSING|STR_FUNC_SYMBOL|STR_FUNC_ARRAY|STR_FUNC_EXPAND),
|
||||
str_heredoc = (STR_FUNC_PARSING|STR_FUNC_HEREDOC),
|
||||
str_xquote = (STR_FUNC_PARSING|STR_FUNC_XQUOTE|STR_FUNC_EXPAND),
|
||||
};
|
||||
|
||||
/* heredoc structure */
|
||||
struct mrb_parser_heredoc_info {
|
||||
mrb_bool allow_indent:1;
|
||||
mrb_bool remove_indent:1;
|
||||
mrb_bool line_head:1;
|
||||
size_t indent;
|
||||
mrb_ast_node *indented;
|
||||
enum mrb_string_type type;
|
||||
const char *term;
|
||||
int term_len;
|
||||
mrb_ast_node *doc;
|
||||
};
|
||||
|
||||
#define MRB_PARSER_TOKBUF_MAX (UINT16_MAX-1)
|
||||
#define MRB_PARSER_TOKBUF_SIZE 256
|
||||
|
||||
@@ -104,7 +142,7 @@ struct mrb_parser_state {
|
||||
unsigned int cmdarg_stack;
|
||||
int paren_nest;
|
||||
int lpar_beg;
|
||||
int in_def, in_single, in_kwarg;
|
||||
int in_def, in_single;
|
||||
mrb_bool cmd_start:1;
|
||||
mrb_ast_node *locals;
|
||||
|
||||
@@ -126,7 +164,6 @@ struct mrb_parser_state {
|
||||
mrb_bool no_optimize:1;
|
||||
mrb_bool capture_errors:1;
|
||||
mrb_bool no_ext_ops:1;
|
||||
mrb_bool no_return_value:1;
|
||||
const struct RProc *upper;
|
||||
struct mrb_parser_message error_buffer[10];
|
||||
struct mrb_parser_message warn_buffer[10];
|
||||
@@ -135,7 +172,6 @@ struct mrb_parser_state {
|
||||
uint16_t filename_table_length;
|
||||
uint16_t current_filename_index;
|
||||
|
||||
/* Variable-sized node management */
|
||||
mrb_ast_node *nvars;
|
||||
};
|
||||
|
||||
|
||||
@@ -52,13 +52,13 @@ MRB_API mrb_irep *mrb_read_irep_buf(mrb_state*, const void*, size_t);
|
||||
/* Binary Format Version Major:Minor */
|
||||
/* Major: Incompatible to prior versions */
|
||||
/* Minor: Upper-compatible to prior versions */
|
||||
#define RITE_BINARY_MAJOR_VER "04"
|
||||
#define RITE_BINARY_MAJOR_VER "03"
|
||||
#define RITE_BINARY_MINOR_VER "00"
|
||||
#define RITE_BINARY_FORMAT_VER RITE_BINARY_MAJOR_VER RITE_BINARY_MINOR_VER
|
||||
#define RITE_COMPILER_NAME "MATZ"
|
||||
#define RITE_COMPILER_VERSION "0000"
|
||||
|
||||
#define RITE_VM_VER "0400"
|
||||
#define RITE_VM_VER "0300"
|
||||
|
||||
#define RITE_BINARY_EOF "END\0"
|
||||
#define RITE_SECTION_IREP_IDENT "IREP"
|
||||
|
||||
+4
-45
@@ -8,7 +8,6 @@
|
||||
#define MRUBY_ERROR_H
|
||||
|
||||
#include "common.h"
|
||||
#include <string.h>
|
||||
|
||||
/**
|
||||
* mruby error handling.
|
||||
@@ -37,7 +36,7 @@ MRB_API mrb_value mrb_exc_new_str(mrb_state *mrb, struct RClass* c, mrb_value st
|
||||
#define mrb_exc_new_lit(mrb, c, lit) mrb_exc_new_str(mrb, c, mrb_str_new_lit(mrb, lit))
|
||||
MRB_API mrb_noreturn void mrb_no_method_error(mrb_state *mrb, mrb_sym id, mrb_value args, const char *fmt, ...);
|
||||
|
||||
#if defined(MRB_NAN_BOXING) || defined(MRB_WORD_BOXING) || defined(MRB_64BIT)
|
||||
#if defined(MRB_64BIT) || defined(MRB_USE_FLOAT32) || defined(MRB_NAN_BOXING) || defined(MRB_WORD_BOXING)
|
||||
#undef MRB_USE_RBREAK_VALUE_UNION
|
||||
#else
|
||||
#define MRB_USE_RBREAK_VALUE_UNION 1
|
||||
@@ -54,11 +53,7 @@ struct RBreak {
|
||||
#ifndef MRB_USE_RBREAK_VALUE_UNION
|
||||
mrb_value val;
|
||||
#else
|
||||
/* Store value as uint32_t words instead of union mrb_value_union
|
||||
to avoid 8-byte alignment of int64_t/double on 32-bit platforms
|
||||
(e.g., ARM, MIPS, PowerPC) which would inflate struct size beyond
|
||||
the 5-word RVALUE limit due to padding. */
|
||||
uint32_t value[sizeof(union mrb_value_union) / sizeof(uint32_t)];
|
||||
union mrb_value_union value;
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -71,14 +66,14 @@ static inline mrb_value
|
||||
mrb_break_value_get(struct RBreak *brk)
|
||||
{
|
||||
mrb_value val;
|
||||
memcpy(&val.value, brk->value, sizeof(val.value));
|
||||
val.value = brk->value;
|
||||
val.tt = (enum mrb_vtype)(brk->flags & RBREAK_VALUE_TT_MASK);
|
||||
return val;
|
||||
}
|
||||
static inline void
|
||||
mrb_break_value_set(struct RBreak *brk, mrb_value val)
|
||||
{
|
||||
memcpy(brk->value, &val.value, sizeof(val.value));
|
||||
brk->value = val.value;
|
||||
brk->flags &= ~RBREAK_VALUE_TT_MASK;
|
||||
brk->flags |= val.tt;
|
||||
}
|
||||
@@ -132,42 +127,6 @@ MRB_API mrb_value mrb_rescue_exceptions(mrb_state *mrb, mrb_func_t body, mrb_val
|
||||
mrb_func_t rescue, mrb_value r_data,
|
||||
mrb_int len, struct RClass **classes);
|
||||
|
||||
/**
|
||||
* Calls `func` via `mrb_protect_error()` and then always executes the user block exactly once.
|
||||
* Even if a global jump (similar to a Ruby exception) occurs within `func`, the block will be executed,
|
||||
* and after the block's completion, the global jump will be re-thrown.
|
||||
*
|
||||
* By checking `mrb->exc != NULL` within the block, you can determine if a global jump occurred in `func`.
|
||||
*
|
||||
* If you want to suppress the global jump and continue processing, use `mrb_clear_error(mrb); break;`.
|
||||
*
|
||||
* - `mrb`: The mruby state reference
|
||||
* - `result_var`: Pre-defined mrb_value type variable (to receive `func`'s return value)
|
||||
* - `func`: Function to call (compatible with `mrb_protect_error_func`)
|
||||
* - `data`: User data to pass to `func`
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* mrb_value result;
|
||||
* MRB_ENSURE(mrb, result, body_func, userdata) {
|
||||
* // This block is always executed (equivalent to Ruby's ensure)
|
||||
*
|
||||
* if (mrb->exc) {
|
||||
* // Post-processing when an exception occurs
|
||||
* }
|
||||
*
|
||||
* // To ignore the global jump, use `mrb_clear_error(mrb); break;` here
|
||||
* }
|
||||
*/
|
||||
#define MRB_ENSURE(mrb, result_var, func, data) \
|
||||
for (mrb_bool MRB_UNIQNAME(_break_) = FALSE; \
|
||||
!MRB_UNIQNAME(_break_) && \
|
||||
(((result_var) = mrb_protect_error(mrb, func, data, &MRB_UNIQNAME(_break_))), \
|
||||
((mrb)->exc = (MRB_UNIQNAME(_break_) ? mrb_obj_ptr((result_var)) : NULL)), \
|
||||
TRUE); \
|
||||
(void)(MRB_UNIQNAME(_break_) && (mrb)->jmp && (mrb_exc_raise(mrb, result_var), TRUE)), \
|
||||
MRB_UNIQNAME(_break_) = TRUE)
|
||||
|
||||
MRB_END_DECL
|
||||
|
||||
#endif /* MRUBY_ERROR_H */
|
||||
|
||||
+2
-9
@@ -28,10 +28,6 @@ MRB_API void mrb_free_context(struct mrb_state *mrb, struct mrb_context *c);
|
||||
#define MRB_GC_ARENA_SIZE 100
|
||||
#endif
|
||||
|
||||
#ifndef MRB_GRAY_STACK_SIZE
|
||||
#define MRB_GRAY_STACK_SIZE 1024
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
MRB_GC_STATE_ROOT = 0,
|
||||
MRB_GC_STATE_MARK,
|
||||
@@ -42,10 +38,8 @@ typedef struct mrb_gc {
|
||||
struct mrb_heap_page *heaps; /* all heaps pages */
|
||||
struct mrb_heap_page *free_heaps;/* heaps for allocation */
|
||||
struct mrb_heap_page *sweeps; /* page where sweep starts */
|
||||
struct mrb_heap_region *regions; /* contiguous heap regions */
|
||||
struct RBasic *gray_stack[MRB_GRAY_STACK_SIZE]; /* stack of gray objects */
|
||||
size_t gray_stack_top; /* top index of gray stack */
|
||||
mrb_bool gray_overflow:1; /* gray stack overflowed; needs heap rescan */
|
||||
struct RBasic *gray_list; /* list of gray objects to be traversed incrementally */
|
||||
struct RBasic *atomic_gray_list; /* list of objects to be traversed atomically */
|
||||
size_t live; /* count of live objects */
|
||||
size_t live_after_mark; /* old generation objects */
|
||||
size_t threshold; /* threshold to start GC */
|
||||
@@ -70,7 +64,6 @@ typedef struct mrb_gc {
|
||||
} mrb_gc;
|
||||
|
||||
MRB_API mrb_bool mrb_object_dead_p(struct mrb_state *mrb, struct RBasic *object);
|
||||
MRB_API int mrb_gc_add_region(struct mrb_state *mrb, void *start, size_t size);
|
||||
|
||||
#define MRB_GC_RED 7
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
*/
|
||||
MRB_BEGIN_DECL
|
||||
|
||||
/* offset of `iv` must match struct RObject */
|
||||
/* offset of `iv` must be 3 words */
|
||||
struct RHash {
|
||||
MRB_OBJECT_HEADER;
|
||||
#ifdef MRB_64BIT
|
||||
|
||||
@@ -12,6 +12,8 @@ void mrb_ary_decref(mrb_state*, mrb_shared_array*);
|
||||
mrb_value mrb_ary_subseq(mrb_state *mrb, mrb_value ary, mrb_int beg, mrb_int len);
|
||||
#endif
|
||||
|
||||
mrb_bool mrb_inspect_recursive_p(mrb_state *mrb, mrb_value self);
|
||||
|
||||
#ifdef MRUBY_CLASS_H
|
||||
struct RClass *mrb_vm_define_class(mrb_state*, mrb_value, mrb_value, mrb_sym);
|
||||
struct RClass *mrb_vm_define_module(mrb_state*, mrb_value, mrb_sym);
|
||||
@@ -131,11 +133,6 @@ mrb_value mrb_as_rational(mrb_state *mrb, mrb_value x);
|
||||
void mrb_rational_copy(mrb_state *mrb, mrb_value x, mrb_value y);
|
||||
int mrb_rational_mark(mrb_state *mrb, struct RBasic *rat);
|
||||
#endif
|
||||
#ifdef MRB_USE_SET
|
||||
size_t mrb_gc_mark_set(mrb_state *mrb, struct RBasic *set);
|
||||
void mrb_gc_free_set(mrb_state *mrb, struct RBasic *set);
|
||||
size_t mrb_set_memsize(mrb_value);
|
||||
#endif
|
||||
|
||||
#ifdef MRUBY_PROC_H
|
||||
struct RProc *mrb_closure_new(mrb_state*, const mrb_irep*);
|
||||
@@ -163,13 +160,9 @@ mrb_value mrb_str_inspect(mrb_state *mrb, mrb_value str);
|
||||
mrb_bool mrb_str_beg_len(mrb_int str_len, mrb_int *begp, mrb_int *lenp);
|
||||
mrb_value mrb_str_byte_subseq(mrb_state *mrb, mrb_value str, mrb_int beg, mrb_int len);
|
||||
mrb_value mrb_str_aref(mrb_state *mrb, mrb_value str, mrb_value idx, mrb_value len);
|
||||
mrb_bool mrb_strcasecmp_p(const char *s1, mrb_int len1, const char *s2, mrb_int len2);
|
||||
#define MRB_STR_CASECMP_P(str, lit) \
|
||||
mrb_strcasecmp_p(RSTRING_PTR(str), RSTRING_LEN(str), lit, sizeof(lit"")-1)
|
||||
uint32_t mrb_byte_hash(const uint8_t*, mrb_int);
|
||||
uint32_t mrb_byte_hash_step(const uint8_t*, mrb_int, uint32_t);
|
||||
|
||||
mrb_int mrb_utf8_to_buf(char *buf, uint32_t cp);
|
||||
#ifdef MRB_UTF8_STRING
|
||||
mrb_int mrb_utf8len(const char *str, const char *end);
|
||||
mrb_int mrb_utf8_strlen(const char *str, mrb_int byte_len);
|
||||
@@ -182,6 +175,7 @@ mrb_value mrb_vm_cv_get(mrb_state*, mrb_sym);
|
||||
void mrb_vm_cv_set(mrb_state*, mrb_sym, mrb_value);
|
||||
mrb_value mrb_vm_const_get(mrb_state*, mrb_sym);
|
||||
size_t mrb_obj_iv_tbl_memsize(mrb_value);
|
||||
mrb_value mrb_obj_iv_inspect(mrb_state*, struct RObject*);
|
||||
void mrb_obj_iv_set_force(mrb_state *mrb, struct RObject *obj, mrb_sym sym, mrb_value v);
|
||||
mrb_value mrb_mod_constants(mrb_state *mrb, mrb_value mod);
|
||||
mrb_value mrb_mod_const_at(mrb_state *mrb, struct RClass *c, mrb_value ary);
|
||||
@@ -199,10 +193,6 @@ void mrb_gc_free_gv(mrb_state*);
|
||||
size_t mrb_gc_mark_iv(mrb_state*, struct RObject*);
|
||||
void mrb_gc_free_iv(mrb_state*, struct RObject*);
|
||||
|
||||
/* IV shape tree */
|
||||
void mrb_init_shape(mrb_state*);
|
||||
void mrb_free_shape(mrb_state*);
|
||||
|
||||
/* VM */
|
||||
#define MRB_CI_VISIBILITY(ci) MRB_FLAGS_GET((ci)->vis, 0, 2)
|
||||
#define MRB_CI_SET_VISIBILITY(ci, visi) MRB_FLAGS_SET((ci)->vis, 0, 2, visi)
|
||||
@@ -266,17 +256,6 @@ void mrb_bint_copy(mrb_state *mrb, mrb_value x, mrb_value y);
|
||||
size_t mrb_bint_memsize(mrb_value x);
|
||||
mrb_value mrb_bint_hash(mrb_state *mrb, mrb_value x);
|
||||
mrb_value mrb_bint_sqrt(mrb_state *mrb, mrb_value x);
|
||||
mrb_int mrb_bint_size(mrb_state *mrb, mrb_value bint);
|
||||
mrb_value mrb_bint_from_bytes(mrb_state *mrb, const uint8_t *bytes, mrb_int len);
|
||||
mrb_int mrb_bint_sign(mrb_state *mrb, mrb_value bint);
|
||||
mrb_value mrb_bint_gcd(mrb_state *mrb, mrb_value x, mrb_value y);
|
||||
mrb_value mrb_bint_lcm(mrb_state *mrb, mrb_value x, mrb_value y);
|
||||
mrb_value mrb_bint_abs(mrb_state *mrb, mrb_value x);
|
||||
#endif
|
||||
|
||||
#ifdef MRB_USE_TASK_SCHEDULER
|
||||
/* GC marking for task scheduler */
|
||||
void mrb_task_mark_all(mrb_state *mrb);
|
||||
#endif
|
||||
|
||||
#endif /* MRUBY_INTERNAL_H */
|
||||
|
||||
@@ -82,7 +82,6 @@ struct mrb_irep {
|
||||
#define MRB_ISEQ_NO_FREE 1
|
||||
#define MRB_IREP_NO_FREE 2
|
||||
#define MRB_IREP_STATIC (MRB_ISEQ_NO_FREE | MRB_IREP_NO_FREE)
|
||||
#define MRB_IREP_CONSOLIDATED 4 /* pool/syms/reps packed with irep struct */
|
||||
|
||||
MRB_API mrb_irep *mrb_add_irep(mrb_state *mrb);
|
||||
|
||||
|
||||
+111
-262
@@ -20,13 +20,12 @@ MRB_BEGIN_DECL
|
||||
typedef uint32_t khint_t;
|
||||
typedef khint_t khiter_t;
|
||||
|
||||
#ifndef KHASH_INITIAL_SIZE
|
||||
# define KHASH_INITIAL_SIZE 8
|
||||
#ifndef KHASH_DEFAULT_SIZE
|
||||
# define KHASH_DEFAULT_SIZE 8
|
||||
#endif
|
||||
#define KHASH_MIN_SIZE 8
|
||||
#define KHASH_SMALL_LIMIT 4
|
||||
|
||||
#define KH_UPPER_BOUND(x) ((x) - ((x)>>3)) /* 87.5% load factor */
|
||||
#define UPPER_BOUND(x) ((x)>>2|(x)>>1)
|
||||
|
||||
/* extern uint8_t __m[]; */
|
||||
|
||||
@@ -49,21 +48,7 @@ static const uint8_t __m_either[] = {0x03, 0x0c, 0x30, 0xc0};
|
||||
v++;\
|
||||
} while (0)
|
||||
#define khash_mask(h) ((h)->n_buckets-1)
|
||||
#define khash_upper_bound(h) (KH_UPPER_BOUND((h)->n_buckets))
|
||||
|
||||
/* BREAKING CHANGE: khash structure optimized for 50% memory reduction
|
||||
*
|
||||
* The structure now uses a single data pointer instead of separate keys,
|
||||
* vals, and ed_flags pointers, reducing size from 32 to 16 bytes.
|
||||
*
|
||||
* MIGRATION REQUIRED for field access macros:
|
||||
* - OLD: kh_key(h, x) NEW: kh_key(typename, h, x)
|
||||
* - OLD: kh_val(h, x) NEW: kh_val(typename, h, x)
|
||||
* - OLD: kh_exist(h, x) NEW: kh_exist(typename, h, x)
|
||||
* - OLD: KHASH_FOREACH() NEW: KHASH_FOREACH(typename, ...)
|
||||
*
|
||||
* Function-style macros (kh_get, kh_put, etc.) remain unchanged.
|
||||
*/
|
||||
#define khash_upper_bound(h) (UPPER_BOUND((h)->n_buckets))
|
||||
|
||||
/* declare struct kh_xxx and kh_xxx_funcs
|
||||
|
||||
@@ -74,22 +59,13 @@ static const uint8_t __m_either[] = {0x03, 0x0c, 0x30, 0xc0};
|
||||
*/
|
||||
#define KHASH_DECLARE(name, khkey_t, khval_t, kh_is_map) \
|
||||
typedef struct kh_##name { \
|
||||
void *data; /* Single allocation: [keys][vals][flags] */ \
|
||||
khint_t n_buckets; /* Number of buckets (power of 2) */ \
|
||||
khint_t size; /* Number of elements */ \
|
||||
khint_t n_buckets; \
|
||||
khint_t size; \
|
||||
uint8_t *ed_flags; \
|
||||
khkey_t *keys; \
|
||||
khval_t *vals; \
|
||||
} kh_##name##_t; \
|
||||
/* Address calculation functions for optimized memory layout */ \
|
||||
static inline khkey_t* kh_keys_##name(const kh_##name##_t *h) { \
|
||||
return (khkey_t*)(h)->data; \
|
||||
} \
|
||||
static inline khval_t* kh_vals_##name(const kh_##name##_t *h) { \
|
||||
return kh_is_map ? \
|
||||
(khval_t*)((uint8_t*)(h)->data + sizeof(khkey_t) * (h)->n_buckets) : NULL; \
|
||||
} \
|
||||
static inline uint8_t* kh_flags_##name(const kh_##name##_t *h) { \
|
||||
return (uint8_t*)(h)->data + sizeof(khkey_t) * (h)->n_buckets + \
|
||||
(kh_is_map ? sizeof(khval_t) * (h)->n_buckets : 0); \
|
||||
} \
|
||||
void kh_alloc_##name(mrb_state *mrb, kh_##name##_t *h); \
|
||||
kh_##name##_t *kh_init_##name##_size(mrb_state *mrb, khint_t size); \
|
||||
kh_##name##_t *kh_init_##name(mrb_state *mrb); \
|
||||
void kh_destroy_##name(mrb_state *mrb, kh_##name##_t *h); \
|
||||
@@ -98,10 +74,15 @@ static const uint8_t __m_either[] = {0x03, 0x0c, 0x30, 0xc0};
|
||||
khint_t kh_put_##name(mrb_state *mrb, kh_##name##_t *h, khkey_t key, int *ret); \
|
||||
void kh_resize_##name(mrb_state *mrb, kh_##name##_t *h, khint_t new_n_buckets); \
|
||||
void kh_del_##name(mrb_state *mrb, kh_##name##_t *h, khint_t x); \
|
||||
kh_##name##_t *kh_copy_##name(mrb_state *mrb, kh_##name##_t *h); \
|
||||
void kh_init_data_##name(mrb_state *mrb, kh_##name##_t *h, khint_t size); \
|
||||
void kh_destroy_data_##name(mrb_state *mrb, kh_##name##_t *h); \
|
||||
void kh_replace_##name(mrb_state *mrb, kh_##name##_t *dst, const kh_##name##_t *src);
|
||||
kh_##name##_t *kh_copy_##name(mrb_state *mrb, kh_##name##_t *h);
|
||||
|
||||
static inline void
|
||||
kh_fill_flags(uint8_t *p, uint8_t c, size_t len)
|
||||
{
|
||||
while (len-- > 0) {
|
||||
*p++ = c;
|
||||
}
|
||||
}
|
||||
|
||||
/* define kh_xxx_funcs
|
||||
|
||||
@@ -114,136 +95,64 @@ static const uint8_t __m_either[] = {0x03, 0x0c, 0x30, 0xc0};
|
||||
*/
|
||||
#define KHASH_DEFINE(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
|
||||
mrb_noreturn void mrb_raise_nomemory(mrb_state *mrb); \
|
||||
/* Internal helper functions */ \
|
||||
static inline size_t kh__kv_size_##name(khint_t count) { \
|
||||
return sizeof(khkey_t) * count + \
|
||||
(kh_is_map ? sizeof(khval_t) * count : 0); \
|
||||
} \
|
||||
static inline size_t kh__htable_size_##name(khint_t n_buckets) { \
|
||||
return kh__kv_size_##name(n_buckets) + n_buckets / 4; \
|
||||
} \
|
||||
static inline void kh__mark_occupied_##name(kh_##name##_t *h, khint_t i) { \
|
||||
uint8_t *flags = kh_flags_##name(h); \
|
||||
flags[i/4] &= ~__m_either[i%4]; /* Clear both empty and deleted bits */ \
|
||||
} \
|
||||
static inline void kh__mark_deleted_##name(kh_##name##_t *h, khint_t i) { \
|
||||
uint8_t *flags = kh_flags_##name(h); \
|
||||
flags[i/4] |= __m_del[i%4]; /* Set deleted bit */ \
|
||||
} \
|
||||
static inline khint_t kh__key_idx_##name(mrb_state *mrb, khkey_t key, kh_##name##_t *h) { \
|
||||
return __hash_func(mrb, key) & khash_mask(h); \
|
||||
} \
|
||||
static inline khint_t kh__next_probe_##name(khint_t k, khint_t *step, kh_##name##_t *h) { \
|
||||
return (k+(++(*step))) & khash_mask(h); \
|
||||
} \
|
||||
static inline khint_t kh__insert_key_##name(kh_##name##_t *h, khint_t index, khkey_t key) { \
|
||||
khkey_t *keys = kh_keys_##name(h); \
|
||||
keys[index] = key; \
|
||||
kh__mark_occupied_##name(h, index); \
|
||||
h->size++; \
|
||||
return index; \
|
||||
} \
|
||||
static inline void kh__clear_flags_##name(kh_##name##_t *h, khint_t n_buckets) { \
|
||||
memset(kh_flags_##name(h), 0xaa, n_buckets/4); \
|
||||
} \
|
||||
static inline void kh__alloc_##name(mrb_state *mrb, kh_##name##_t *h) { \
|
||||
int kh_alloc_simple_##name(mrb_state *mrb, kh_##name##_t *h) \
|
||||
{ \
|
||||
khint_t sz = h->n_buckets; \
|
||||
uint8_t *p = (uint8_t*)mrb_malloc(mrb, kh__htable_size_##name(sz)); \
|
||||
size_t len = sizeof(khkey_t) + (kh_is_map ? sizeof(khval_t) : 0); \
|
||||
uint8_t *p = (uint8_t*)mrb_malloc_simple(mrb, sizeof(uint8_t)*sz/4+len*sz); \
|
||||
if (!p) { return 1; } \
|
||||
h->size = 0; \
|
||||
h->data = p; /* Single data pointer for optimized layout */ \
|
||||
kh__clear_flags_##name(h, sz); \
|
||||
h->keys = (khkey_t*)p; \
|
||||
h->vals = kh_is_map ? (khval_t*)(p+sizeof(khkey_t)*sz) : NULL; \
|
||||
h->ed_flags = p+len*sz; \
|
||||
kh_fill_flags(h->ed_flags, 0xaa, sz/4); \
|
||||
return 0; \
|
||||
} \
|
||||
/* Small table optimization functions */ \
|
||||
static inline int kh__is_small_##name(const kh_##name##_t *h) { \
|
||||
return h->n_buckets == 0; /* Small table marker */ \
|
||||
} \
|
||||
static inline khint_t kh__get_small_##name(mrb_state *mrb, kh_##name##_t *h, khkey_t key) { \
|
||||
khkey_t *keys = kh_keys_##name(h); \
|
||||
for (khint_t i = 0; i < h->size; i++) { \
|
||||
if (__hash_equal(mrb, keys[i], key)) return i; \
|
||||
void kh_alloc_##name(mrb_state *mrb, kh_##name##_t *h) \
|
||||
{ \
|
||||
if (kh_alloc_simple_##name(mrb, h)) { \
|
||||
mrb_raise_nomemory(mrb); \
|
||||
} \
|
||||
return h->size; /* Not found - return end position */ \
|
||||
} \
|
||||
static inline void kh__rebuild_##name(mrb_state *mrb, kh_##name##_t *h, khint_t new_n_buckets) { \
|
||||
kh_##name##_t hh; \
|
||||
hh.data = NULL; \
|
||||
hh.size = 0; \
|
||||
kh_init_data_##name(mrb, &hh, new_n_buckets); \
|
||||
/* Rehash from old 'h' to 'hh' */ \
|
||||
khkey_t *old_keys = kh_keys_##name(h); \
|
||||
khval_t *old_vals = kh_vals_##name(h); \
|
||||
uint8_t *old_flags = kh__is_small_##name(h) ? NULL : kh_flags_##name(h); \
|
||||
khint_t limit = old_flags ? h->n_buckets : h->size; \
|
||||
for (khint_t i = 0; i < limit; i++) { \
|
||||
if (old_flags && __ac_iseither(old_flags, i)) continue; \
|
||||
khint_t k = kh_put_##name(mrb, &hh, old_keys[i], NULL); \
|
||||
if (kh_is_map) { \
|
||||
kh_val(name, &hh, k) = old_vals[i]; \
|
||||
} \
|
||||
} \
|
||||
/* Final Swap */ \
|
||||
mrb_free(mrb, h->data); \
|
||||
h->data = hh.data; \
|
||||
h->n_buckets = hh.n_buckets; \
|
||||
h->size = hh.size; \
|
||||
} \
|
||||
static inline khint_t kh__put_small_##name(mrb_state *mrb, kh_##name##_t *h, khkey_t key, int *ret) { \
|
||||
/* First check if key exists */ \
|
||||
khint_t pos = kh__get_small_##name(mrb, h, key); \
|
||||
if (pos < h->size) { \
|
||||
if (ret) *ret = 0; /* Key exists */ \
|
||||
return pos; \
|
||||
} \
|
||||
/* Check if we need to convert to hash table */ \
|
||||
if (h->size >= KHASH_SMALL_LIMIT) { \
|
||||
/* Convert from small table to hash table */ \
|
||||
kh__rebuild_##name(mrb, h, KHASH_MIN_SIZE); \
|
||||
/* Now add the new key using regular hash table */ \
|
||||
return kh_put_##name(mrb, h, key, ret); \
|
||||
} \
|
||||
/* Add new element to small table */ \
|
||||
khkey_t *keys = kh_keys_##name(h); \
|
||||
keys[h->size] = key; \
|
||||
h->size++; \
|
||||
if (ret) *ret = 1; /* New key */ \
|
||||
return h->size - 1; \
|
||||
} \
|
||||
kh_##name##_t *kh_init_##name##_size(mrb_state *mrb, khint_t size) { \
|
||||
kh_##name##_t *h = (kh_##name##_t*)mrb_calloc(mrb, 1, sizeof(kh_##name##_t)); \
|
||||
kh_init_data_##name(mrb, h, size); \
|
||||
if (size < KHASH_MIN_SIZE) \
|
||||
size = KHASH_MIN_SIZE; \
|
||||
khash_power2(size); \
|
||||
h->n_buckets = size; \
|
||||
if (kh_alloc_simple_##name(mrb, h)) { \
|
||||
mrb_free(mrb, h); \
|
||||
mrb_raise_nomemory(mrb); \
|
||||
} \
|
||||
return h; \
|
||||
} \
|
||||
kh_##name##_t *kh_init_##name(mrb_state *mrb) { \
|
||||
return kh_init_##name##_size(mrb, KHASH_INITIAL_SIZE); \
|
||||
return kh_init_##name##_size(mrb, KHASH_DEFAULT_SIZE); \
|
||||
} \
|
||||
void kh_destroy_##name(mrb_state *mrb, kh_##name##_t *h) \
|
||||
{ \
|
||||
kh_destroy_data_##name(mrb, h); \
|
||||
mrb_free(mrb, h); \
|
||||
if (h) { \
|
||||
mrb_free(mrb, h->keys); \
|
||||
mrb_free(mrb, h); \
|
||||
} \
|
||||
} \
|
||||
void kh_clear_##name(mrb_state *mrb, kh_##name##_t *h) \
|
||||
{ \
|
||||
(void)mrb; \
|
||||
if (h && h->data) { \
|
||||
kh__clear_flags_##name(h, h->n_buckets); \
|
||||
if (h && h->ed_flags) { \
|
||||
kh_fill_flags(h->ed_flags, 0xaa, h->n_buckets/4); \
|
||||
h->size = 0; \
|
||||
} \
|
||||
} \
|
||||
khint_t kh_get_##name(mrb_state *mrb, kh_##name##_t *h, khkey_t key) \
|
||||
{ \
|
||||
if (kh__is_small_##name(h)) { \
|
||||
return kh__get_small_##name(mrb, h, key); \
|
||||
} \
|
||||
/* Cache calculated pointers for performance */ \
|
||||
khkey_t *keys = kh_keys_##name(h); \
|
||||
uint8_t *ed_flags = kh_flags_##name(h); \
|
||||
khint_t k = kh__key_idx_##name(mrb, key, h), step = 0; \
|
||||
khint_t k = __hash_func(mrb,key) & khash_mask(h), step = 0; \
|
||||
(void)mrb; \
|
||||
while (!__ac_isempty(ed_flags, k)) { \
|
||||
if (!__ac_isdel(ed_flags, k)) { \
|
||||
if (__hash_equal(mrb, keys[k], key)) return k; \
|
||||
while (!__ac_isempty(h->ed_flags, k)) { \
|
||||
if (!__ac_isdel(h->ed_flags, k)) { \
|
||||
if (__hash_equal(mrb,h->keys[k], key)) return k; \
|
||||
} \
|
||||
k = kh__next_probe_##name(k, &step, h); \
|
||||
k = (k+(++step)) & khash_mask(h); \
|
||||
} \
|
||||
return kh_end(h); \
|
||||
} \
|
||||
@@ -252,25 +161,38 @@ static const uint8_t __m_either[] = {0x03, 0x0c, 0x30, 0xc0};
|
||||
if (new_n_buckets < KHASH_MIN_SIZE) \
|
||||
new_n_buckets = KHASH_MIN_SIZE; \
|
||||
khash_power2(new_n_buckets); \
|
||||
kh__rebuild_##name(mrb, h, new_n_buckets); \
|
||||
{ \
|
||||
kh_##name##_t hh; \
|
||||
uint8_t *old_ed_flags = h->ed_flags; \
|
||||
khkey_t *old_keys = h->keys; \
|
||||
khval_t *old_vals = h->vals; \
|
||||
khint_t old_n_buckets = h->n_buckets; \
|
||||
khint_t i; \
|
||||
hh.n_buckets = new_n_buckets; \
|
||||
kh_alloc_##name(mrb, &hh); \
|
||||
/* relocate */ \
|
||||
for (i=0; i<old_n_buckets; i++) { \
|
||||
if (!__ac_iseither(old_ed_flags, i)) { \
|
||||
khint_t k = kh_put_##name(mrb, &hh, old_keys[i], NULL); \
|
||||
if (kh_is_map) kh_value(&hh,k) = old_vals[i]; \
|
||||
} \
|
||||
} \
|
||||
/* copy hh to h */ \
|
||||
*h = hh; \
|
||||
mrb_free(mrb, old_keys); \
|
||||
} \
|
||||
} \
|
||||
khint_t kh_put_##name(mrb_state *mrb, kh_##name##_t *h, khkey_t key, int *ret) \
|
||||
{ \
|
||||
if (kh__is_small_##name(h)) { \
|
||||
return kh__put_small_##name(mrb, h, key, ret); \
|
||||
} \
|
||||
khint_t k, del_k, step = 0; \
|
||||
if (h->size >= khash_upper_bound(h)) { \
|
||||
kh_resize_##name(mrb, h, h->n_buckets*2); \
|
||||
} \
|
||||
/* Cache calculated pointers for performance */ \
|
||||
khkey_t *keys = kh_keys_##name(h); \
|
||||
uint8_t *ed_flags = kh_flags_##name(h); \
|
||||
k = kh__key_idx_##name(mrb, key, h); \
|
||||
k = __hash_func(mrb,key) & khash_mask(h); \
|
||||
del_k = kh_end(h); \
|
||||
while (!__ac_isempty(ed_flags, k)) { \
|
||||
if (!__ac_isdel(ed_flags, k)) { \
|
||||
if (__hash_equal(mrb, keys[k], key)) { \
|
||||
while (!__ac_isempty(h->ed_flags, k)) { \
|
||||
if (!__ac_isdel(h->ed_flags, k)) { \
|
||||
if (__hash_equal(mrb,h->keys[k], key)) { \
|
||||
if (ret) *ret = 0; \
|
||||
return k; \
|
||||
} \
|
||||
@@ -278,17 +200,21 @@ static const uint8_t __m_either[] = {0x03, 0x0c, 0x30, 0xc0};
|
||||
else if (del_k == kh_end(h)) { \
|
||||
del_k = k; \
|
||||
} \
|
||||
k = kh__next_probe_##name(k, &step, h); \
|
||||
k = (k+(++step)) & khash_mask(h); \
|
||||
} \
|
||||
if (del_k != kh_end(h)) { \
|
||||
/* put at del */ \
|
||||
kh__insert_key_##name(h, del_k, key); \
|
||||
h->keys[del_k] = key; \
|
||||
h->ed_flags[del_k/4] &= ~__m_del[del_k%4]; \
|
||||
h->size++; \
|
||||
if (ret) *ret = 2; \
|
||||
return del_k; \
|
||||
} \
|
||||
else { \
|
||||
/* put at empty */ \
|
||||
kh__insert_key_##name(h, k, key); \
|
||||
h->keys[k] = key; \
|
||||
h->ed_flags[k/4] &= ~__m_empty[k%4]; \
|
||||
h->size++; \
|
||||
if (ret) *ret = 1; \
|
||||
return k; \
|
||||
} \
|
||||
@@ -296,81 +222,23 @@ static const uint8_t __m_either[] = {0x03, 0x0c, 0x30, 0xc0};
|
||||
void kh_del_##name(mrb_state *mrb, kh_##name##_t *h, khint_t x) \
|
||||
{ \
|
||||
(void)mrb; \
|
||||
if (kh__is_small_##name(h)) { \
|
||||
/* Small table deletion: shift elements down */ \
|
||||
mrb_assert(x < h->size); \
|
||||
khkey_t *keys = kh_keys_##name(h); \
|
||||
khval_t *vals = kh_vals_##name(h); \
|
||||
for (khint_t i = x; i < h->size - 1; i++) { \
|
||||
keys[i] = keys[i + 1]; \
|
||||
if (kh_is_map) vals[i] = vals[i + 1]; \
|
||||
} \
|
||||
h->size--; \
|
||||
} \
|
||||
else { \
|
||||
/* Regular hash table deletion */ \
|
||||
mrb_assert(x != h->n_buckets && !__ac_iseither(kh_flags_##name(h), x)); \
|
||||
kh__mark_deleted_##name(h, x); \
|
||||
h->size--; \
|
||||
} \
|
||||
mrb_assert(x != h->n_buckets && !__ac_iseither(h->ed_flags, x)); \
|
||||
h->ed_flags[x/4] |= __m_del[x%4]; \
|
||||
h->size--; \
|
||||
} \
|
||||
kh_##name##_t *kh_copy_##name(mrb_state *mrb, kh_##name##_t *h) \
|
||||
{ \
|
||||
kh_##name##_t *h2 = (kh_##name##_t*)mrb_calloc(mrb, 1, sizeof(kh_##name##_t)); \
|
||||
kh_replace_##name(mrb, h2, h); \
|
||||
kh_##name##_t *h2; \
|
||||
khiter_t k, k2; \
|
||||
\
|
||||
h2 = kh_init_##name(mrb); \
|
||||
for (k = kh_begin(h); k != kh_end(h); k++) { \
|
||||
if (kh_exist(h, k)) { \
|
||||
k2 = kh_put_##name(mrb, h2, kh_key(h, k), NULL); \
|
||||
if (kh_is_map) kh_value(h2, k2) = kh_value(h, k); \
|
||||
} \
|
||||
} \
|
||||
return h2; \
|
||||
} \
|
||||
void kh_init_data_##name(mrb_state *mrb, kh_##name##_t *h, khint_t size) { \
|
||||
if (size <= KHASH_SMALL_LIMIT) { \
|
||||
/* Start as small table */ \
|
||||
h->n_buckets = 0; /* Small table marker */ \
|
||||
h->data = mrb_malloc(mrb, kh__kv_size_##name(KHASH_SMALL_LIMIT)); \
|
||||
h->size = 0; \
|
||||
} \
|
||||
else { \
|
||||
/* Start as regular hash table */ \
|
||||
if (size < KHASH_MIN_SIZE) \
|
||||
size = KHASH_MIN_SIZE; \
|
||||
khash_power2(size); \
|
||||
h->n_buckets = size; \
|
||||
kh__alloc_##name(mrb, h); \
|
||||
} \
|
||||
} \
|
||||
void kh_destroy_data_##name(mrb_state *mrb, kh_##name##_t *h) \
|
||||
{ \
|
||||
if (h && h->data) { \
|
||||
mrb_free(mrb, h->data); /* Free only the data allocation */ \
|
||||
h->data = NULL; \
|
||||
} \
|
||||
} \
|
||||
void kh_replace_##name(mrb_state *mrb, kh_##name##_t *dst, const kh_##name##_t *src) \
|
||||
{ \
|
||||
if (!src || (src->n_buckets == 0 && src->size == 0)) { \
|
||||
/* Empty source */ \
|
||||
kh_destroy_data_##name(mrb, dst); \
|
||||
dst->data = NULL; \
|
||||
dst->n_buckets = 0; \
|
||||
dst->size = 0; \
|
||||
} \
|
||||
else if (src->n_buckets == 0) { \
|
||||
/* Small table case */ \
|
||||
size_t data_size = kh__kv_size_##name(KHASH_SMALL_LIMIT); \
|
||||
dst->data = mrb_realloc(mrb, dst->data, data_size); \
|
||||
dst->size = src->size; \
|
||||
dst->n_buckets = 0; \
|
||||
/* Copy only the used portion of keys and values */ \
|
||||
size_t copy_size = kh__kv_size_##name(src->size); \
|
||||
memcpy(dst->data, src->data, copy_size); \
|
||||
} \
|
||||
else { \
|
||||
/* Regular hash table case */ \
|
||||
size_t data_size = kh__htable_size_##name(src->n_buckets); \
|
||||
dst->data = mrb_realloc(mrb, dst->data, data_size); \
|
||||
dst->size = src->size; \
|
||||
dst->n_buckets = src->n_buckets; \
|
||||
/* Copy the entire data block: [keys][vals][flags] */ \
|
||||
memcpy(dst->data, src->data, data_size); \
|
||||
} \
|
||||
}
|
||||
|
||||
|
||||
@@ -386,28 +254,13 @@ static const uint8_t __m_either[] = {0x03, 0x0c, 0x30, 0xc0};
|
||||
#define kh_get(name, mrb, h, k) kh_get_##name(mrb, h, k)
|
||||
#define kh_del(name, mrb, h, k) kh_del_##name(mrb, h, k)
|
||||
#define kh_copy(name, mrb, h) kh_copy_##name(mrb, h)
|
||||
#define kh_init_data(name, mrb, h, size) kh_init_data_##name(mrb, h, size)
|
||||
#define kh_destroy_data(name, mrb, h) kh_destroy_data_##name(mrb, h)
|
||||
#define kh_replace(name, mrb, dst, src) kh_replace_##name(mrb, dst, src)
|
||||
|
||||
/* BREAKING CHANGE: Field access macros now require type name as first parameter
|
||||
* The macros keep their familiar names but now need the hash type name.
|
||||
*
|
||||
* MIGRATION: Add type name as first parameter:
|
||||
* kh_key(h, x) -> kh_key(typename, h, x)
|
||||
* kh_val(h, x) -> kh_val(typename, h, x)
|
||||
* kh_exist(h, x) -> kh_exist(typename, h, x)
|
||||
* kh_value(h, x) -> kh_value(typename, h, x)
|
||||
*/
|
||||
|
||||
/* Type-aware access macros - same names, now with type parameter */
|
||||
#define kh_exist(name, h, x) ((h)->n_buckets == 0 ? ((x) < (h)->size) : (!__ac_iseither(kh_flags_##name(h), (x))))
|
||||
#define kh_key(name, h, x) (kh_keys_##name(h)[x])
|
||||
#define kh_val(name, h, x) (kh_vals_##name(h)[x])
|
||||
#define kh_value(name, h, x) (kh_vals_##name(h)[x])
|
||||
#define kh_exist(h, x) (!__ac_iseither((h)->ed_flags, (x)))
|
||||
#define kh_key(h, x) ((h)->keys[x])
|
||||
#define kh_val(h, x) ((h)->vals[x])
|
||||
#define kh_value(h, x) ((h)->vals[x])
|
||||
#define kh_begin(h) (khint_t)(0)
|
||||
#define kh_end(h) ((h)->n_buckets == 0 ? (h)->size : (h)->n_buckets)
|
||||
#define kh_is_end(h, i) ((i) >= kh_end(h))
|
||||
#define kh_end(h) ((h)->n_buckets)
|
||||
#define kh_size(h) ((h)->size)
|
||||
#define kh_n_buckets(h) ((h)->n_buckets)
|
||||
|
||||
@@ -432,24 +285,20 @@ MRB_END_DECL
|
||||
* Macro for iterating over all elements in a khash.
|
||||
*
|
||||
* Usage:
|
||||
* KHASH_FOREACH(typename, kh, k) {
|
||||
* KHASH_FOREACH(mrb, kh, k) {
|
||||
* // k is the khiter_t iterator
|
||||
* // Access the key with kh_key(typename, kh, k)
|
||||
* // Access the value with kh_val(typename, kh, k) if applicable
|
||||
* // Access the key with kh_key(kh, k)
|
||||
* // Access the value with kh_val(kh, k) if applicable
|
||||
* // Your code here
|
||||
* }
|
||||
*
|
||||
* @param name The hash type name
|
||||
* @param kh The khash to iterate over
|
||||
* @param k The name to use for the khiter_t iterator variable
|
||||
* @param mrb The mrb_state
|
||||
* @param kh The khash to iterate over
|
||||
* @param k The name to use for the khiter_t iterator variable
|
||||
*/
|
||||
/* BREAKING CHANGE: KHASH_FOREACH now requires type name as first parameter
|
||||
* OLD: KHASH_FOREACH(mrb, kh, k)
|
||||
* NEW: KHASH_FOREACH(name, kh, k)
|
||||
*/
|
||||
#define KHASH_FOREACH(name, kh, k) \
|
||||
#define KHASH_FOREACH(mrb, kh, k) \
|
||||
if (kh) \
|
||||
for (khiter_t k = kh_begin(kh); !kh_is_end(kh, k); k++) \
|
||||
if (kh_exist(name, kh, k))
|
||||
for (khiter_t k = kh_begin(kh); k != kh_end(kh); k++) \
|
||||
if (kh_exist(kh, k))
|
||||
|
||||
#endif /* MRUBY_KHASH_H */
|
||||
|
||||
@@ -119,12 +119,12 @@ mrb_int_mul_overflow(mrb_int a, mrb_int b, mrb_int *c)
|
||||
*c = (mrb_int)n;
|
||||
return n > MRB_INT_MAX || n < MRB_INT_MIN;
|
||||
#else /* MRB_INT64 */
|
||||
*c = a * b;
|
||||
if (a > 0 && b > 0 && a > MRB_INT_MAX / b) return TRUE;
|
||||
if (a < 0 && b > 0 && a < MRB_INT_MIN / b) return TRUE;
|
||||
if (a > 0 && b < 0 && b < MRB_INT_MIN / a) return TRUE;
|
||||
if (a < 0 && b < 0 && (a <= MRB_INT_MIN || b <= MRB_INT_MIN || -a > MRB_INT_MAX / -b))
|
||||
return TRUE;
|
||||
*c = a * b;
|
||||
return FALSE;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#define MRB_OBJECT_HEADER \
|
||||
struct RClass *c; \
|
||||
struct RBasic *gcnext; \
|
||||
enum mrb_vtype tt:8; \
|
||||
unsigned int gc_color:3; \
|
||||
unsigned int frozen:1; \
|
||||
@@ -24,13 +25,6 @@ struct RBasic {
|
||||
#define MRB_OBJ_IS_FROZEN 1
|
||||
#define mrb_frozen_p(o) ((o)->frozen)
|
||||
|
||||
/* Object shape flag -- when set, obj->iv is shaped, not iv_tbl* */
|
||||
/* Bit 5: avoids conflict with MRB_INSTANCE_TT_MASK (bits 0-4);
|
||||
but conflicts with MRB_HASH_AR_EA_N_USED on 32-bit, so the
|
||||
predicate must also check tt to avoid false positives */
|
||||
#define MRB_FL_OBJ_SHAPED (1 << 5)
|
||||
#define MRB_OBJ_SHAPED_P(o) ((o)->tt == MRB_TT_OBJECT && ((o)->flags & MRB_FL_OBJ_SHAPED))
|
||||
|
||||
struct RObject {
|
||||
MRB_OBJECT_HEADER;
|
||||
struct iv_tbl *iv;
|
||||
@@ -45,7 +39,7 @@ struct RFiber {
|
||||
};
|
||||
|
||||
#define mrb_static_assert_object_size(st) \
|
||||
mrb_static_assert(sizeof(st) <= sizeof(void*) * 5, \
|
||||
#st " size must be within 5 words")
|
||||
mrb_static_assert(sizeof(st) <= sizeof(void*) * 6, \
|
||||
#st " size must be within 6 words")
|
||||
|
||||
#endif /* MRUBY_OBJECT_H */
|
||||
|
||||
@@ -13,10 +13,6 @@ enum mrb_insn {
|
||||
#undef OPCODE
|
||||
};
|
||||
|
||||
/* backward compatibility aliases */
|
||||
#define OP_LOADT OP_LOADTRUE
|
||||
#define OP_LOADF OP_LOADFALSE
|
||||
|
||||
#define OP_L_STRICT 1
|
||||
#define OP_L_CAPTURE 2
|
||||
#define OP_L_METHOD OP_L_STRICT
|
||||
|
||||
+3
-16
@@ -31,8 +31,8 @@ OPCODE(LOADI32, BSS) /* R[a] = mrb_int((b<<16)+c) */
|
||||
OPCODE(LOADSYM, BB) /* R[a] = Syms[b] */
|
||||
OPCODE(LOADNIL, B) /* R[a] = nil */
|
||||
OPCODE(LOADSELF, B) /* R[a] = self */
|
||||
OPCODE(LOADTRUE, B) /* R[a] = true */
|
||||
OPCODE(LOADFALSE, B) /* R[a] = false */
|
||||
OPCODE(LOADT, B) /* R[a] = true */
|
||||
OPCODE(LOADF, B) /* R[a] = false */
|
||||
OPCODE(GETGV, BB) /* R[a] = getglobal(Syms[b]) */
|
||||
OPCODE(SETGV, BB) /* setglobal(Syms[b], R[a]) */
|
||||
OPCODE(GETSV, BB) /* R[a] = Special[Syms[b]] */
|
||||
@@ -48,7 +48,6 @@ OPCODE(SETMCNST, BB) /* R[a+1]::Syms[b] = R[a] */
|
||||
OPCODE(GETUPVAR, BBB) /* R[a] = uvget(b,c) */
|
||||
OPCODE(SETUPVAR, BBB) /* uvset(b,c,R[a]) */
|
||||
OPCODE(GETIDX, B) /* R[a] = R[a][R[a+1]] */
|
||||
OPCODE(GETIDX0, BB) /* R[a] = R[b][0]; a+1 for method call */
|
||||
OPCODE(SETIDX, B) /* R[a][R[a+1]] = R[a+2] */
|
||||
OPCODE(JMP, S) /* pc+=a */
|
||||
OPCODE(JMPIF, BS) /* if R[a] pc+=b */
|
||||
@@ -58,35 +57,25 @@ OPCODE(JMPUW, S) /* unwind_and_jump_to(a) */
|
||||
OPCODE(EXCEPT, B) /* R[a] = exc */
|
||||
OPCODE(RESCUE, BB) /* R[b] = R[a].isa?(R[b]) */
|
||||
OPCODE(RAISEIF, B) /* raise(R[a]) if R[a] */
|
||||
OPCODE(MATCHERR, B) /* raise NoMatchingPatternError unless R[a] */
|
||||
OPCODE(SSEND, BBB) /* R[a] = self.send(Syms[b],R[a+1]..,R[a+n+1]:R[a+n+2]..) (c=n|k<<4) */
|
||||
OPCODE(SSEND0, BB) /* R[a] = self.send(Syms[b]) (no args) */
|
||||
OPCODE(SSENDB, BBB) /* R[a] = self.send(Syms[b],R[a+1]..,R[a+n+1]:R[a+n+2]..,&R[a+n+2k+1]) */
|
||||
OPCODE(SEND, BBB) /* R[a] = R[a].send(Syms[b],R[a+1]..,R[a+n+1]:R[a+n+2]..) (c=n|k<<4) */
|
||||
OPCODE(SEND0, BB) /* R[a] = R[a].send(Syms[b]) (no args) */
|
||||
OPCODE(SENDB, BBB) /* R[a] = R[a].send(Syms[b],R[a+1]..,R[a+n+1]:R[a+n+2]..,&R[a+n+2k+1]) */
|
||||
OPCODE(CALL, Z) /* self.call(*, **, &) (But overlay the current call frame; tailcall) */
|
||||
OPCODE(BLKCALL, BB) /* R[a] = R[a].call(R[a+1],... ,R[a+b]); direct block call */
|
||||
OPCODE(SUPER, BB) /* R[a] = super(R[a+1],... ,R[a+b+1]) */
|
||||
OPCODE(ARGARY, BS) /* R[a] = argument array (16=m5:r1:m5:d1:lv4) */
|
||||
OPCODE(ENTER, W) /* arg setup according to flags (24=n1:m5:o5:r1:m5:k5:d1:b1) */
|
||||
OPCODE(ENTER, W) /* arg setup according to flags (23=m5:o5:r1:m5:k5:d1:b1) */
|
||||
OPCODE(KEY_P, BB) /* R[a] = kdict.key?(Syms[b]) */
|
||||
OPCODE(KEYEND, Z) /* raise unless kdict.empty? */
|
||||
OPCODE(KARG, BB) /* R[a] = kdict[Syms[b]]; kdict.delete(Syms[b]) */
|
||||
OPCODE(RETURN, B) /* return R[a] (normal) */
|
||||
OPCODE(RETURN_BLK, B) /* return R[a] (in-block return) */
|
||||
OPCODE(RETSELF, Z) /* return self */
|
||||
OPCODE(RETNIL, Z) /* return nil */
|
||||
OPCODE(RETTRUE, Z) /* return true */
|
||||
OPCODE(RETFALSE, Z) /* return false */
|
||||
OPCODE(BREAK, B) /* break R[a] */
|
||||
OPCODE(BLKPUSH, BS) /* R[a] = block (16=m5:r1:m5:d1:lv4) */
|
||||
OPCODE(ADD, B) /* R[a] = R[a]+R[a+1] */
|
||||
OPCODE(ADDI, BB) /* R[a] = R[a]+mrb_int(b) */
|
||||
OPCODE(SUB, B) /* R[a] = R[a]-R[a+1] */
|
||||
OPCODE(SUBI, BB) /* R[a] = R[a]-mrb_int(b) */
|
||||
OPCODE(ADDILV, BBB) /* R[a] = R[a]+mrb_int(c); R[b],R[b+1] for method call */
|
||||
OPCODE(SUBILV, BBB) /* R[a] = R[a]-mrb_int(c); R[b],R[b+1] for method call */
|
||||
OPCODE(MUL, B) /* R[a] = R[a]*R[a+1] */
|
||||
OPCODE(DIV, B) /* R[a] = R[a]/R[a+1] */
|
||||
OPCODE(EQ, B) /* R[a] = R[a]==R[a+1] */
|
||||
@@ -119,8 +108,6 @@ OPCODE(CLASS, BB) /* R[a] = newclass(R[a],Syms[b],R[a+1]) */
|
||||
OPCODE(MODULE, BB) /* R[a] = newmodule(R[a],Syms[b]) */
|
||||
OPCODE(EXEC, BB) /* R[a] = blockexec(R[a],Irep[b]) */
|
||||
OPCODE(DEF, BB) /* R[a].newmethod(Syms[b],R[a+1]); R[a] = Syms[b] */
|
||||
OPCODE(TDEF, BBB) /* target_class.newmethod(Syms[b],Irep[c]); R[a] = Syms[b] */
|
||||
OPCODE(SDEF, BBB) /* R[a].singleton_class.newmethod(Syms[b],Irep[c]); R[a] = Syms[b] */
|
||||
OPCODE(ALIAS, BB) /* alias_method(target_class,Syms[a],Syms[b]) */
|
||||
OPCODE(UNDEF, B) /* undef_method(target_class,Syms[a]) */
|
||||
OPCODE(SCLASS, B) /* R[a] = R[a].singleton_class */
|
||||
|
||||
+11
-35
@@ -7,13 +7,16 @@
|
||||
#ifndef MRUBY_PRESYM_H
|
||||
#define MRUBY_PRESYM_H
|
||||
|
||||
#if !defined(MRB_PRESYM_SCANNING)
|
||||
|
||||
#include <mruby/presym/id.h>
|
||||
#if defined(MRB_NO_PRESYM)
|
||||
# include <mruby/presym/disable.h>
|
||||
#elif !defined(MRB_PRESYM_SCANNING)
|
||||
# include <mruby/presym/enable.h>
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Where `mrb_intern_lit` is allowed for symbol interning, it is directly
|
||||
* replaced by the symbol ID using the following macros.
|
||||
* replaced by the symbol ID if presym is enabled by using the following
|
||||
* macros.
|
||||
*
|
||||
* MRB_OPSYM(xor) //=> ^ (Operator)
|
||||
* MRB_GVSYM(xor) //=> $xor (Global Variable)
|
||||
@@ -29,37 +32,10 @@
|
||||
* can be specified for it). Other than that, describe only word characters
|
||||
* excluding leading and ending punctuation.
|
||||
*
|
||||
* These macros are expanded to compile-time integer constants.
|
||||
* These macros are expanded to `mrb_intern_lit` if presym is disabled,
|
||||
* therefore the mruby state variable is required. The above macros can be
|
||||
* used when the variable name is `mrb`. If you want to use other variable
|
||||
* names, you need to use macros with `_2` suffix, such as `MRB_SYM_2`.
|
||||
*/
|
||||
|
||||
#define MRB_OPSYM(name) MRB_OPSYM__##name
|
||||
#define MRB_GVSYM(name) MRB_GVSYM__##name
|
||||
#define MRB_CVSYM(name) MRB_CVSYM__##name
|
||||
#define MRB_IVSYM(name) MRB_IVSYM__##name
|
||||
#define MRB_SYM_B(name) MRB_SYM_B__##name
|
||||
#define MRB_SYM_Q(name) MRB_SYM_Q__##name
|
||||
#define MRB_SYM_E(name) MRB_SYM_E__##name
|
||||
#define MRB_SYM(name) MRB_SYM__##name
|
||||
|
||||
/* backward compatibility: _2 variants accept but ignore mrb_state* */
|
||||
#define MRB_OPSYM_2(mrb, name) MRB_OPSYM(name)
|
||||
#define MRB_GVSYM_2(mrb, name) MRB_GVSYM(name)
|
||||
#define MRB_CVSYM_2(mrb, name) MRB_CVSYM(name)
|
||||
#define MRB_IVSYM_2(mrb, name) MRB_IVSYM(name)
|
||||
#define MRB_SYM_B_2(mrb, name) MRB_SYM_B(name)
|
||||
#define MRB_SYM_Q_2(mrb, name) MRB_SYM_Q(name)
|
||||
#define MRB_SYM_E_2(mrb, name) MRB_SYM_E(name)
|
||||
#define MRB_SYM_2(mrb, name) MRB_SYM(name)
|
||||
|
||||
#define MRB_PRESYM_DEFINE_VAR_AND_INITER(name, size, ...) \
|
||||
static const mrb_sym name[] = {__VA_ARGS__};
|
||||
|
||||
#define MRB_PRESYM_INIT_SYMBOLS(mrb, name) (void)(mrb)
|
||||
|
||||
/* use MRB_SYM() for E_RUNTIME_ERROR etc. */
|
||||
#undef MRB_ERROR_SYM
|
||||
#define MRB_ERROR_SYM(sym) MRB_SYM(sym)
|
||||
|
||||
#endif /* !MRB_PRESYM_SCANNING */
|
||||
|
||||
#endif /* MRUBY_PRESYM_H */
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
** @file mruby/presym/disable.h - Disable Preallocated Symbols
|
||||
**
|
||||
** See Copyright Notice in mruby.h
|
||||
*/
|
||||
|
||||
#ifndef MRUBY_PRESYM_DISABLE_H
|
||||
#define MRUBY_PRESYM_DISABLE_H
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#define MRB_PRESYM_MAX 0
|
||||
|
||||
#define MRB_OPSYM(name) MRB_OPSYM__##name(mrb)
|
||||
#define MRB_GVSYM(name) mrb_intern_lit(mrb, "$" #name)
|
||||
#define MRB_CVSYM(name) mrb_intern_lit(mrb, "@@" #name)
|
||||
#define MRB_IVSYM(name) mrb_intern_lit(mrb, "@" #name)
|
||||
#define MRB_SYM_B(name) mrb_intern_lit(mrb, #name "!")
|
||||
#define MRB_SYM_Q(name) mrb_intern_lit(mrb, #name "?")
|
||||
#define MRB_SYM_E(name) mrb_intern_lit(mrb, #name "=")
|
||||
#define MRB_SYM(name) mrb_intern_lit(mrb, #name)
|
||||
|
||||
#define MRB_OPSYM_2(mrb, name) MRB_OPSYM__##name(mrb)
|
||||
#define MRB_GVSYM_2(mrb, name) mrb_intern_lit(mrb, "$" #name)
|
||||
#define MRB_CVSYM_2(mrb, name) mrb_intern_lit(mrb, "@@" #name)
|
||||
#define MRB_IVSYM_2(mrb, name) mrb_intern_lit(mrb, "@" #name)
|
||||
#define MRB_SYM_B_2(mrb, name) mrb_intern_lit(mrb, #name "!")
|
||||
#define MRB_SYM_Q_2(mrb, name) mrb_intern_lit(mrb, #name "?")
|
||||
#define MRB_SYM_E_2(mrb, name) mrb_intern_lit(mrb, #name "=")
|
||||
#define MRB_SYM_2(mrb, name) mrb_intern_lit(mrb, #name)
|
||||
|
||||
#define MRB_OPSYM__not(mrb) mrb_intern_lit(mrb, "!")
|
||||
#define MRB_OPSYM__mod(mrb) mrb_intern_lit(mrb, "%")
|
||||
#define MRB_OPSYM__and(mrb) mrb_intern_lit(mrb, "&")
|
||||
#define MRB_OPSYM__mul(mrb) mrb_intern_lit(mrb, "*")
|
||||
#define MRB_OPSYM__add(mrb) mrb_intern_lit(mrb, "+")
|
||||
#define MRB_OPSYM__sub(mrb) mrb_intern_lit(mrb, "-")
|
||||
#define MRB_OPSYM__div(mrb) mrb_intern_lit(mrb, "/")
|
||||
#define MRB_OPSYM__lt(mrb) mrb_intern_lit(mrb, "<")
|
||||
#define MRB_OPSYM__gt(mrb) mrb_intern_lit(mrb, ">")
|
||||
#define MRB_OPSYM__xor(mrb) mrb_intern_lit(mrb, "^")
|
||||
#define MRB_OPSYM__tick(mrb) mrb_intern_lit(mrb, "`")
|
||||
#define MRB_OPSYM__or(mrb) mrb_intern_lit(mrb, "|")
|
||||
#define MRB_OPSYM__neg(mrb) mrb_intern_lit(mrb, "~")
|
||||
#define MRB_OPSYM__neq(mrb) mrb_intern_lit(mrb, "!=")
|
||||
#define MRB_OPSYM__nmatch(mrb) mrb_intern_lit(mrb, "!~")
|
||||
#define MRB_OPSYM__andand(mrb) mrb_intern_lit(mrb, "&&")
|
||||
#define MRB_OPSYM__pow(mrb) mrb_intern_lit(mrb, "**")
|
||||
#define MRB_OPSYM__plus(mrb) mrb_intern_lit(mrb, "+@")
|
||||
#define MRB_OPSYM__minus(mrb) mrb_intern_lit(mrb, "-@")
|
||||
#define MRB_OPSYM__lshift(mrb) mrb_intern_lit(mrb, "<<")
|
||||
#define MRB_OPSYM__le(mrb) mrb_intern_lit(mrb, "<=")
|
||||
#define MRB_OPSYM__eq(mrb) mrb_intern_lit(mrb, "==")
|
||||
#define MRB_OPSYM__match(mrb) mrb_intern_lit(mrb, "=~")
|
||||
#define MRB_OPSYM__ge(mrb) mrb_intern_lit(mrb, ">=")
|
||||
#define MRB_OPSYM__rshift(mrb) mrb_intern_lit(mrb, ">>")
|
||||
#define MRB_OPSYM__aref(mrb) mrb_intern_lit(mrb, "[]")
|
||||
#define MRB_OPSYM__oror(mrb) mrb_intern_lit(mrb, "||")
|
||||
#define MRB_OPSYM__cmp(mrb) mrb_intern_lit(mrb, "<=>")
|
||||
#define MRB_OPSYM__eqq(mrb) mrb_intern_lit(mrb, "===")
|
||||
#define MRB_OPSYM__aset(mrb) mrb_intern_lit(mrb, "[]=")
|
||||
|
||||
#define MRB_PRESYM_DEFINE_VAR_AND_INITER(name, size, ...) \
|
||||
static mrb_sym name[size]; \
|
||||
static void presym_init_##name(mrb_state *mrb) { \
|
||||
mrb_sym name__[] = {__VA_ARGS__}; \
|
||||
memcpy(name, name__, sizeof(name)); \
|
||||
}
|
||||
|
||||
#define MRB_PRESYM_INIT_SYMBOLS(mrb, name) presym_init_##name(mrb)
|
||||
|
||||
#endif /* MRUBY_PRESYM_DISABLE_H */
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
** @file mruby/presym/enable.h - Enable Preallocated Symbols
|
||||
**
|
||||
** See Copyright Notice in mruby.h
|
||||
*/
|
||||
|
||||
#ifndef MRUBY_PRESYM_ENABLE_H
|
||||
#define MRUBY_PRESYM_ENABLE_H
|
||||
|
||||
#include <mruby/presym/id.h>
|
||||
|
||||
#define MRB_OPSYM(name) MRB_OPSYM__##name
|
||||
#define MRB_GVSYM(name) MRB_GVSYM__##name
|
||||
#define MRB_CVSYM(name) MRB_CVSYM__##name
|
||||
#define MRB_IVSYM(name) MRB_IVSYM__##name
|
||||
#define MRB_SYM_B(name) MRB_SYM_B__##name
|
||||
#define MRB_SYM_Q(name) MRB_SYM_Q__##name
|
||||
#define MRB_SYM_E(name) MRB_SYM_E__##name
|
||||
#define MRB_SYM(name) MRB_SYM__##name
|
||||
|
||||
#define MRB_OPSYM_2(mrb, name) MRB_OPSYM__##name
|
||||
#define MRB_GVSYM_2(mrb, name) MRB_GVSYM__##name
|
||||
#define MRB_CVSYM_2(mrb, name) MRB_CVSYM__##name
|
||||
#define MRB_IVSYM_2(mrb, name) MRB_IVSYM__##name
|
||||
#define MRB_SYM_B_2(mrb, name) MRB_SYM_B__##name
|
||||
#define MRB_SYM_Q_2(mrb, name) MRB_SYM_Q__##name
|
||||
#define MRB_SYM_E_2(mrb, name) MRB_SYM_E__##name
|
||||
#define MRB_SYM_2(mrb, name) MRB_SYM__##name
|
||||
|
||||
#define MRB_PRESYM_DEFINE_VAR_AND_INITER(name, size, ...) \
|
||||
static const mrb_sym name[] = {__VA_ARGS__};
|
||||
|
||||
#define MRB_PRESYM_INIT_SYMBOLS(mrb, name) (void)(mrb)
|
||||
|
||||
/* use MRB_SYM() for E_RUNTIME_ERROR etc. */
|
||||
#undef MRB_ERROR_SYM
|
||||
#define MRB_ERROR_SYM(sym) MRB_SYM(sym)
|
||||
|
||||
#endif /* MRUBY_PRESYM_ENABLE_H */
|
||||
@@ -38,15 +38,14 @@
|
||||
#define MRB_SYM_E(name) MRB_PRESYM_SCANNING_TAGGED(#name "=")
|
||||
#define MRB_SYM(name) MRB_PRESYM_SCANNING_TAGGED(#name)
|
||||
|
||||
/* backward compatibility: _2 variants accept but ignore mrb_state* */
|
||||
#define MRB_OPSYM_2(mrb, name) MRB_OPSYM(name)
|
||||
#define MRB_GVSYM_2(mrb, name) MRB_GVSYM(name)
|
||||
#define MRB_CVSYM_2(mrb, name) MRB_CVSYM(name)
|
||||
#define MRB_IVSYM_2(mrb, name) MRB_IVSYM(name)
|
||||
#define MRB_SYM_B_2(mrb, name) MRB_SYM_B(name)
|
||||
#define MRB_SYM_Q_2(mrb, name) MRB_SYM_Q(name)
|
||||
#define MRB_SYM_E_2(mrb, name) MRB_SYM_E(name)
|
||||
#define MRB_SYM_2(mrb, name) MRB_SYM(name)
|
||||
#define MRB_OPSYM_2(mrb, name) MRB_OPSYM__##name(mrb)
|
||||
#define MRB_GVSYM_2(mrb, name) MRB_PRESYM_SCANNING_TAGGED("$" #name)
|
||||
#define MRB_CVSYM_2(mrb, name) MRB_PRESYM_SCANNING_TAGGED("@@" #name)
|
||||
#define MRB_IVSYM_2(mrb, name) MRB_PRESYM_SCANNING_TAGGED("@" #name)
|
||||
#define MRB_SYM_B_2(mrb, name) MRB_PRESYM_SCANNING_TAGGED(#name "!")
|
||||
#define MRB_SYM_Q_2(mrb, name) MRB_PRESYM_SCANNING_TAGGED(#name "?")
|
||||
#define MRB_SYM_E_2(mrb, name) MRB_PRESYM_SCANNING_TAGGED(#name "=")
|
||||
#define MRB_SYM_2(mrb, name) MRB_PRESYM_SCANNING_TAGGED(#name)
|
||||
|
||||
#define MRB_OPSYM__not(mrb) MRB_PRESYM_SCANNING_TAGGED("!")
|
||||
#define MRB_OPSYM__mod(mrb) MRB_PRESYM_SCANNING_TAGGED("%")
|
||||
|
||||
+10
-8
@@ -72,7 +72,6 @@ struct RProc {
|
||||
#define MRB_ASPEC_KEY(a) (((a) >> 2) & 0x1f)
|
||||
#define MRB_ASPEC_KDICT(a) (((a) >> 1) & 0x1)
|
||||
#define MRB_ASPEC_BLOCK(a) ((a) & 1)
|
||||
#define MRB_ASPEC_NOBLOCK(a) (((a) >> 23) & 0x1)
|
||||
|
||||
#define MRB_PROC_CFUNC_FL 128
|
||||
#define MRB_PROC_CFUNC_P(p) (((p)->flags & MRB_PROC_CFUNC_FL) != 0)
|
||||
@@ -97,7 +96,7 @@ struct RProc {
|
||||
} while (0)
|
||||
#define MRB_PROC_SCOPE 2048
|
||||
#define MRB_PROC_SCOPE_P(p) (((p)->flags & MRB_PROC_SCOPE) != 0)
|
||||
#define MRB_PROC_NOARG 4096 /* for MRB_PROC_CFUNC_FL, aspec == MRB_ARGS_NONE() */
|
||||
#define MRB_PROC_NOARG 4096 /* for MRB_PROC_CFUNC_FL, it would be something like MRB_ARGS_NONE() or MRB_METHOD_NOARG_FL */
|
||||
#define MRB_PROC_NOARG_P(p) (((p)->flags & MRB_PROC_NOARG) != 0)
|
||||
#define MRB_PROC_ALIAS 8192
|
||||
#define MRB_PROC_ALIAS_P(p) (((p)->flags & MRB_PROC_ALIAS) != 0)
|
||||
@@ -114,15 +113,18 @@ MRB_API mrb_value mrb_proc_cfunc_env_get(mrb_state *mrb, mrb_int idx);
|
||||
/* old name */
|
||||
#define mrb_cfunc_env_get(mrb, idx) mrb_proc_cfunc_env_get(mrb, idx)
|
||||
|
||||
#define MRB_METHOD_FUNC_FL (1 << 24)
|
||||
#define MRB_METHOD_PUBLIC_FL 0
|
||||
#define MRB_METHOD_PRIVATE_FL (1 << 25)
|
||||
#define MRB_METHOD_PROTECTED_FL (1 << 26)
|
||||
#define MRB_METHOD_VDEFAULT_FL ((1 << 25) | (1 << 26))
|
||||
#define MRB_METHOD_VISIBILITY_MASK ((1 << 25) | (1 << 26))
|
||||
#define MRB_METHOD_FUNC_FL 8
|
||||
#define MRB_METHOD_NOARG_FL 4
|
||||
#define MRB_METHOD_PUBLIC_FL 0
|
||||
#define MRB_METHOD_PRIVATE_FL 1
|
||||
#define MRB_METHOD_PROTECTED_FL 2
|
||||
#define MRB_METHOD_VDEFAULT_FL 3
|
||||
#define MRB_METHOD_VISIBILITY_MASK 3
|
||||
|
||||
#define MRB_METHOD_FUNC_P(m) ((m).flags&MRB_METHOD_FUNC_FL)
|
||||
#define MRB_METHOD_NOARG_P(m) (((m).flags&MRB_METHOD_NOARG_FL)?1:0)
|
||||
#define MRB_METHOD_FUNC(m) ((m).as.func)
|
||||
#define MRB_METHOD_NOARG_SET(m) do{(m).flags|=MRB_METHOD_NOARG_FL;}while(0)
|
||||
#define MRB_METHOD_FROM_FUNC(m,fn) do{(m).flags=MRB_METHOD_FUNC_FL;(m).as.func=(fn);}while(0)
|
||||
#define MRB_METHOD_FROM_PROC(m,pr) do{(m).flags=0;(m).as.proc=(pr);}while(0)
|
||||
#define MRB_METHOD_PROC_P(m) (!MRB_METHOD_FUNC_P(m))
|
||||
|
||||
+1
-25
@@ -2,30 +2,6 @@
|
||||
** @file mruby/throw.h - mruby exception throwing handler
|
||||
**
|
||||
** See Copyright Notice in mruby.h
|
||||
**
|
||||
** WARNING: This header is for mruby core internal use only.
|
||||
** Do not include this header in user code or mrbgems.
|
||||
**
|
||||
** When MRB_USE_CXX_EXCEPTION is defined, this header requires C++
|
||||
** compilation. C source files that include this header will fail
|
||||
** to compile when linked into C++ projects using MRB_USE_CXX_EXCEPTION.
|
||||
**
|
||||
** For exception-safe code in mrbgems and user code, use the
|
||||
** mrb_protect_error() API from <mruby/error.h> instead:
|
||||
**
|
||||
** #include <mruby/error.h>
|
||||
**
|
||||
** mrb_value my_func_body(mrb_state *mrb, void *data) {
|
||||
** // code that may raise exceptions
|
||||
** return result;
|
||||
** }
|
||||
**
|
||||
** void my_func(mrb_state *mrb) {
|
||||
** mrb_bool error;
|
||||
** mrb_value result = mrb_protect_error(mrb, my_func_body, data, &error);
|
||||
** // cleanup code runs here regardless of exception
|
||||
** if (error) mrb_exc_raise(mrb, result);
|
||||
** }
|
||||
*/
|
||||
|
||||
#ifndef MRB_THROW_H
|
||||
@@ -57,7 +33,7 @@ typedef void *mrb_jmpbuf_impl;
|
||||
#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
|
||||
#define MRB_SETJMP _setjmp
|
||||
#define MRB_LONGJMP _longjmp
|
||||
#elif defined(__MINGW64__) && !defined(_M_ARM64) && defined(__GNUC__) && __GNUC__ >= 4
|
||||
#elif defined(__MINGW64__) && defined(__GNUC__) && __GNUC__ >= 4
|
||||
#define MRB_SETJMP __builtin_setjmp
|
||||
#define MRB_LONGJMP __builtin_longjmp
|
||||
#else
|
||||
|
||||
@@ -134,7 +134,7 @@ static const unsigned int IEEE754_INFINITY_BITS_SINGLE = 0x7F800000;
|
||||
#endif
|
||||
|
||||
#define MRB_VTYPE_FOREACH(f) \
|
||||
/* mrb_vtype */ /* C type */ /* Ruby class */ \
|
||||
/* mrb_vtype */ /* c type */ /* ruby class */ \
|
||||
f(MRB_TT_FALSE, void, "false") \
|
||||
f(MRB_TT_TRUE, void, "true") \
|
||||
f(MRB_TT_SYMBOL, void, "Symbol") \
|
||||
@@ -163,8 +163,7 @@ static const unsigned int IEEE754_INFINITY_BITS_SINGLE = 0x7F800000;
|
||||
f(MRB_TT_COMPLEX, struct RComplex, "Complex") \
|
||||
f(MRB_TT_RATIONAL, struct RRational, "Rational") \
|
||||
f(MRB_TT_BIGINT, struct RBigint, "Integer") \
|
||||
f(MRB_TT_BACKTRACE, struct RBacktrace, "backtrace") \
|
||||
f(MRB_TT_SET, struct RSet, "Set")
|
||||
f(MRB_TT_BACKTRACE, struct RBacktrace, "backtrace")
|
||||
|
||||
enum mrb_vtype {
|
||||
#define MRB_VTYPE_DEFINE(tt, type, name) tt,
|
||||
|
||||
@@ -7,12 +7,6 @@
|
||||
#ifndef MRUBY_VARIABLE_H
|
||||
#define MRUBY_VARIABLE_H
|
||||
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#define MRB_MEM_PREFETCH(addr) __builtin_prefetch(addr, 0, 1)
|
||||
#else
|
||||
#define MRB_MEM_PREFETCH(addr)
|
||||
#endif
|
||||
|
||||
#include "common.h"
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,7 +27,7 @@ MRB_BEGIN_DECL
|
||||
/*
|
||||
* The version of Ruby used by mruby.
|
||||
*/
|
||||
#define MRUBY_RUBY_VERSION "4.0"
|
||||
#define MRUBY_RUBY_VERSION "3.4"
|
||||
|
||||
/*
|
||||
* Ruby engine.
|
||||
@@ -37,12 +37,12 @@ MRB_BEGIN_DECL
|
||||
/*
|
||||
* Major release version number.
|
||||
*/
|
||||
#define MRUBY_RELEASE_MAJOR 4
|
||||
#define MRUBY_RELEASE_MAJOR 3
|
||||
|
||||
/*
|
||||
* Minor release version number.
|
||||
*/
|
||||
#define MRUBY_RELEASE_MINOR 0
|
||||
#define MRUBY_RELEASE_MINOR 4
|
||||
|
||||
/*
|
||||
* Tiny release version number.
|
||||
@@ -57,7 +57,7 @@ MRB_BEGIN_DECL
|
||||
/*
|
||||
* Patch level string. (optional)
|
||||
*/
|
||||
#define MRUBY_PATCHLEVEL_STR "RC"
|
||||
#define MRUBY_PATCHLEVEL_STR ""
|
||||
|
||||
#ifndef MRUBY_PATCHLEVEL_STR
|
||||
# if MRUBY_PATCHLEVEL < 0
|
||||
@@ -80,17 +80,17 @@ MRB_BEGIN_DECL
|
||||
/*
|
||||
* Release year.
|
||||
*/
|
||||
#define MRUBY_RELEASE_YEAR 2026
|
||||
#define MRUBY_RELEASE_YEAR 2025
|
||||
|
||||
/*
|
||||
* Release month.
|
||||
*/
|
||||
#define MRUBY_RELEASE_MONTH 3
|
||||
#define MRUBY_RELEASE_MONTH 4
|
||||
|
||||
/*
|
||||
* Release day.
|
||||
*/
|
||||
#define MRUBY_RELEASE_DAY 5
|
||||
#define MRUBY_RELEASE_DAY 20
|
||||
|
||||
/*
|
||||
* Release date as a string.
|
||||
|
||||
@@ -1,568 +0,0 @@
|
||||
module MRuby
|
||||
class Amalgam
|
||||
# Top-level headers to include (internal dependencies are inlined recursively)
|
||||
# mruby.h is the main header - it includes value.h, gc.h, version.h with proper macro order
|
||||
# Note: internal.h is NOT included here - it goes in mruby.c
|
||||
HEADER_ORDER = %w[
|
||||
mruby.h
|
||||
mruby/array.h
|
||||
mruby/string.h
|
||||
mruby/hash.h
|
||||
mruby/class.h
|
||||
mruby/proc.h
|
||||
mruby/range.h
|
||||
mruby/variable.h
|
||||
mruby/numeric.h
|
||||
mruby/error.h
|
||||
mruby/data.h
|
||||
mruby/istruct.h
|
||||
mruby/mempool.h
|
||||
mruby/debug.h
|
||||
mruby/dump.h
|
||||
mruby/irep.h
|
||||
mruby/opcode.h
|
||||
mruby/re.h
|
||||
mruby/throw.h
|
||||
mruby/khash.h
|
||||
mruby/endian.h
|
||||
mruby/presym.h
|
||||
mruby/compile.h
|
||||
].freeze
|
||||
|
||||
# Boxing headers are conditionally included
|
||||
BOXING_HEADERS = %w[
|
||||
mruby/boxing_no.h
|
||||
mruby/boxing_word.h
|
||||
mruby/boxing_nan.h
|
||||
].freeze
|
||||
|
||||
# Core sources in recommended order
|
||||
CORE_SOURCE_ORDER = %w[
|
||||
allocf.c
|
||||
readnum.c
|
||||
readint.c
|
||||
readfloat.c
|
||||
state.c
|
||||
symbol.c
|
||||
class.c
|
||||
object.c
|
||||
gc.c
|
||||
mempool.c
|
||||
variable.c
|
||||
array.c
|
||||
hash.c
|
||||
string.c
|
||||
range.c
|
||||
numeric.c
|
||||
numops.c
|
||||
proc.c
|
||||
kernel.c
|
||||
enum.c
|
||||
error.c
|
||||
backtrace.c
|
||||
vm.c
|
||||
load.c
|
||||
dump.c
|
||||
cdump.c
|
||||
codedump.c
|
||||
print.c
|
||||
fmt_fp.c
|
||||
debug.c
|
||||
etc.c
|
||||
version.c
|
||||
init.c
|
||||
].freeze
|
||||
|
||||
def initialize(build)
|
||||
@build = build
|
||||
@processed_guards = {}
|
||||
@processed_headers = [] # Track header paths for include transformation
|
||||
# Pre-collect gem header names for source include transformation
|
||||
@gem_header_names = collect_gem_header_names
|
||||
end
|
||||
|
||||
def collect_gem_header_names
|
||||
names = []
|
||||
library_gems.each do |gem|
|
||||
gem_include = "#{gem.dir}/include"
|
||||
next unless File.directory?(gem_include)
|
||||
Dir.glob("#{gem_include}/**/*.h").each do |path|
|
||||
rel_path = path.sub("#{gem_include}/", "")
|
||||
names << rel_path
|
||||
# Also track basename for simple includes like "io_hal.h"
|
||||
names << File.basename(rel_path)
|
||||
end
|
||||
end
|
||||
names.uniq
|
||||
end
|
||||
|
||||
def generate_header(output_path)
|
||||
FileUtils.mkdir_p(File.dirname(output_path))
|
||||
_pp "GEN", output_path.relative_path
|
||||
|
||||
File.open(output_path, "w:binary") do |f|
|
||||
write_header_preamble(f)
|
||||
write_ordered_headers(f)
|
||||
# Boxing headers are inlined at their include point in value.h
|
||||
# Presym headers are inlined via presym.h -> enable.h -> id.h
|
||||
write_gem_headers(f)
|
||||
write_header_postamble(f)
|
||||
end
|
||||
end
|
||||
|
||||
def generate_source(output_path)
|
||||
FileUtils.mkdir_p(File.dirname(output_path))
|
||||
_pp "GEN", output_path.relative_path
|
||||
|
||||
File.open(output_path, "w:binary") do |f|
|
||||
write_source_preamble(f)
|
||||
write_internal_headers(f)
|
||||
write_core_sources(f)
|
||||
write_generated_sources(f)
|
||||
write_gem_sources(f)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def include_dir
|
||||
"#{MRUBY_ROOT}/include"
|
||||
end
|
||||
|
||||
def build_include_dir
|
||||
"#{@build.build_dir}/include"
|
||||
end
|
||||
|
||||
def src_dir
|
||||
"#{MRUBY_ROOT}/src"
|
||||
end
|
||||
|
||||
# Filter out binary gems (they have main() functions)
|
||||
def library_gems
|
||||
@build.gems.reject { |gem| gem.name.start_with?("mruby-bin-") }
|
||||
end
|
||||
|
||||
# ========== Header Generation ==========
|
||||
|
||||
def write_header_preamble(f)
|
||||
f.puts <<~PREAMBLE
|
||||
/*
|
||||
** mruby amalgamated header
|
||||
** Generated from mruby source files
|
||||
**
|
||||
** This file is auto-generated. Do not edit directly.
|
||||
*/
|
||||
|
||||
#ifndef MRUBY_AMALGAM_H
|
||||
#define MRUBY_AMALGAM_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
#define __STDC_LIMIT_MACROS
|
||||
#define __STDC_CONSTANT_MACROS
|
||||
#define __STDC_FORMAT_MACROS
|
||||
#endif
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
|
||||
PREAMBLE
|
||||
|
||||
# Add build-level defines from gems (e.g., MRB_USE_TASK_SCHEDULER)
|
||||
gem_defines = collect_gem_defines
|
||||
unless gem_defines.empty?
|
||||
f.puts "/* Gem-required defines */"
|
||||
gem_defines.each do |d|
|
||||
f.puts "#define #{d}"
|
||||
end
|
||||
f.puts
|
||||
end
|
||||
end
|
||||
|
||||
# Collect defines added by gems that affect core headers
|
||||
def collect_gem_defines
|
||||
defines = []
|
||||
@build.defines.each do |d|
|
||||
# Include defines that affect mrb_state or core functionality
|
||||
defines << d if d =~ /^MRB_USE_|^MRB_UTF8_|^HAVE_MRUBY_/
|
||||
end
|
||||
defines.uniq.sort
|
||||
end
|
||||
|
||||
def write_header_postamble(f)
|
||||
f.puts <<~POSTAMBLE
|
||||
|
||||
#endif /* MRUBY_AMALGAM_H */
|
||||
POSTAMBLE
|
||||
end
|
||||
|
||||
def write_ordered_headers(f)
|
||||
# Process top-level headers; internal includes are recursively inlined
|
||||
HEADER_ORDER.each do |header|
|
||||
path = "#{include_dir}/#{header}"
|
||||
next unless File.exist?(path)
|
||||
write_header_content(f, header, path)
|
||||
end
|
||||
end
|
||||
|
||||
def write_boxing_headers(f)
|
||||
f.puts "\n/* Boxing type selection */"
|
||||
f.puts "#if defined(MRB_NAN_BOXING)"
|
||||
write_header_content(f, "mruby/boxing_nan.h", "#{include_dir}/mruby/boxing_nan.h")
|
||||
f.puts "#elif defined(MRB_WORD_BOXING)"
|
||||
write_header_content(f, "mruby/boxing_word.h", "#{include_dir}/mruby/boxing_word.h")
|
||||
f.puts "#else"
|
||||
write_header_content(f, "mruby/boxing_no.h", "#{include_dir}/mruby/boxing_no.h")
|
||||
f.puts "#endif"
|
||||
end
|
||||
|
||||
def write_presym_headers(f)
|
||||
presym_dir = "#{@build.build_dir}/include/mruby/presym"
|
||||
return unless File.directory?(presym_dir)
|
||||
|
||||
%w[id.h table.h].each do |header|
|
||||
path = "#{presym_dir}/#{header}"
|
||||
next unless File.exist?(path)
|
||||
write_header_content(f, "mruby/presym/#{header}", path)
|
||||
end
|
||||
end
|
||||
|
||||
def write_gem_headers(f)
|
||||
library_gems.each do |gem|
|
||||
gem_include = "#{gem.dir}/include"
|
||||
next unless File.directory?(gem_include)
|
||||
|
||||
Dir.glob("#{gem_include}/**/*.h").sort.each do |path|
|
||||
rel_path = path.sub("#{gem_include}/", "")
|
||||
write_header_content(f, "#{gem.name}: #{rel_path}", path)
|
||||
# Also track the relative path for source include transformation
|
||||
# (handles includes like #include "io_hal.h")
|
||||
@processed_headers << rel_path unless @processed_headers.include?(rel_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def write_header_content(f, name, path)
|
||||
return unless File.exist?(path)
|
||||
|
||||
content = File.read(path, mode: "rb")
|
||||
guard = extract_include_guard(content)
|
||||
|
||||
# Skip if already processed
|
||||
if guard && @processed_guards[guard]
|
||||
f.puts "/* #{name} - already included */"
|
||||
return
|
||||
end
|
||||
@processed_guards[guard] = true if guard
|
||||
@processed_headers << name # Track header path for include transformation
|
||||
|
||||
f.puts "\n/* ======== #{name} ======== */"
|
||||
content = strip_include_guard(content, guard) if guard
|
||||
content = transform_includes(content, inline: true)
|
||||
f.puts content
|
||||
end
|
||||
|
||||
# ========== Source Generation ==========
|
||||
|
||||
def write_source_preamble(f)
|
||||
f.puts <<~PREAMBLE
|
||||
/*
|
||||
** mruby amalgamated source
|
||||
** Generated from mruby source files
|
||||
**
|
||||
** This file is auto-generated. Do not edit directly.
|
||||
*/
|
||||
|
||||
#include "mruby.h"
|
||||
|
||||
PREAMBLE
|
||||
end
|
||||
|
||||
def write_internal_headers(f)
|
||||
f.puts "/* ======== Internal headers ======== */"
|
||||
|
||||
# Forward declarations needed for amalgamation
|
||||
# (functions called before defined due to source file ordering)
|
||||
# Note: mrb_irep_catch_handler_table is static inline in internal.h, no forward decl needed
|
||||
f.puts <<~FORWARD
|
||||
/* Forward declarations for amalgamation */
|
||||
static void mrb_irep_free(mrb_state *mrb, mrb_irep *irep);
|
||||
static mrb_value mrb_class_find_path(mrb_state *mrb, struct RClass *c);
|
||||
static void mrb_method_added(mrb_state *mrb, struct RClass *c, mrb_sym mid);
|
||||
static void mrb_proc_copy(mrb_state *mrb, struct RProc *a, const struct RProc *b);
|
||||
static size_t mrb_gc_mark_range(mrb_state *mrb, struct RRange *r);
|
||||
static void mrb_ary_decref(mrb_state *mrb, mrb_shared_array *shared);
|
||||
static mrb_int mrb_proc_arity(const struct RProc *p);
|
||||
FORWARD
|
||||
|
||||
# internal.h
|
||||
internal_path = "#{include_dir}/mruby/internal.h"
|
||||
if File.exist?(internal_path)
|
||||
content = File.read(internal_path, mode: "rb")
|
||||
content = strip_include_guard(content, extract_include_guard(content))
|
||||
content = transform_source_includes(content)
|
||||
f.puts "\n/* mruby/internal.h */"
|
||||
f.puts content
|
||||
end
|
||||
|
||||
# presym/table.h (generated, needed by symbol.c)
|
||||
table_path = "#{build_include_dir}/mruby/presym/table.h"
|
||||
if File.exist?(table_path)
|
||||
content = File.read(table_path, mode: "rb")
|
||||
content = transform_source_includes(content)
|
||||
f.puts "\n/* mruby/presym/table.h */"
|
||||
f.puts content
|
||||
end
|
||||
|
||||
# value_array.h (internal src header)
|
||||
value_array_path = "#{src_dir}/value_array.h"
|
||||
if File.exist?(value_array_path)
|
||||
content = File.read(value_array_path, mode: "rb")
|
||||
content = strip_include_guard(content, extract_include_guard(content))
|
||||
content = transform_source_includes(content)
|
||||
f.puts "\n/* src/value_array.h */"
|
||||
f.puts content
|
||||
end
|
||||
end
|
||||
|
||||
def write_core_sources(f)
|
||||
f.puts "\n/* ======== Core sources ======== */"
|
||||
|
||||
CORE_SOURCE_ORDER.each do |source|
|
||||
path = "#{src_dir}/#{source}"
|
||||
next unless File.exist?(path)
|
||||
write_source_content(f, "src/#{source}", path)
|
||||
end
|
||||
|
||||
# Clear potentially conflicting macros from core sources
|
||||
write_macro_cleanup(f, "core")
|
||||
end
|
||||
|
||||
def write_generated_sources(f)
|
||||
# mrblib.c - compiled Ruby stdlib
|
||||
mrblib_path = "#{@build.build_dir}/mrblib/mrblib.c"
|
||||
if File.exist?(mrblib_path)
|
||||
write_source_content(f, "mrblib.c", mrblib_path)
|
||||
end
|
||||
end
|
||||
|
||||
# Macros that may conflict between source files in amalgamation
|
||||
# These are #undef-ed after each source file within a gem
|
||||
CONFLICTING_MACROS = %w[
|
||||
mrb_stat
|
||||
mrb_lstat
|
||||
mrb_fstat
|
||||
lesser
|
||||
greater
|
||||
CASE
|
||||
NEXT
|
||||
JUMP
|
||||
CALL
|
||||
node_type
|
||||
push
|
||||
pop
|
||||
peek
|
||||
].freeze
|
||||
|
||||
def write_gem_sources(f)
|
||||
f.puts "\n/* ======== Gem sources ======== */"
|
||||
|
||||
library_gems.each do |gem|
|
||||
# Some gems use 'core/' instead of 'src/' (mruby-compiler, mruby-bigint)
|
||||
source_dirs = ["#{gem.dir}/src", "#{gem.dir}/core"].select { |d| File.directory?(d) }
|
||||
|
||||
# Include C sources if the gem has any
|
||||
unless source_dirs.empty?
|
||||
sources = source_dirs.flat_map { |d| Dir.glob("#{d}/**/*.c") }.sort
|
||||
sources.each_with_index do |path, idx|
|
||||
rel_path = path.sub("#{gem.dir}/", "")
|
||||
write_source_content(f, "#{gem.name}: #{rel_path}", path)
|
||||
# Clear macros between source files to avoid conflicts
|
||||
# (e.g., mrb_stat macro in file.c vs function in file_test.c)
|
||||
write_macro_cleanup(f, "#{gem.name}/#{File.basename(path)}") if idx < sources.size - 1
|
||||
end
|
||||
end
|
||||
|
||||
# Gem's compiled mrblib (Ruby-only gems like mruby-enum-ext have this)
|
||||
gem_mrblib = "#{gem.build_dir}/gem_mrblib.c"
|
||||
if File.exist?(gem_mrblib)
|
||||
write_source_content(f, "#{gem.name}: gem_mrblib.c", gem_mrblib)
|
||||
end
|
||||
|
||||
# Gem's init functions (GENERATED_TMP_mrb_*_gem_init/final)
|
||||
# Required for both C and Ruby-only gems
|
||||
gem_init = "#{gem.build_dir}/gem_init.c"
|
||||
if File.exist?(gem_init)
|
||||
write_source_content(f, "#{gem.name}: gem_init.c", gem_init)
|
||||
end
|
||||
|
||||
# Clear potentially conflicting macros after each gem
|
||||
write_macro_cleanup(f, gem.name)
|
||||
end
|
||||
|
||||
# gem_init.c - gem registration
|
||||
gem_init_path = "#{@build.build_dir}/mrbgems/gem_init.c"
|
||||
if File.exist?(gem_init_path)
|
||||
write_source_content(f, "gem_init.c", gem_init_path)
|
||||
end
|
||||
end
|
||||
|
||||
def write_source_content(f, name, path)
|
||||
return unless File.exist?(path)
|
||||
|
||||
content = File.read(path, mode: "rb")
|
||||
# For source files, comment out all mruby includes (they're in the header)
|
||||
# and inline local includes (like .cstub files)
|
||||
source_dir = File.dirname(path)
|
||||
content = transform_source_includes(content, source_dir)
|
||||
|
||||
f.puts "\n/* ======== #{name} ======== */"
|
||||
f.puts content
|
||||
end
|
||||
|
||||
def write_macro_cleanup(f, gem_name)
|
||||
f.puts "\n/* Cleanup macros from #{gem_name} to avoid conflicts */"
|
||||
CONFLICTING_MACROS.each do |macro|
|
||||
f.puts "#ifdef #{macro}"
|
||||
f.puts "#undef #{macro}"
|
||||
f.puts "#endif"
|
||||
end
|
||||
end
|
||||
|
||||
# X-macro pattern headers that must be inlined every time they're included
|
||||
# (not commented out) because they expand differently based on macro definitions
|
||||
XMACRO_HEADERS = %w[
|
||||
mruby/ops.h
|
||||
].freeze
|
||||
|
||||
def transform_source_includes(content, source_dir = nil)
|
||||
content.gsub(/^(\s*)(#\s*include\s+([<"])([^>"]+)[>"])/m) do |match|
|
||||
prefix = $1
|
||||
include_stmt = $2
|
||||
quote_type = $3 # < or "
|
||||
header = $4
|
||||
|
||||
# X-macro headers must be inlined every time (not commented out)
|
||||
# because they expand differently based on surrounding macro definitions
|
||||
if XMACRO_HEADERS.include?(header)
|
||||
xmacro_path = "#{include_dir}/#{header}"
|
||||
if File.exist?(xmacro_path)
|
||||
xmacro_content = File.read(xmacro_path, mode: "rb")
|
||||
"#{prefix}/* Inlined X-macro: #{header} */\n#{xmacro_content}"
|
||||
else
|
||||
match
|
||||
end
|
||||
# Comment out all mruby-related includes and any header already in amalgam
|
||||
elsif mruby_header?(header) || header == "mruby.h" ||
|
||||
@processed_headers.include?(header) || @gem_header_names.include?(header)
|
||||
"#{prefix}// #{include_stmt} - in amalgam header"
|
||||
elsif source_dir && quote_type == '"' && !header.include?("/")
|
||||
# Check for local includes like "known_errors_def.cstub"
|
||||
local_path = "#{source_dir}/#{header}"
|
||||
if File.exist?(local_path)
|
||||
local_content = File.read(local_path, mode: "rb")
|
||||
"#{prefix}/* Inlined: #{header} */\n#{local_content}"
|
||||
else
|
||||
match
|
||||
end
|
||||
else
|
||||
match
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# ========== Content Transformation ==========
|
||||
|
||||
def extract_include_guard(content)
|
||||
# Match #ifndef GUARD_NAME at start of file (after comments)
|
||||
if content =~ /\A(?:\/\*.*?\*\/\s*|\/\/[^\n]*\n)*\s*#ifndef\s+(\w+)\s*\n\s*#define\s+\1/m
|
||||
$1
|
||||
end
|
||||
end
|
||||
|
||||
def strip_include_guard(content, guard)
|
||||
return content unless guard
|
||||
|
||||
# Remove opening #ifndef GUARD but KEEP #define GUARD (needed for #ifdef checks)
|
||||
content = content.sub(/\A((?:\/\*.*?\*\/\s*|\/\/[^\n]*\n)*\s*)#ifndef\s+#{guard}\s*\n/m, '\1')
|
||||
|
||||
# Remove closing #endif (any comment is ok, guard name may not match exactly)
|
||||
content = content.sub(/\n#endif\s*(?:\/\*[^*]*\*\/|\/\/[^\n]*)?\s*\z/m, "\n")
|
||||
|
||||
content
|
||||
end
|
||||
|
||||
def transform_includes(content, inline: false)
|
||||
# Match both "#include" and "# include" (preprocessor allows spaces)
|
||||
# Only match includes at the start of a line (real preprocessor directives)
|
||||
content.gsub(/^(\s*)(#\s*include\s+[<"]([^>"]+)[>"])/m) do |match|
|
||||
prefix = $1
|
||||
include_stmt = $2
|
||||
header = $3
|
||||
|
||||
if already_included?(header)
|
||||
# Keep original whitespace, comment out the include
|
||||
"#{prefix}// #{include_stmt} - in amalgam"
|
||||
elsif inline && mruby_header?(header)
|
||||
# Recursively inline this header
|
||||
"#{prefix}#{inline_header(header)}"
|
||||
else
|
||||
match
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def already_included?(header)
|
||||
# Check if header was already processed
|
||||
# Only use end_with? matching for mruby headers to avoid matching
|
||||
# system headers like <time.h> against mruby/time.h
|
||||
if mruby_header?(header)
|
||||
@processed_headers.any? { |h| h == header || h.end_with?("/#{header}") }
|
||||
else
|
||||
@processed_headers.include?(header)
|
||||
end
|
||||
end
|
||||
|
||||
def mruby_header?(header)
|
||||
# Check if this is an mruby header that should be inlined/transformed
|
||||
header == "mruby.h" || header.start_with?("mruby/") ||
|
||||
%w[mrbconf.h boxing_nan.h boxing_word.h boxing_no.h common.h object.h value_array.h].include?(header)
|
||||
end
|
||||
|
||||
def inline_header(header)
|
||||
# Find the full path for this header
|
||||
if header == "mruby.h" || header == "mrbconf.h" || header.start_with?("mruby/")
|
||||
full_header = header
|
||||
# Check both source include dir and build include dir (for generated headers)
|
||||
path = "#{include_dir}/#{header}"
|
||||
path = "#{build_include_dir}/#{header}" unless File.exist?(path)
|
||||
else
|
||||
# Relative includes like "boxing_word.h" -> "mruby/boxing_word.h"
|
||||
full_header = "mruby/#{header}"
|
||||
path = "#{include_dir}/#{full_header}"
|
||||
path = "#{build_include_dir}/#{full_header}" unless File.exist?(path)
|
||||
end
|
||||
|
||||
return "/* #{header} - not found */" unless File.exist?(path)
|
||||
|
||||
# Mark as processed to avoid infinite recursion
|
||||
@processed_headers << header
|
||||
@processed_headers << full_header
|
||||
|
||||
content = File.read(path, mode: "rb")
|
||||
guard = extract_include_guard(content)
|
||||
|
||||
if guard && @processed_guards[guard]
|
||||
return "/* #{header} - already included */"
|
||||
end
|
||||
@processed_guards[guard] = true if guard
|
||||
|
||||
content = strip_include_guard(content, guard) if guard
|
||||
content = transform_includes(content, inline: true)
|
||||
|
||||
"\n/* ======== #{full_header} (inlined) ======== */\n#{content}"
|
||||
end
|
||||
end
|
||||
end
|
||||
+26
-10
@@ -60,11 +60,7 @@ module MRuby
|
||||
def mruby_config_path
|
||||
path = ENV['MRUBY_CONFIG'] || ENV['CONFIG']
|
||||
if path.nil? || path.empty?
|
||||
path = if Dir.pwd != MRUBY_ROOT && File.file?("./build_config.rb")
|
||||
"./build_config.rb"
|
||||
else
|
||||
"#{MRUBY_ROOT}/build_config/default.rb"
|
||||
end
|
||||
path = "#{MRUBY_ROOT}/build_config/default.rb"
|
||||
elsif !File.file?(path) && !Pathname.new(path).absolute?
|
||||
f = "#{MRUBY_ROOT}/build_config/#{path}.rb"
|
||||
path = File.exist?(f) ? f : File.extname(path).empty? ? f : path
|
||||
@@ -134,6 +130,7 @@ module MRuby
|
||||
@enable_bintest = false
|
||||
@enable_test = false
|
||||
@enable_lock = true
|
||||
@enable_presym = true
|
||||
@enable_benchmark = true
|
||||
@mrbcfile_external = false
|
||||
@internal = internal
|
||||
@@ -155,9 +152,13 @@ module MRuby
|
||||
current.instance_eval(&block)
|
||||
ensure
|
||||
if current.libmruby_enabled? && !current.mrbcfile_external?
|
||||
current.create_mrbc_build if current.host? || current.gems["mruby-bin-mrbc"]
|
||||
if current.presym_enabled?
|
||||
current.create_mrbc_build if current.host? || current.gems["mruby-bin-mrbc"]
|
||||
elsif current.host?
|
||||
current.build_mrbc_exec
|
||||
end
|
||||
end
|
||||
current.presym = Presym.new(current)
|
||||
current.presym = Presym.new(current) if current.presym_enabled?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -183,6 +184,17 @@ module MRuby
|
||||
@enable_debug = true
|
||||
end
|
||||
|
||||
def presym_enabled?
|
||||
@enable_presym
|
||||
end
|
||||
|
||||
def disable_presym
|
||||
if @enable_presym
|
||||
@enable_presym = false
|
||||
compilers.each{|c| c.defines << "MRB_NO_PRESYM"}
|
||||
end
|
||||
end
|
||||
|
||||
def disable_lock
|
||||
@enable_lock = false
|
||||
end
|
||||
@@ -253,7 +265,7 @@ module MRuby
|
||||
if cxx_src
|
||||
obj ||= cxx_src + @exts.object
|
||||
dsts = [obj]
|
||||
dsts << (cxx_src + @exts.presym_preprocessed)
|
||||
dsts << (cxx_src + @exts.presym_preprocessed) if presym_enabled?
|
||||
defines = []
|
||||
include_paths = ["#{MRUBY_ROOT}/src", *includes]
|
||||
dsts.each do |dst|
|
||||
@@ -367,7 +379,7 @@ EOS
|
||||
end
|
||||
[@cc, *(@cxx if cxx_exception_enabled?)].each do |compiler|
|
||||
compiler.define_rules(@build_dir, MRUBY_ROOT, @exts.object)
|
||||
compiler.define_rules(@build_dir, MRUBY_ROOT, @exts.presym_preprocessed)
|
||||
compiler.define_rules(@build_dir, MRUBY_ROOT, @exts.presym_preprocessed) if presym_enabled?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -553,7 +565,7 @@ EOS
|
||||
end
|
||||
build.build_mrbc_exec
|
||||
build.disable_libmruby
|
||||
build.presym = Presym.new(build)
|
||||
build.disable_presym
|
||||
@mrbc_build = build
|
||||
self.mrbcfile = build.mrbcfile
|
||||
build
|
||||
@@ -576,6 +588,7 @@ EOS
|
||||
conf.toolchain
|
||||
conf.build_mrbc_exec
|
||||
conf.disable_libmruby
|
||||
conf.disable_presym
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -600,6 +613,9 @@ EOS
|
||||
targets = @gems.select { |v| File.directory? "#{v.dir}/bintest" }.map { |v| filename v.dir }
|
||||
mrbc = @gems["mruby-bin-mrbc"] ? exefile("#{@build_dir}/bin/mrbc") : mrbcfile
|
||||
|
||||
emulator = @test_runner.command
|
||||
emulator = @test_runner.shellquote(emulator) if emulator
|
||||
|
||||
env = {
|
||||
"BUILD_DIR" => @build_dir,
|
||||
"MRBCFILE" => mrbc,
|
||||
|
||||
@@ -144,13 +144,13 @@ module MRuby
|
||||
#
|
||||
# === Example of +.d+ file
|
||||
#
|
||||
# ==== Without `-MP` compiler flag
|
||||
# ==== Without <tt>-MP</tt> compiler flag
|
||||
#
|
||||
# /build/host/src/array.o: /src/array.c \
|
||||
# /include/mruby/common.h /include/mruby/value.h \
|
||||
# /src/value_array.h
|
||||
#
|
||||
# ==== With `-MP` compiler flag
|
||||
# ==== With <tt>-MP</tt> compiler flag
|
||||
#
|
||||
# /build/host/src/array.o: /src/array.c \
|
||||
# /include/mruby/common.h /include/mruby/value.h \
|
||||
|
||||
@@ -32,21 +32,14 @@ module MRuby
|
||||
gemrake = File.join(checkout.full_gemdir, "mrbgem.rake")
|
||||
fail "Can't find #{gemrake}" unless File.exist?(gemrake)
|
||||
|
||||
current_build = MRuby::Build.current
|
||||
build = self.is_a?(MRuby::Build) ? self : MRuby::Build.current
|
||||
MRuby::Build.current = build
|
||||
Gem.current = nil
|
||||
begin
|
||||
load gemrake
|
||||
ensure
|
||||
MRuby::Build.current = current_build
|
||||
end
|
||||
load gemrake
|
||||
return nil unless Gem.current
|
||||
current = Gem.current
|
||||
|
||||
# Add it to gems
|
||||
current.dir = checkout.full_gemdir
|
||||
current.build = build
|
||||
current.build = self.is_a?(MRuby::Build) ? self : MRuby::Build.current
|
||||
current.build_config_initializer = block
|
||||
gems << current
|
||||
|
||||
|
||||
+8
-59
@@ -53,8 +53,10 @@ module MRuby
|
||||
return if defined?(@bins) # return if already set up
|
||||
|
||||
MRuby::Gem.current = self
|
||||
reset_commands # for backward compatibility, reset the commands from the beginning.
|
||||
@build_settings = nil
|
||||
MRuby::Build::COMMANDS.each do |command|
|
||||
instance_variable_set("@#{command}", @build.send(command).clone)
|
||||
end
|
||||
@linker.run_attrs.each(&:clear)
|
||||
|
||||
@rbfiles = Dir.glob("#{@dir}/mrblib/**/*.rb").sort
|
||||
@objs = srcs_to_objs("src")
|
||||
@@ -68,10 +70,7 @@ module MRuby
|
||||
|
||||
@requirements = []
|
||||
@export_include_paths = []
|
||||
# Headers in include/ are for inter-gem use only
|
||||
# Headers in include/export/ are exported to external users via mruby-config
|
||||
export_dir = "#{dir}/include/export"
|
||||
@export_include_paths << export_dir if File.directory?(export_dir)
|
||||
@export_include_paths << "#{dir}/include" if File.directory? "#{dir}/include"
|
||||
|
||||
instance_eval(&@initializer)
|
||||
|
||||
@@ -96,7 +95,7 @@ module MRuby
|
||||
|
||||
def setup_compilers
|
||||
(core? ? [@cc, *(@cxx if build.cxx_exception_enabled?)] : compilers).each do |compiler|
|
||||
compiler.define_rules build_dir, @dir, @build.exts.presym_preprocessed
|
||||
compiler.define_rules build_dir, @dir, @build.exts.presym_preprocessed if build.presym_enabled?
|
||||
compiler.define_rules build_dir, @dir, @build.exts.object
|
||||
compiler.defines << %Q[MRBGEM_#{funcname.upcase}_VERSION=#{version}]
|
||||
compiler.include_paths << "#{@dir}/include" if File.directory? "#{@dir}/include"
|
||||
@@ -119,7 +118,7 @@ module MRuby
|
||||
end
|
||||
|
||||
def cdump?
|
||||
@cdump
|
||||
build.presym_enabled? && @cdump
|
||||
end
|
||||
|
||||
def core?
|
||||
@@ -193,19 +192,6 @@ module MRuby
|
||||
end
|
||||
end
|
||||
|
||||
def build_settings(&blk)
|
||||
@build_settings = blk
|
||||
end
|
||||
|
||||
def setup_build
|
||||
if @build_settings
|
||||
# by this point, build.cc or other commands may have been modified.
|
||||
# therefore, reset the commands again before calling build_settings.
|
||||
reset_commands
|
||||
@build_settings.call(self)
|
||||
end
|
||||
end
|
||||
|
||||
def define_gem_init_builder
|
||||
file "#{build_dir}/gem_init.c" => [build.mrbcfile, __FILE__] + [rbfiles].flatten do |t|
|
||||
mkdir_p build_dir
|
||||
@@ -317,13 +303,6 @@ module MRuby
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
private def reset_commands
|
||||
MRuby::Build::COMMANDS.each do |command|
|
||||
instance_variable_set("@#{command}", @build.send(command).clone)
|
||||
end
|
||||
@linker.run_attrs.each(&:clear)
|
||||
end
|
||||
end # Specification
|
||||
|
||||
class Version
|
||||
@@ -422,21 +401,7 @@ module MRuby
|
||||
end
|
||||
end
|
||||
|
||||
def setup(build)
|
||||
gemset = nil
|
||||
begin
|
||||
gemset_prev = gemset
|
||||
self.each(&:setup)
|
||||
gemset = self.setup_dependencies(build).keys.sort
|
||||
end until gemset == gemset_prev
|
||||
end
|
||||
|
||||
def setup_build
|
||||
each(&:setup_build)
|
||||
self
|
||||
end
|
||||
|
||||
def setup_dependencies(build)
|
||||
def generate_gem_table build
|
||||
gem_table = each_with_object({}) { |spec, h| h[spec.name] = spec }
|
||||
|
||||
default_gems = {}
|
||||
@@ -459,12 +424,6 @@ module MRuby
|
||||
end
|
||||
end
|
||||
|
||||
gem_table
|
||||
end
|
||||
|
||||
def generate_gem_table(build)
|
||||
gem_table = setup_dependencies(build)
|
||||
|
||||
each do |g|
|
||||
g.dependencies.each do |dep|
|
||||
name = dep[:gem]
|
||||
@@ -545,16 +504,6 @@ module MRuby
|
||||
# as circular dependency has already detected in the caller.
|
||||
import_include_paths(dep_g)
|
||||
|
||||
# Add dependency's include/ to compiler paths (for inter-gem use)
|
||||
dep_include = "#{dep_g.dir}/include"
|
||||
if File.directory?(dep_include)
|
||||
g.compilers.each do |compiler|
|
||||
compiler.include_paths << dep_include
|
||||
compiler.include_paths.uniq!
|
||||
end
|
||||
end
|
||||
|
||||
# Propagate any explicitly set export_include_paths
|
||||
dep_g.export_include_paths.uniq!
|
||||
g.compilers.each do |compiler|
|
||||
compiler.include_paths += dep_g.export_include_paths
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
MRuby::GemBox.new do |conf|
|
||||
Dir.glob("#{root}/mrbgems/mruby-*/mrbgem.rake") do |x|
|
||||
g = File.basename(File.dirname(x))
|
||||
conf.gem :core => g unless g =~ /^mruby-(?:bin-debugger|test|sleep)$/
|
||||
conf.gem :core => g unless g =~ /^mruby-(?:bin-debugger|test)$/
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
MRuby::Gem::Specification.new('hal-posix-dir') do |spec|
|
||||
spec.license = 'MIT'
|
||||
spec.authors = 'mruby developers'
|
||||
spec.summary = 'POSIX HAL for mruby-dir (Linux, macOS, BSD, Unix)'
|
||||
|
||||
spec.add_dependency 'mruby-dir', core: 'mruby-dir'
|
||||
end
|
||||
@@ -1,193 +0,0 @@
|
||||
/*
|
||||
** dir_hal.c - POSIX HAL implementation for mruby-dir
|
||||
**
|
||||
** See Copyright Notice in mruby.h
|
||||
**
|
||||
** POSIX implementation for directory operations using standard POSIX APIs.
|
||||
** Supported platforms: Linux, macOS, BSD, Unix
|
||||
*/
|
||||
|
||||
#include <mruby.h>
|
||||
#include "dir_hal.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <dirent.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
#ifdef HAVE_SYS_PARAM_H
|
||||
#include <sys/param.h>
|
||||
#endif
|
||||
|
||||
/* On POSIX, mrb_dir_handle wraps DIR */
|
||||
struct mrb_dir_handle {
|
||||
DIR *dir;
|
||||
};
|
||||
|
||||
/*
|
||||
* Directory Operations
|
||||
*/
|
||||
|
||||
mrb_dir_handle*
|
||||
mrb_hal_dir_open(mrb_state *mrb, const char *path)
|
||||
{
|
||||
DIR *dir = opendir(path);
|
||||
if (dir == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
mrb_dir_handle *handle = (mrb_dir_handle*)mrb_malloc(mrb, sizeof(mrb_dir_handle));
|
||||
handle->dir = dir;
|
||||
return handle;
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_dir_close(mrb_state *mrb, mrb_dir_handle *handle)
|
||||
{
|
||||
int result = closedir(handle->dir);
|
||||
mrb_free(mrb, handle);
|
||||
return result;
|
||||
}
|
||||
|
||||
const char*
|
||||
mrb_hal_dir_read(mrb_state *mrb, mrb_dir_handle *handle)
|
||||
{
|
||||
(void)mrb;
|
||||
struct dirent *dp = readdir(handle->dir);
|
||||
return dp ? dp->d_name : NULL;
|
||||
}
|
||||
|
||||
void
|
||||
mrb_hal_dir_rewind(mrb_state *mrb, mrb_dir_handle *handle)
|
||||
{
|
||||
(void)mrb;
|
||||
rewinddir(handle->dir);
|
||||
}
|
||||
|
||||
/*
|
||||
* Optional Operations
|
||||
*/
|
||||
|
||||
int
|
||||
mrb_hal_dir_seek(mrb_state *mrb, mrb_dir_handle *handle, long pos)
|
||||
{
|
||||
#if defined(__ANDROID__)
|
||||
/* Android doesn't have reliable seekdir */
|
||||
(void)mrb; (void)handle; (void)pos;
|
||||
errno = ENOSYS;
|
||||
return -1;
|
||||
#else
|
||||
(void)mrb;
|
||||
seekdir(handle->dir, pos);
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
long
|
||||
mrb_hal_dir_tell(mrb_state *mrb, mrb_dir_handle *handle)
|
||||
{
|
||||
#if defined(__ANDROID__)
|
||||
/* Android doesn't have reliable telldir */
|
||||
(void)mrb; (void)handle;
|
||||
errno = ENOSYS;
|
||||
return -1;
|
||||
#else
|
||||
(void)mrb;
|
||||
return telldir(handle->dir);
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* Filesystem Operations
|
||||
*/
|
||||
|
||||
int
|
||||
mrb_hal_dir_mkdir(mrb_state *mrb, const char *path, int mode)
|
||||
{
|
||||
(void)mrb;
|
||||
return mkdir(path, (mode_t)mode);
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_dir_rmdir(mrb_state *mrb, const char *path)
|
||||
{
|
||||
(void)mrb;
|
||||
return rmdir(path);
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_dir_chdir(mrb_state *mrb, const char *path)
|
||||
{
|
||||
(void)mrb;
|
||||
return chdir(path);
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_dir_getcwd(mrb_state *mrb, char *buf, size_t size)
|
||||
{
|
||||
(void)mrb;
|
||||
return getcwd(buf, size) ? 0 : -1;
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_dir_chroot(mrb_state *mrb, const char *path)
|
||||
{
|
||||
#if defined(__ANDROID__) || defined(__MSDOS__)
|
||||
/* Not available on these platforms */
|
||||
(void)mrb; (void)path;
|
||||
errno = ENOSYS;
|
||||
return -1;
|
||||
#else
|
||||
(void)mrb;
|
||||
return chroot(path);
|
||||
#endif
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_dir_is_directory(mrb_state *mrb, const char *path)
|
||||
{
|
||||
struct stat sb;
|
||||
(void)mrb;
|
||||
|
||||
if (stat(path, &sb) == 0 && S_ISDIR(sb.st_mode)) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* HAL Initialization/Finalization
|
||||
*/
|
||||
|
||||
void
|
||||
mrb_hal_dir_init(mrb_state *mrb)
|
||||
{
|
||||
(void)mrb;
|
||||
/* No initialization needed for POSIX */
|
||||
}
|
||||
|
||||
void
|
||||
mrb_hal_dir_final(mrb_state *mrb)
|
||||
{
|
||||
(void)mrb;
|
||||
/* No cleanup needed for POSIX */
|
||||
}
|
||||
|
||||
/*
|
||||
* Gem initialization
|
||||
*/
|
||||
|
||||
void
|
||||
mrb_hal_posix_dir_gem_init(mrb_state *mrb)
|
||||
{
|
||||
(void)mrb;
|
||||
/* HAL interface functions are called by mruby-dir gem */
|
||||
}
|
||||
|
||||
void
|
||||
mrb_hal_posix_dir_gem_final(mrb_state *mrb)
|
||||
{
|
||||
(void)mrb;
|
||||
/* Cleanup handled by mrb_hal_dir_final called from mruby-dir */
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
MRuby::Gem::Specification.new('hal-posix-io') do |spec|
|
||||
spec.license = 'MIT'
|
||||
spec.author = 'mruby developers'
|
||||
spec.summary = 'POSIX HAL for mruby-io (Linux, macOS, BSD, Unix)'
|
||||
|
||||
# HAL gem depends on feature gem - brings in mruby-io automatically
|
||||
spec.add_dependency 'mruby-io', core: 'mruby-io'
|
||||
end
|
||||
@@ -1,602 +0,0 @@
|
||||
/*
|
||||
** io_hal.c - POSIX HAL implementation for mruby-io
|
||||
**
|
||||
** See Copyright Notice in mruby.h
|
||||
**
|
||||
** POSIX implementation for I/O operations using standard POSIX APIs.
|
||||
** Supported platforms: Linux, macOS, BSD, Unix
|
||||
*/
|
||||
|
||||
#include <mruby.h>
|
||||
#include "io_hal.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/file.h>
|
||||
#include <sys/param.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <pwd.h>
|
||||
|
||||
#ifndef __DJGPP__
|
||||
#include <libgen.h>
|
||||
#endif
|
||||
|
||||
/* Maximum path length */
|
||||
#ifndef PATH_MAX
|
||||
#define PATH_MAX 4096
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Helper Functions
|
||||
*/
|
||||
|
||||
/* Convert POSIX struct stat to mrb_io_stat */
|
||||
static void
|
||||
convert_stat(const struct stat *src, mrb_io_stat *dst)
|
||||
{
|
||||
/* Extract time values FIRST while macros are still defined.
|
||||
* On POSIX systems, st_atime may be a macro for st_atim.tv_sec */
|
||||
time_t atime_val, mtime_val, ctime_val;
|
||||
#if defined(st_atime)
|
||||
/* st_atime is a macro - use it to extract from src */
|
||||
atime_val = src->st_atime;
|
||||
mtime_val = src->st_mtime;
|
||||
ctime_val = src->st_ctime;
|
||||
#elif defined(__APPLE__) || defined(__FreeBSD__) || \
|
||||
defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__)
|
||||
/* BSD/macOS: st_atime is typically a direct member */
|
||||
atime_val = src->st_atime;
|
||||
mtime_val = src->st_mtime;
|
||||
ctime_val = src->st_ctime;
|
||||
#else
|
||||
/* POSIX.1-2008: use st_atim.tv_sec directly */
|
||||
atime_val = src->st_atim.tv_sec;
|
||||
mtime_val = src->st_mtim.tv_sec;
|
||||
ctime_val = src->st_ctim.tv_sec;
|
||||
#endif
|
||||
|
||||
/* Undefine macros to avoid interference with mrb_io_stat fields */
|
||||
#undef st_atime
|
||||
#undef st_mtime
|
||||
#undef st_ctime
|
||||
|
||||
dst->st_dev = (uint64_t)src->st_dev;
|
||||
dst->st_ino = (uint64_t)src->st_ino;
|
||||
dst->st_mode = (uint32_t)src->st_mode;
|
||||
dst->st_nlink = (uint32_t)src->st_nlink;
|
||||
dst->st_uid = (uint32_t)src->st_uid;
|
||||
dst->st_gid = (uint32_t)src->st_gid;
|
||||
dst->st_rdev = (uint64_t)src->st_rdev;
|
||||
dst->st_size = (int64_t)src->st_size;
|
||||
dst->st_atime = (int64_t)atime_val;
|
||||
dst->st_mtime = (int64_t)mtime_val;
|
||||
dst->st_ctime = (int64_t)ctime_val;
|
||||
|
||||
#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
|
||||
dst->st_blksize = (int64_t)src->st_blksize;
|
||||
#else
|
||||
dst->st_blksize = 512;
|
||||
#endif
|
||||
#ifdef HAVE_STRUCT_STAT_ST_BLOCKS
|
||||
dst->st_blocks = (int64_t)src->st_blocks;
|
||||
#else
|
||||
dst->st_blocks = (dst->st_size + 511) / 512;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* File Operations
|
||||
*/
|
||||
|
||||
int
|
||||
mrb_hal_io_stat(mrb_state *mrb, const char *path, mrb_io_stat *st)
|
||||
{
|
||||
struct stat s;
|
||||
(void)mrb;
|
||||
|
||||
if (stat(path, &s) == -1) {
|
||||
return -1;
|
||||
}
|
||||
convert_stat(&s, st);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_io_fstat(mrb_state *mrb, int fd, mrb_io_stat *st)
|
||||
{
|
||||
struct stat s;
|
||||
(void)mrb;
|
||||
|
||||
if (fstat(fd, &s) == -1) {
|
||||
return -1;
|
||||
}
|
||||
convert_stat(&s, st);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_io_lstat(mrb_state *mrb, const char *path, mrb_io_stat *st)
|
||||
{
|
||||
struct stat s;
|
||||
(void)mrb;
|
||||
|
||||
if (lstat(path, &s) == -1) {
|
||||
return -1;
|
||||
}
|
||||
convert_stat(&s, st);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_io_chmod(mrb_state *mrb, const char *path, uint32_t mode)
|
||||
{
|
||||
(void)mrb;
|
||||
return chmod(path, (mode_t)mode);
|
||||
}
|
||||
|
||||
uint32_t
|
||||
mrb_hal_io_umask(mrb_state *mrb, int32_t mask)
|
||||
{
|
||||
mode_t old;
|
||||
(void)mrb;
|
||||
|
||||
if (mask < 0) {
|
||||
/* Just query current value */
|
||||
old = umask(0);
|
||||
umask(old);
|
||||
}
|
||||
else {
|
||||
old = umask((mode_t)mask);
|
||||
}
|
||||
return (uint32_t)old;
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_io_ftruncate(mrb_state *mrb, int fd, int64_t length)
|
||||
{
|
||||
(void)mrb;
|
||||
return ftruncate(fd, (off_t)length);
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_io_flock(mrb_state *mrb, int fd, int operation)
|
||||
{
|
||||
(void)mrb;
|
||||
|
||||
while (flock(fd, operation) == -1) {
|
||||
if (errno == EINTR) {
|
||||
continue; /* Retry on interrupt */
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_io_unlink(mrb_state *mrb, const char *path)
|
||||
{
|
||||
(void)mrb;
|
||||
return unlink(path);
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_io_rename(mrb_state *mrb, const char *oldpath, const char *newpath)
|
||||
{
|
||||
(void)mrb;
|
||||
return rename(oldpath, newpath);
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_io_symlink(mrb_state *mrb, const char *target, const char *linkpath)
|
||||
{
|
||||
(void)mrb;
|
||||
return symlink(target, linkpath);
|
||||
}
|
||||
|
||||
int64_t
|
||||
mrb_hal_io_readlink(mrb_state *mrb, const char *path, char *buf, size_t bufsize)
|
||||
{
|
||||
ssize_t rc;
|
||||
(void)mrb;
|
||||
|
||||
rc = readlink(path, buf, bufsize);
|
||||
return (int64_t)rc;
|
||||
}
|
||||
|
||||
char*
|
||||
mrb_hal_io_realpath(mrb_state *mrb, const char *path, char *resolved)
|
||||
{
|
||||
(void)mrb;
|
||||
return realpath(path, resolved);
|
||||
}
|
||||
|
||||
char*
|
||||
mrb_hal_io_getcwd(mrb_state *mrb, char *buf, size_t size)
|
||||
{
|
||||
(void)mrb;
|
||||
return getcwd(buf, size);
|
||||
}
|
||||
|
||||
const char*
|
||||
mrb_hal_io_getenv(mrb_state *mrb, const char *name)
|
||||
{
|
||||
(void)mrb;
|
||||
return getenv(name);
|
||||
}
|
||||
|
||||
const char*
|
||||
mrb_hal_io_gethome(mrb_state *mrb, const char *username)
|
||||
{
|
||||
const char *home;
|
||||
|
||||
if (username == NULL || *username == '\0') {
|
||||
/* Get current user's home */
|
||||
home = getenv("HOME");
|
||||
if (home == NULL) {
|
||||
errno = ENOENT;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Get specified user's home */
|
||||
struct passwd *pwd = getpwnam(username);
|
||||
if (pwd == NULL) {
|
||||
errno = ENOENT;
|
||||
return NULL;
|
||||
}
|
||||
home = pwd->pw_dir;
|
||||
}
|
||||
|
||||
return home;
|
||||
}
|
||||
|
||||
/*
|
||||
* Core I/O Operations
|
||||
*/
|
||||
|
||||
int
|
||||
mrb_hal_io_open(mrb_state *mrb, const char *path, int flags, uint32_t mode)
|
||||
{
|
||||
int fd;
|
||||
(void)mrb;
|
||||
|
||||
fd = open(path, flags, (mode_t)mode);
|
||||
if (fd == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Set close-on-exec for non-standard descriptors */
|
||||
#if defined(F_GETFD) && defined(F_SETFD) && defined(FD_CLOEXEC)
|
||||
if (fd > 2) {
|
||||
int fd_flags = fcntl(fd, F_GETFD);
|
||||
if (fd_flags != -1) {
|
||||
fcntl(fd, F_SETFD, fd_flags | FD_CLOEXEC);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_io_close(mrb_state *mrb, int fd)
|
||||
{
|
||||
(void)mrb;
|
||||
return close(fd);
|
||||
}
|
||||
|
||||
int64_t
|
||||
mrb_hal_io_read(mrb_state *mrb, int fd, void *buf, size_t count)
|
||||
{
|
||||
ssize_t n;
|
||||
(void)mrb;
|
||||
|
||||
n = read(fd, buf, count);
|
||||
return (int64_t)n;
|
||||
}
|
||||
|
||||
int64_t
|
||||
mrb_hal_io_write(mrb_state *mrb, int fd, const void *buf, size_t count)
|
||||
{
|
||||
ssize_t n;
|
||||
(void)mrb;
|
||||
|
||||
n = write(fd, buf, count);
|
||||
return (int64_t)n;
|
||||
}
|
||||
|
||||
int64_t
|
||||
mrb_hal_io_lseek(mrb_state *mrb, int fd, int64_t offset, int whence)
|
||||
{
|
||||
off_t pos;
|
||||
int posix_whence;
|
||||
(void)mrb;
|
||||
|
||||
/* Convert MRB_IO_SEEK_* to POSIX SEEK_* */
|
||||
switch (whence) {
|
||||
case MRB_IO_SEEK_SET: posix_whence = SEEK_SET; break;
|
||||
case MRB_IO_SEEK_CUR: posix_whence = SEEK_CUR; break;
|
||||
case MRB_IO_SEEK_END: posix_whence = SEEK_END; break;
|
||||
default:
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
pos = lseek(fd, (off_t)offset, posix_whence);
|
||||
return (int64_t)pos;
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_io_dup(mrb_state *mrb, int fd)
|
||||
{
|
||||
int new_fd;
|
||||
(void)mrb;
|
||||
|
||||
new_fd = dup(fd);
|
||||
if (new_fd == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Set close-on-exec */
|
||||
#if defined(F_GETFD) && defined(F_SETFD) && defined(FD_CLOEXEC)
|
||||
if (new_fd > 2) {
|
||||
int fd_flags = fcntl(new_fd, F_GETFD);
|
||||
if (fd_flags != -1) {
|
||||
fcntl(new_fd, F_SETFD, fd_flags | FD_CLOEXEC);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return new_fd;
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_io_fcntl(mrb_state *mrb, int fd, int cmd, int arg)
|
||||
{
|
||||
(void)mrb;
|
||||
return fcntl(fd, cmd, arg);
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_io_isatty(mrb_state *mrb, int fd)
|
||||
{
|
||||
(void)mrb;
|
||||
return isatty(fd) ? 1 : 0;
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_io_pipe(mrb_state *mrb, int fds[2])
|
||||
{
|
||||
int ret;
|
||||
(void)mrb;
|
||||
|
||||
ret = pipe(fds);
|
||||
if (ret == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Set close-on-exec on both ends */
|
||||
#if defined(F_GETFD) && defined(F_SETFD) && defined(FD_CLOEXEC)
|
||||
for (int i = 0; i < 2; i++) {
|
||||
int fd_flags = fcntl(fds[i], F_GETFD);
|
||||
if (fd_flags != -1) {
|
||||
fcntl(fds[i], F_SETFD, fd_flags | FD_CLOEXEC);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Process Operations
|
||||
*/
|
||||
|
||||
int
|
||||
mrb_hal_io_spawn_process(mrb_state *mrb, const char *cmd,
|
||||
int stdin_fd, int stdout_fd, int stderr_fd,
|
||||
int *pid)
|
||||
{
|
||||
pid_t child_pid;
|
||||
(void)mrb;
|
||||
|
||||
/* Skip leading whitespace */
|
||||
while (*cmd == ' ' || *cmd == '\t' || *cmd == '\n') {
|
||||
cmd++;
|
||||
}
|
||||
|
||||
if (!*cmd) {
|
||||
errno = ENOENT;
|
||||
return -1;
|
||||
}
|
||||
|
||||
child_pid = fork();
|
||||
if (child_pid == -1) {
|
||||
/* Fork failed */
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (child_pid == 0) {
|
||||
/* Child process */
|
||||
|
||||
/* Redirect stdin */
|
||||
if (stdin_fd != -1) {
|
||||
dup2(stdin_fd, STDIN_FILENO);
|
||||
if (stdin_fd > 2) close(stdin_fd);
|
||||
}
|
||||
|
||||
/* Redirect stdout */
|
||||
if (stdout_fd != -1) {
|
||||
dup2(stdout_fd, STDOUT_FILENO);
|
||||
if (stdout_fd > 2) close(stdout_fd);
|
||||
}
|
||||
|
||||
/* Redirect stderr */
|
||||
if (stderr_fd != -1) {
|
||||
dup2(stderr_fd, STDERR_FILENO);
|
||||
if (stderr_fd > 2) close(stderr_fd);
|
||||
}
|
||||
|
||||
/* Close all other file descriptors */
|
||||
int max_fd = sysconf(_SC_OPEN_MAX);
|
||||
if (max_fd == -1) max_fd = 1024;
|
||||
for (int i = 3; i < max_fd; i++) {
|
||||
close(i);
|
||||
}
|
||||
|
||||
/* Execute command via shell */
|
||||
execl("/bin/sh", "sh", "-c", cmd, (char*)NULL);
|
||||
|
||||
/* If execl returns, it failed */
|
||||
_exit(127);
|
||||
}
|
||||
|
||||
/* Parent process */
|
||||
*pid = (int)child_pid;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_io_waitpid(mrb_state *mrb, int pid, int *status, int options)
|
||||
{
|
||||
pid_t result;
|
||||
int stat;
|
||||
(void)mrb;
|
||||
|
||||
result = waitpid((pid_t)pid, &stat, options);
|
||||
if (result == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (status != NULL) {
|
||||
*status = stat;
|
||||
}
|
||||
|
||||
return (int)result;
|
||||
}
|
||||
|
||||
/*
|
||||
* I/O Multiplexing
|
||||
*/
|
||||
|
||||
struct mrb_io_fdset {
|
||||
fd_set fds;
|
||||
};
|
||||
|
||||
mrb_io_fdset*
|
||||
mrb_hal_io_fdset_alloc(mrb_state *mrb)
|
||||
{
|
||||
mrb_io_fdset *fdset = (mrb_io_fdset*)mrb_malloc(mrb, sizeof(mrb_io_fdset));
|
||||
FD_ZERO(&fdset->fds);
|
||||
return fdset;
|
||||
}
|
||||
|
||||
void
|
||||
mrb_hal_io_fdset_free(mrb_state *mrb, mrb_io_fdset *fdset)
|
||||
{
|
||||
if (fdset) {
|
||||
mrb_free(mrb, fdset);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
mrb_hal_io_fdset_zero(mrb_state *mrb, mrb_io_fdset *fdset)
|
||||
{
|
||||
(void)mrb;
|
||||
if (fdset) {
|
||||
FD_ZERO(&fdset->fds);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
mrb_hal_io_fdset_set(mrb_state *mrb, int fd, mrb_io_fdset *fdset)
|
||||
{
|
||||
(void)mrb;
|
||||
if (fd < 0 || fd >= FD_SETSIZE) {
|
||||
mrb_raise(mrb, E_ARGUMENT_ERROR, "fd is out of range");
|
||||
return;
|
||||
}
|
||||
if (fdset) {
|
||||
FD_SET(fd, &fdset->fds);
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_io_fdset_isset(mrb_state *mrb, int fd, mrb_io_fdset *fdset)
|
||||
{
|
||||
(void)mrb;
|
||||
if (fd < 0 || fd >= FD_SETSIZE) {
|
||||
mrb_raise(mrb, E_ARGUMENT_ERROR, "fd is out of range");
|
||||
return 0;
|
||||
}
|
||||
if (fdset) {
|
||||
return FD_ISSET(fd, &fdset->fds);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_io_select(mrb_state *mrb, int nfds,
|
||||
mrb_io_fdset *readfds,
|
||||
mrb_io_fdset *writefds,
|
||||
mrb_io_fdset *errorfds,
|
||||
mrb_io_timeval *timeout)
|
||||
{
|
||||
fd_set *r = readfds ? &readfds->fds : NULL;
|
||||
fd_set *w = writefds ? &writefds->fds : NULL;
|
||||
fd_set *e = errorfds ? &errorfds->fds : NULL;
|
||||
struct timeval *tv = NULL;
|
||||
struct timeval tv_storage;
|
||||
(void)mrb;
|
||||
|
||||
if (timeout) {
|
||||
tv_storage.tv_sec = (time_t)timeout->tv_sec;
|
||||
tv_storage.tv_usec = (suseconds_t)timeout->tv_usec;
|
||||
tv = &tv_storage;
|
||||
}
|
||||
|
||||
return select(nfds, r, w, e, tv);
|
||||
}
|
||||
|
||||
/*
|
||||
* HAL Initialization/Finalization
|
||||
*/
|
||||
|
||||
void
|
||||
mrb_hal_io_init(mrb_state *mrb)
|
||||
{
|
||||
(void)mrb;
|
||||
/* No special initialization needed for POSIX */
|
||||
}
|
||||
|
||||
void
|
||||
mrb_hal_io_final(mrb_state *mrb)
|
||||
{
|
||||
(void)mrb;
|
||||
/* No special cleanup needed for POSIX */
|
||||
}
|
||||
|
||||
/*
|
||||
* Gem initialization
|
||||
*/
|
||||
|
||||
void
|
||||
mrb_hal_posix_io_gem_init(mrb_state *mrb)
|
||||
{
|
||||
(void)mrb;
|
||||
/* HAL interface functions are called by mruby-io gem */
|
||||
}
|
||||
|
||||
void
|
||||
mrb_hal_posix_io_gem_final(mrb_state *mrb)
|
||||
{
|
||||
(void)mrb;
|
||||
/* Cleanup handled by mrb_hal_io_final called from mruby-io */
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
MRuby::Gem::Specification.new('hal-posix-socket') do |spec|
|
||||
spec.license = 'MIT'
|
||||
spec.author = 'mruby developers'
|
||||
spec.summary = 'POSIX HAL for mruby-socket (Linux, macOS, BSD, Unix)'
|
||||
|
||||
# HAL gem depends on feature gem - brings in mruby-socket automatically
|
||||
spec.add_dependency 'mruby-socket', core: 'mruby-socket'
|
||||
end
|
||||
@@ -1,158 +0,0 @@
|
||||
/*
|
||||
** socket_hal.c - POSIX HAL implementation for mruby-socket
|
||||
**
|
||||
** See Copyright Notice in mruby.h
|
||||
**
|
||||
** POSIX implementation for socket operations using standard POSIX APIs.
|
||||
** Supported platforms: Linux, macOS, BSD, Unix
|
||||
*/
|
||||
|
||||
#include <mruby.h>
|
||||
#include <mruby/string.h>
|
||||
#include <mruby/class.h>
|
||||
#include <mruby/error.h>
|
||||
#include <mruby/presym.h>
|
||||
#include "socket_hal.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
/*
|
||||
* Socket HAL Initialization/Finalization
|
||||
*/
|
||||
|
||||
void
|
||||
mrb_hal_socket_init(mrb_state *mrb)
|
||||
{
|
||||
(void)mrb;
|
||||
/* No initialization needed for POSIX sockets */
|
||||
}
|
||||
|
||||
void
|
||||
mrb_hal_socket_final(mrb_state *mrb)
|
||||
{
|
||||
(void)mrb;
|
||||
/* No cleanup needed for POSIX sockets */
|
||||
}
|
||||
|
||||
/*
|
||||
* Socket Control Operations
|
||||
*/
|
||||
|
||||
int
|
||||
mrb_hal_socket_set_nonblock(mrb_state *mrb, int fd, int nonblock)
|
||||
{
|
||||
(void)mrb;
|
||||
|
||||
int flags = fcntl(fd, F_GETFL, 0);
|
||||
if (flags == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (nonblock) {
|
||||
flags |= O_NONBLOCK;
|
||||
}
|
||||
else {
|
||||
flags &= ~O_NONBLOCK;
|
||||
}
|
||||
|
||||
if (fcntl(fd, F_SETFL, flags) == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Address Conversion Functions
|
||||
*/
|
||||
|
||||
const char*
|
||||
mrb_hal_socket_inet_ntop(int af, const void *src, char *dst, size_t size)
|
||||
{
|
||||
return inet_ntop(af, src, dst, (socklen_t)size);
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_socket_inet_pton(int af, const char *src, void *dst)
|
||||
{
|
||||
return inet_pton(af, src, dst);
|
||||
}
|
||||
|
||||
/*
|
||||
* Platform-Specific Socket Features
|
||||
*/
|
||||
|
||||
mrb_value
|
||||
mrb_hal_socket_sockaddr_un(mrb_state *mrb, const char *path, size_t pathlen)
|
||||
{
|
||||
struct sockaddr_un *sunp;
|
||||
|
||||
if (pathlen > sizeof(sunp->sun_path) - 1) {
|
||||
mrb_raisef(mrb, mrb_class_get_id(mrb, MRB_SYM(ArgumentError)),
|
||||
"too long unix socket path (max: %d bytes)",
|
||||
(int)sizeof(sunp->sun_path) - 1);
|
||||
}
|
||||
|
||||
mrb_value s = mrb_str_new_capa(mrb, sizeof(struct sockaddr_un));
|
||||
sunp = (struct sockaddr_un*)RSTRING_PTR(s);
|
||||
|
||||
#if defined(HAVE_SA_LEN) && HAVE_SA_LEN
|
||||
sunp->sun_len = sizeof(struct sockaddr_un);
|
||||
#endif
|
||||
|
||||
sunp->sun_family = AF_UNIX;
|
||||
memcpy(sunp->sun_path, path, pathlen);
|
||||
sunp->sun_path[pathlen] = '\0';
|
||||
mrb_str_resize(mrb, s, sizeof(struct sockaddr_un));
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_socket_socketpair(mrb_state *mrb, int domain, int type, int protocol, int sv[2])
|
||||
{
|
||||
(void)mrb;
|
||||
return socketpair(domain, type, protocol, sv);
|
||||
}
|
||||
|
||||
mrb_value
|
||||
mrb_hal_socket_unix_path(mrb_state *mrb, const char *sockaddr, size_t socklen)
|
||||
{
|
||||
const struct sockaddr *sa = (const struct sockaddr*)sockaddr;
|
||||
|
||||
if (sa->sa_family != AF_UNIX) {
|
||||
mrb_raise(mrb, mrb_class_get_id(mrb, MRB_SYM(SocketError)), "need AF_UNIX address");
|
||||
}
|
||||
|
||||
if (socklen < offsetof(struct sockaddr_un, sun_path) + 1) {
|
||||
return mrb_str_new(mrb, "", 0);
|
||||
}
|
||||
|
||||
return mrb_str_new_cstr(mrb, ((const struct sockaddr_un*)sockaddr)->sun_path);
|
||||
}
|
||||
|
||||
/*
|
||||
* Gem initialization
|
||||
*/
|
||||
|
||||
void
|
||||
mrb_hal_posix_socket_gem_init(mrb_state *mrb)
|
||||
{
|
||||
(void)mrb;
|
||||
/* HAL interface functions are called by mruby-socket gem */
|
||||
}
|
||||
|
||||
void
|
||||
mrb_hal_posix_socket_gem_final(mrb_state *mrb)
|
||||
{
|
||||
(void)mrb;
|
||||
/* Cleanup handled by mrb_hal_socket_final called from mruby-socket */
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
# hal-posix-task
|
||||
|
||||
POSIX Hardware Abstraction Layer (HAL) implementation for mruby-task.
|
||||
|
||||
## Description
|
||||
|
||||
Provides timer and interrupt support for the mruby-task cooperative scheduler on POSIX-compliant platforms. Uses `SIGALRM` and `setitimer()` for periodic timer ticks, and `sigprocmask()` for interrupt protection.
|
||||
|
||||
## Supported Platforms
|
||||
|
||||
- Linux
|
||||
- macOS
|
||||
- BSD (FreeBSD, OpenBSD, NetBSD)
|
||||
- Other POSIX-compliant Unix systems
|
||||
|
||||
## Requirements
|
||||
|
||||
- POSIX-compliant operating system
|
||||
- Signal support (`SIGALRM`, `sigaction`, `sigprocmask`)
|
||||
- Timer support (`setitimer`, `ITIMER_REAL`)
|
||||
|
||||
## Usage
|
||||
|
||||
### Explicit HAL Selection (Recommended)
|
||||
|
||||
```ruby
|
||||
MRuby::Build.new do |conf|
|
||||
# ... other configuration ...
|
||||
|
||||
# Specify POSIX HAL - automatically brings in mruby-task
|
||||
conf.gem core: 'hal-posix-task'
|
||||
end
|
||||
```
|
||||
|
||||
### Auto-detection (Development)
|
||||
|
||||
```ruby
|
||||
MRuby::Build.new do |conf|
|
||||
# ... other configuration ...
|
||||
|
||||
# Auto-detects and selects hal-posix-task on POSIX platforms
|
||||
conf.gem core: 'mruby-task'
|
||||
end
|
||||
```
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Timer Mechanism
|
||||
|
||||
- Uses `setitimer(ITIMER_REAL, ...)` to generate periodic `SIGALRM` signals
|
||||
- Timer interval configured by `MRB_TICK_UNIT` (default: 4ms)
|
||||
- Signal handler calls `mrb_tick()` for all registered VM instances
|
||||
|
||||
### Interrupt Protection
|
||||
|
||||
- Critical sections protected using `sigprocmask()` to block `SIGALRM`
|
||||
- Prevents race conditions during task queue modifications
|
||||
- Supports nested critical sections through signal masking
|
||||
|
||||
### Multi-VM Support
|
||||
|
||||
- Supports up to `MRB_TASK_MAX_VMS` concurrent mruby VM instances (default: 8)
|
||||
- Single shared timer ticks all registered VMs
|
||||
- Per-VM task counters optimize timer usage (timer disabled when idle)
|
||||
|
||||
### Timer Optimization
|
||||
|
||||
The implementation dynamically enables/disables the timer based on task state:
|
||||
|
||||
- **Timer enabled** when: Multiple ready tasks OR any waiting tasks exist
|
||||
- **Timer disabled** when: Single task or all tasks dormant/suspended
|
||||
- Reduces CPU usage and power consumption when scheduler is idle
|
||||
|
||||
## Configuration
|
||||
|
||||
Override these macros in your build config if needed:
|
||||
|
||||
```ruby
|
||||
conf.gem core: 'hal-posix-task' do |spec|
|
||||
# Custom tick interval (10ms instead of default 4ms)
|
||||
spec.build.defines << 'MRB_TICK_UNIT=10'
|
||||
|
||||
# Custom timeslice (5 ticks instead of default 3)
|
||||
spec.build.defines << 'MRB_TIMESLICE_TICK_COUNT=5'
|
||||
|
||||
# More concurrent VMs (16 instead of default 8)
|
||||
spec.build.defines << 'MRB_TASK_MAX_VMS=16'
|
||||
end
|
||||
```
|
||||
|
||||
## Known Limitations
|
||||
|
||||
- `SIGALRM` conflicts with other code using the same signal
|
||||
- Timer resolution limited by platform (typically 1-10ms)
|
||||
- Signal delivery may be delayed under heavy system load
|
||||
- Not suitable for hard real-time requirements
|
||||
|
||||
## See Also
|
||||
|
||||
- `mruby-task` - Core task scheduler
|
||||
- `hal-win-task` - Windows HAL implementation
|
||||
- Task scheduler documentation: `mrbgems/mruby-task/README.md`
|
||||
@@ -1,8 +0,0 @@
|
||||
MRuby::Gem::Specification.new('hal-posix-task') do |spec|
|
||||
spec.license = 'MIT'
|
||||
spec.authors = 'mruby developers'
|
||||
spec.summary = 'POSIX HAL for mruby-task (Linux, macOS, BSD, Unix)'
|
||||
|
||||
# HAL gem depends on feature gem - brings in mruby-task automatically
|
||||
spec.add_dependency 'mruby-task', core: 'mruby-task'
|
||||
end
|
||||
@@ -1,252 +0,0 @@
|
||||
/*
|
||||
** task_hal.c - POSIX HAL implementation for mruby-task
|
||||
**
|
||||
** See Copyright Notice in mruby.h
|
||||
**
|
||||
** POSIX implementation using SIGALRM and setitimer() for timer,
|
||||
** and sigprocmask() for interrupt protection.
|
||||
**
|
||||
** Supported platforms: Linux, macOS, BSD, Unix
|
||||
**
|
||||
** Note: When compiled for Emscripten/WASM, the SIGALRM timer is disabled
|
||||
** because JavaScript handles tick calls via setInterval. Using both would
|
||||
** cause tick_ to increment twice as fast, making sleep wake up early.
|
||||
*/
|
||||
|
||||
#include <mruby.h>
|
||||
#include "task_hal.h"
|
||||
#ifndef __EMSCRIPTEN__
|
||||
#include <signal.h>
|
||||
#include <sys/time.h>
|
||||
#endif
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* Time conversion constants */
|
||||
#define NSEC_PER_MSEC 1000000ULL
|
||||
#define NSEC_PER_SEC 1000000000ULL
|
||||
#define USEC_PER_MSEC 1000ULL
|
||||
|
||||
#ifndef __EMSCRIPTEN__
|
||||
/* Multi-VM support */
|
||||
static mrb_state *vm_list[MRB_TASK_MAX_VMS];
|
||||
static volatile sig_atomic_t vm_count = 0;
|
||||
static sigset_t alarm_mask;
|
||||
|
||||
/* SIGALRM signal handler - ticks all registered VMs */
|
||||
static void
|
||||
sigalrm_handler(int sig)
|
||||
{
|
||||
int i;
|
||||
(void)sig;
|
||||
/* Tick all registered VMs */
|
||||
for (i = 0; i < vm_count; i++) {
|
||||
if (vm_list[i]) {
|
||||
mrb_tick(vm_list[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif /* __EMSCRIPTEN__ */
|
||||
|
||||
/*
|
||||
* HAL Interface Implementation
|
||||
*/
|
||||
|
||||
void
|
||||
mrb_hal_task_init(mrb_state *mrb)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* Initialize task state */
|
||||
for (i = 0; i < 4; i++) {
|
||||
mrb->task.queues[i] = NULL;
|
||||
}
|
||||
mrb->task.tick = 0;
|
||||
mrb->task.wakeup_tick = UINT32_MAX;
|
||||
mrb->task.switching = FALSE;
|
||||
|
||||
#ifndef __EMSCRIPTEN__
|
||||
/* POSIX: Set up SIGALRM timer for tick handling */
|
||||
struct sigaction sa;
|
||||
struct itimerval timer;
|
||||
int vm_index = -1;
|
||||
|
||||
/* Block SIGALRM during registration to avoid race */
|
||||
sigemptyset(&alarm_mask);
|
||||
sigaddset(&alarm_mask, SIGALRM);
|
||||
sigprocmask(SIG_BLOCK, &alarm_mask, NULL);
|
||||
|
||||
/* Check if this VM is already registered */
|
||||
for (i = 0; i < vm_count; i++) {
|
||||
if (vm_list[i] == mrb) {
|
||||
vm_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Register new VM if not already present */
|
||||
if (vm_index < 0) {
|
||||
if (vm_count >= MRB_TASK_MAX_VMS) {
|
||||
sigprocmask(SIG_UNBLOCK, &alarm_mask, NULL);
|
||||
mrb_raisef(mrb, E_RUNTIME_ERROR,
|
||||
"too many mrb_states with task scheduler (max: %d)",
|
||||
MRB_TASK_MAX_VMS);
|
||||
}
|
||||
vm_list[vm_count] = mrb;
|
||||
vm_count++;
|
||||
}
|
||||
|
||||
/* Set up signal handler and timer only for first VM */
|
||||
if (vm_count == 1) {
|
||||
/* Set up signal handler - SA_RESTART to avoid breaking IO operations */
|
||||
sa.sa_handler = sigalrm_handler;
|
||||
sa.sa_flags = SA_RESTART;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sigaction(SIGALRM, &sa, NULL);
|
||||
|
||||
/* Start timer */
|
||||
timer.it_value.tv_sec = 0;
|
||||
timer.it_value.tv_usec = MRB_TICK_UNIT * 1000;
|
||||
timer.it_interval.tv_sec = 0;
|
||||
timer.it_interval.tv_usec = MRB_TICK_UNIT * 1000;
|
||||
setitimer(ITIMER_REAL, &timer, NULL);
|
||||
}
|
||||
|
||||
/* Unblock SIGALRM */
|
||||
sigprocmask(SIG_UNBLOCK, &alarm_mask, NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
mrb_task_enable_irq(void)
|
||||
{
|
||||
#ifndef __EMSCRIPTEN__
|
||||
sigprocmask(SIG_UNBLOCK, &alarm_mask, NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
mrb_task_disable_irq(void)
|
||||
{
|
||||
#ifndef __EMSCRIPTEN__
|
||||
sigprocmask(SIG_BLOCK, &alarm_mask, NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
mrb_hal_task_idle_cpu(mrb_state *mrb)
|
||||
{
|
||||
(void)mrb;
|
||||
/* On POSIX, just pause briefly */
|
||||
usleep(MRB_TICK_UNIT * 1000);
|
||||
}
|
||||
|
||||
void
|
||||
mrb_hal_task_sleep_us(mrb_state *mrb, mrb_int usec)
|
||||
{
|
||||
struct timespec start, now, sleep_time;
|
||||
int ret;
|
||||
|
||||
(void)mrb;
|
||||
|
||||
/* Validate input to prevent overflow */
|
||||
if (usec < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
ret = clock_gettime(CLOCK_MONOTONIC, &start);
|
||||
if (ret != 0) {
|
||||
/* Fallback to simple usleep if clock_gettime fails */
|
||||
usleep(usec);
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t target_ns = (uint64_t)usec * USEC_PER_MSEC;
|
||||
|
||||
/* Loop until enough real time has elapsed */
|
||||
while (1) {
|
||||
ret = clock_gettime(CLOCK_MONOTONIC, &now);
|
||||
if (ret != 0) {
|
||||
break; /* Clock failure - exit loop */
|
||||
}
|
||||
|
||||
uint64_t elapsed_ns = (uint64_t)(now.tv_sec - start.tv_sec) * NSEC_PER_SEC +
|
||||
(uint64_t)(now.tv_nsec - start.tv_nsec);
|
||||
|
||||
if (elapsed_ns >= target_ns) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Sleep for remaining time, but at least 1ms to allow timer interrupts */
|
||||
uint64_t remaining_ns = target_ns - elapsed_ns;
|
||||
if (remaining_ns > NSEC_PER_MSEC) {
|
||||
sleep_time.tv_sec = remaining_ns / NSEC_PER_SEC;
|
||||
sleep_time.tv_nsec = remaining_ns % NSEC_PER_SEC;
|
||||
}
|
||||
else {
|
||||
sleep_time.tv_sec = 0;
|
||||
sleep_time.tv_nsec = NSEC_PER_MSEC;
|
||||
}
|
||||
|
||||
nanosleep(&sleep_time, NULL); /* Interrupted by signals - that's OK */
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
mrb_hal_task_final(mrb_state *mrb)
|
||||
{
|
||||
#ifndef __EMSCRIPTEN__
|
||||
struct itimerval timer;
|
||||
int i, j;
|
||||
|
||||
/* Block SIGALRM during unregistration */
|
||||
sigprocmask(SIG_BLOCK, &alarm_mask, NULL);
|
||||
|
||||
/* Find and remove this VM from the list */
|
||||
for (i = 0; i < vm_count; i++) {
|
||||
if (vm_list[i] == mrb) {
|
||||
/* Shift remaining VMs down */
|
||||
for (j = i; j < vm_count - 1; j++) {
|
||||
vm_list[j] = vm_list[j + 1];
|
||||
}
|
||||
vm_list[vm_count - 1] = NULL;
|
||||
vm_count--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Stop timer if last VM */
|
||||
if (vm_count == 0) {
|
||||
timer.it_value.tv_sec = 0;
|
||||
timer.it_value.tv_usec = 0;
|
||||
timer.it_interval.tv_sec = 0;
|
||||
timer.it_interval.tv_usec = 0;
|
||||
setitimer(ITIMER_REAL, &timer, NULL);
|
||||
}
|
||||
|
||||
/* Unblock SIGALRM */
|
||||
sigprocmask(SIG_UNBLOCK, &alarm_mask, NULL);
|
||||
#else
|
||||
/* WASM: No timer cleanup needed */
|
||||
(void)mrb;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* Gem initialization (empty - HAL functions called by mruby-task)
|
||||
*/
|
||||
|
||||
void
|
||||
mrb_hal_posix_task_gem_init(mrb_state *mrb)
|
||||
{
|
||||
(void)mrb;
|
||||
/* HAL interface functions are called by mruby-task gem */
|
||||
}
|
||||
|
||||
void
|
||||
mrb_hal_posix_task_gem_final(mrb_state *mrb)
|
||||
{
|
||||
(void)mrb;
|
||||
/* Cleanup handled by mrb_hal_task_final called from mruby-task */
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
MRuby::Gem::Specification.new('hal-win-dir') do |spec|
|
||||
spec.license = 'MIT and MIT-like license'
|
||||
spec.authors = ['mruby developers', 'Kevlin Henney']
|
||||
spec.summary = 'Windows HAL for mruby-dir'
|
||||
|
||||
spec.add_dependency 'mruby-dir', core: 'mruby-dir'
|
||||
end
|
||||
@@ -1,249 +0,0 @@
|
||||
/*
|
||||
** dir_hal.c - Windows HAL implementation for mruby-dir
|
||||
**
|
||||
** See Copyright Notice in mruby.h
|
||||
**
|
||||
** Windows implementation for directory operations using _findfirst/_findnext APIs.
|
||||
** Provides POSIX-compatible interface on Windows.
|
||||
**
|
||||
** Based on dirent.c by Kevlin Henney (kevlin@acm.org, kevlin@curbralan.com)
|
||||
** Original implementation: Created March 1997. Updated June 2003 and July 2012.
|
||||
** See end of file for Kevlin Henney's copyright notice.
|
||||
*/
|
||||
|
||||
#include <mruby.h>
|
||||
#include "dir_hal.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <direct.h>
|
||||
#include <io.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Windows directory handle implementation */
|
||||
struct mrb_dir_handle {
|
||||
intptr_t handle; /* _findfirst/_findnext handle */
|
||||
struct _finddata_t info; /* Current entry info */
|
||||
char *pattern; /* Search pattern with wildcard */
|
||||
int first; /* Flag: haven't read first entry yet */
|
||||
};
|
||||
|
||||
/*
|
||||
* Directory Operations
|
||||
*/
|
||||
|
||||
mrb_dir_handle*
|
||||
mrb_hal_dir_open(mrb_state *mrb, const char *path)
|
||||
{
|
||||
mrb_dir_handle *handle;
|
||||
size_t len = strlen(path);
|
||||
const char *suffix;
|
||||
|
||||
/* Add wildcard suffix if needed */
|
||||
suffix = (len > 0 && (path[len-1] == '/' || path[len-1] == '\\')) ? "*" : "/*";
|
||||
|
||||
handle = (mrb_dir_handle*)mrb_malloc(mrb, sizeof(mrb_dir_handle));
|
||||
handle->pattern = (char*)mrb_malloc(mrb, len + strlen(suffix) + 1);
|
||||
strcpy(handle->pattern, path);
|
||||
strcat(handle->pattern, suffix);
|
||||
|
||||
handle->handle = _findfirst(handle->pattern, &handle->info);
|
||||
if (handle->handle == -1) {
|
||||
mrb_free(mrb, handle->pattern);
|
||||
mrb_free(mrb, handle);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
handle->first = 1;
|
||||
return handle;
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_dir_close(mrb_state *mrb, mrb_dir_handle *handle)
|
||||
{
|
||||
int result = -1;
|
||||
|
||||
if (handle->handle != -1) {
|
||||
result = _findclose(handle->handle);
|
||||
}
|
||||
|
||||
mrb_free(mrb, handle->pattern);
|
||||
mrb_free(mrb, handle);
|
||||
|
||||
if (result == -1) {
|
||||
/* Map all errors to EBADF */
|
||||
errno = EBADF;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const char*
|
||||
mrb_hal_dir_read(mrb_state *mrb, mrb_dir_handle *handle)
|
||||
{
|
||||
(void)mrb;
|
||||
|
||||
if (handle->handle == -1) {
|
||||
errno = EBADF;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* First call returns the result from _findfirst */
|
||||
if (handle->first) {
|
||||
handle->first = 0;
|
||||
return handle->info.name;
|
||||
}
|
||||
|
||||
/* Subsequent calls use _findnext */
|
||||
if (_findnext(handle->handle, &handle->info) == -1) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return handle->info.name;
|
||||
}
|
||||
|
||||
void
|
||||
mrb_hal_dir_rewind(mrb_state *mrb, mrb_dir_handle *handle)
|
||||
{
|
||||
(void)mrb;
|
||||
|
||||
if (handle->handle == -1) {
|
||||
errno = EBADF;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Close and reopen to rewind */
|
||||
_findclose(handle->handle);
|
||||
handle->handle = _findfirst(handle->pattern, &handle->info);
|
||||
handle->first = 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Optional Operations
|
||||
*/
|
||||
|
||||
int
|
||||
mrb_hal_dir_seek(mrb_state *mrb, mrb_dir_handle *handle, long pos)
|
||||
{
|
||||
/* Not supported on Windows */
|
||||
(void)mrb; (void)handle; (void)pos;
|
||||
errno = ENOSYS;
|
||||
return -1;
|
||||
}
|
||||
|
||||
long
|
||||
mrb_hal_dir_tell(mrb_state *mrb, mrb_dir_handle *handle)
|
||||
{
|
||||
/* Not supported on Windows */
|
||||
(void)mrb; (void)handle;
|
||||
errno = ENOSYS;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Filesystem Operations
|
||||
*/
|
||||
|
||||
int
|
||||
mrb_hal_dir_mkdir(mrb_state *mrb, const char *path, int mode)
|
||||
{
|
||||
/* Windows _mkdir ignores mode parameter */
|
||||
(void)mrb; (void)mode;
|
||||
return _mkdir(path);
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_dir_rmdir(mrb_state *mrb, const char *path)
|
||||
{
|
||||
(void)mrb;
|
||||
return _rmdir(path);
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_dir_chdir(mrb_state *mrb, const char *path)
|
||||
{
|
||||
(void)mrb;
|
||||
return _chdir(path);
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_dir_getcwd(mrb_state *mrb, char *buf, size_t size)
|
||||
{
|
||||
(void)mrb;
|
||||
return _getcwd(buf, (int)size) ? 0 : -1;
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_dir_chroot(mrb_state *mrb, const char *path)
|
||||
{
|
||||
/* Not available on Windows */
|
||||
(void)mrb; (void)path;
|
||||
errno = ENOSYS;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_dir_is_directory(mrb_state *mrb, const char *path)
|
||||
{
|
||||
struct _stat sb;
|
||||
(void)mrb;
|
||||
|
||||
if (_stat(path, &sb) == 0 && (sb.st_mode & _S_IFDIR)) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* HAL Initialization/Finalization
|
||||
*/
|
||||
|
||||
void
|
||||
mrb_hal_dir_init(mrb_state *mrb)
|
||||
{
|
||||
(void)mrb;
|
||||
/* No initialization needed for Windows */
|
||||
}
|
||||
|
||||
void
|
||||
mrb_hal_dir_final(mrb_state *mrb)
|
||||
{
|
||||
(void)mrb;
|
||||
/* No cleanup needed for Windows */
|
||||
}
|
||||
|
||||
/*
|
||||
* Gem initialization
|
||||
*/
|
||||
|
||||
void
|
||||
mrb_hal_win_dir_gem_init(mrb_state *mrb)
|
||||
{
|
||||
(void)mrb;
|
||||
/* HAL interface functions are called by mruby-dir gem */
|
||||
}
|
||||
|
||||
void
|
||||
mrb_hal_win_dir_gem_final(mrb_state *mrb)
|
||||
{
|
||||
(void)mrb;
|
||||
/* Cleanup handled by mrb_hal_dir_final called from mruby-dir */
|
||||
}
|
||||
|
||||
/*
|
||||
** Portions derived from dirent.c by Kevlin Henney:
|
||||
**
|
||||
** Copyright Kevlin Henney, 1997, 2003, 2012. All rights reserved.
|
||||
**
|
||||
** Permission to use, copy, modify, and distribute this software and its
|
||||
** documentation for any purpose is hereby granted without fee, provided
|
||||
** that this copyright and permissions notice appear in all copies and
|
||||
** derivatives.
|
||||
**
|
||||
** This software is supplied "as is" without express or implied warranty.
|
||||
**
|
||||
** But that said, if there are any problems please get in touch.
|
||||
*/
|
||||
@@ -1,11 +0,0 @@
|
||||
MRuby::Gem::Specification.new('hal-win-io') do |spec|
|
||||
spec.license = 'MIT'
|
||||
spec.author = 'mruby developers'
|
||||
spec.summary = 'Windows HAL for mruby-io (Windows, MinGW)'
|
||||
|
||||
# HAL gem depends on feature gem - brings in mruby-io automatically
|
||||
spec.add_dependency 'mruby-io', core: 'mruby-io'
|
||||
|
||||
# Link Windows socket library
|
||||
spec.linker.libraries << "ws2_32"
|
||||
end
|
||||
@@ -1,646 +0,0 @@
|
||||
/*
|
||||
** io_hal.c - Windows HAL implementation for mruby-io
|
||||
**
|
||||
** See Copyright Notice in mruby.h
|
||||
**
|
||||
** Windows implementation for I/O operations using Win32 APIs.
|
||||
** Supported platforms: Windows, MinGW
|
||||
*/
|
||||
|
||||
#include <mruby.h>
|
||||
#include "io_hal.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <io.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <direct.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* Maximum path length */
|
||||
#ifndef PATH_MAX
|
||||
#define PATH_MAX 4096
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Helper Functions
|
||||
*/
|
||||
|
||||
/* Convert Windows struct _stat64 to mrb_io_stat */
|
||||
static void
|
||||
convert_stat(const struct _stat64 *src, mrb_io_stat *dst)
|
||||
{
|
||||
dst->st_dev = (uint64_t)src->st_dev;
|
||||
dst->st_ino = (uint64_t)src->st_ino;
|
||||
dst->st_mode = (uint32_t)src->st_mode;
|
||||
dst->st_nlink = (uint32_t)src->st_nlink;
|
||||
dst->st_uid = 0; /* Windows doesn't have Unix-style UIDs */
|
||||
dst->st_gid = 0; /* Windows doesn't have Unix-style GIDs */
|
||||
dst->st_rdev = (uint64_t)src->st_rdev;
|
||||
dst->st_size = (int64_t)src->st_size;
|
||||
dst->st_atime = (int64_t)src->st_atime;
|
||||
dst->st_mtime = (int64_t)src->st_mtime;
|
||||
dst->st_ctime = (int64_t)src->st_ctime;
|
||||
dst->st_blksize = 512;
|
||||
dst->st_blocks = (dst->st_size + 511) / 512;
|
||||
}
|
||||
|
||||
/* Convert errno to Windows errno */
|
||||
static void
|
||||
set_errno_from_win_error(DWORD error)
|
||||
{
|
||||
switch (error) {
|
||||
case ERROR_FILE_NOT_FOUND:
|
||||
case ERROR_PATH_NOT_FOUND:
|
||||
errno = ENOENT;
|
||||
break;
|
||||
case ERROR_ACCESS_DENIED:
|
||||
errno = EACCES;
|
||||
break;
|
||||
case ERROR_NOT_ENOUGH_MEMORY:
|
||||
errno = ENOMEM;
|
||||
break;
|
||||
case ERROR_INVALID_HANDLE:
|
||||
errno = EBADF;
|
||||
break;
|
||||
case ERROR_ALREADY_EXISTS:
|
||||
errno = EEXIST;
|
||||
break;
|
||||
default:
|
||||
errno = EIO;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* File Operations
|
||||
*/
|
||||
|
||||
int
|
||||
mrb_hal_io_stat(mrb_state *mrb, const char *path, mrb_io_stat *st)
|
||||
{
|
||||
struct _stat64 s;
|
||||
(void)mrb;
|
||||
|
||||
if (_stat64(path, &s) == -1) {
|
||||
return -1;
|
||||
}
|
||||
convert_stat(&s, st);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_io_fstat(mrb_state *mrb, int fd, mrb_io_stat *st)
|
||||
{
|
||||
struct _stat64 s;
|
||||
(void)mrb;
|
||||
|
||||
if (_fstat64(fd, &s) == -1) {
|
||||
return -1;
|
||||
}
|
||||
convert_stat(&s, st);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_io_lstat(mrb_state *mrb, const char *path, mrb_io_stat *st)
|
||||
{
|
||||
/* Windows doesn't distinguish lstat from stat */
|
||||
return mrb_hal_io_stat(mrb, path, st);
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_io_chmod(mrb_state *mrb, const char *path, uint32_t mode)
|
||||
{
|
||||
(void)mrb;
|
||||
return _chmod(path, (int)mode);
|
||||
}
|
||||
|
||||
uint32_t
|
||||
mrb_hal_io_umask(mrb_state *mrb, int32_t mask)
|
||||
{
|
||||
int old;
|
||||
(void)mrb;
|
||||
|
||||
if (mask < 0) {
|
||||
/* Just query current value */
|
||||
old = _umask(0);
|
||||
_umask(old);
|
||||
}
|
||||
else {
|
||||
old = _umask((int)mask);
|
||||
}
|
||||
return (uint32_t)old;
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_io_ftruncate(mrb_state *mrb, int fd, int64_t length)
|
||||
{
|
||||
(void)mrb;
|
||||
return _chsize_s(fd, length);
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_io_flock(mrb_state *mrb, int fd, int operation)
|
||||
{
|
||||
HANDLE h;
|
||||
OVERLAPPED overlapped;
|
||||
DWORD flags = 0;
|
||||
(void)mrb;
|
||||
|
||||
h = (HANDLE)_get_osfhandle(fd);
|
||||
if (h == INVALID_HANDLE_VALUE) {
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(&overlapped, 0, sizeof(overlapped));
|
||||
|
||||
if (operation & MRB_IO_LOCK_UN) {
|
||||
if (!UnlockFileEx(h, 0, MAXDWORD, MAXDWORD, &overlapped)) {
|
||||
set_errno_from_win_error(GetLastError());
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (operation & MRB_IO_LOCK_EX) {
|
||||
flags |= LOCKFILE_EXCLUSIVE_LOCK;
|
||||
}
|
||||
if (operation & MRB_IO_LOCK_NB) {
|
||||
flags |= LOCKFILE_FAIL_IMMEDIATELY;
|
||||
}
|
||||
|
||||
if (!LockFileEx(h, flags, 0, MAXDWORD, MAXDWORD, &overlapped)) {
|
||||
set_errno_from_win_error(GetLastError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_io_unlink(mrb_state *mrb, const char *path)
|
||||
{
|
||||
(void)mrb;
|
||||
return _unlink(path);
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_io_rename(mrb_state *mrb, const char *oldpath, const char *newpath)
|
||||
{
|
||||
(void)mrb;
|
||||
return rename(oldpath, newpath);
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_io_symlink(mrb_state *mrb, const char *target, const char *linkpath)
|
||||
{
|
||||
(void)target;
|
||||
(void)linkpath;
|
||||
/* Symlinks require special privileges on Windows */
|
||||
mrb_raise(mrb, E_NOTIMP_ERROR, "symlink is not supported on Windows");
|
||||
return -1; /* not reached */
|
||||
}
|
||||
|
||||
int64_t
|
||||
mrb_hal_io_readlink(mrb_state *mrb, const char *path, char *buf, size_t bufsize)
|
||||
{
|
||||
(void)path;
|
||||
(void)buf;
|
||||
(void)bufsize;
|
||||
/* Symlinks require special handling on Windows */
|
||||
mrb_raise(mrb, E_NOTIMP_ERROR, "readlink is not supported on Windows");
|
||||
return -1; /* not reached */
|
||||
}
|
||||
|
||||
char*
|
||||
mrb_hal_io_realpath(mrb_state *mrb, const char *path, char *resolved)
|
||||
{
|
||||
DWORD ret;
|
||||
(void)mrb;
|
||||
|
||||
ret = GetFullPathName(path, PATH_MAX, resolved, NULL);
|
||||
if (ret == 0 || ret >= PATH_MAX) {
|
||||
set_errno_from_win_error(GetLastError());
|
||||
return NULL;
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
|
||||
char*
|
||||
mrb_hal_io_getcwd(mrb_state *mrb, char *buf, size_t size)
|
||||
{
|
||||
(void)mrb;
|
||||
return _getcwd(buf, (int)size);
|
||||
}
|
||||
|
||||
const char*
|
||||
mrb_hal_io_getenv(mrb_state *mrb, const char *name)
|
||||
{
|
||||
(void)mrb;
|
||||
return getenv(name);
|
||||
}
|
||||
|
||||
const char*
|
||||
mrb_hal_io_gethome(mrb_state *mrb, const char *username)
|
||||
{
|
||||
const char *home;
|
||||
(void)mrb;
|
||||
|
||||
if (username != NULL && *username != '\0') {
|
||||
/* Windows doesn't have a simple way to get other users' home directories */
|
||||
errno = ENOSYS;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Try USERPROFILE first, then HOMEDRIVE+HOMEPATH */
|
||||
home = getenv("USERPROFILE");
|
||||
if (home == NULL) {
|
||||
const char *homedrive = getenv("HOMEDRIVE");
|
||||
const char *homepath = getenv("HOMEPATH");
|
||||
if (homedrive && homepath) {
|
||||
static char homebuf[PATH_MAX];
|
||||
snprintf(homebuf, PATH_MAX, "%s%s", homedrive, homepath);
|
||||
return homebuf;
|
||||
}
|
||||
errno = ENOENT;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return home;
|
||||
}
|
||||
|
||||
/*
|
||||
* Core I/O Operations
|
||||
*/
|
||||
|
||||
int
|
||||
mrb_hal_io_open(mrb_state *mrb, const char *path, int flags, uint32_t mode)
|
||||
{
|
||||
int fd;
|
||||
(void)mrb;
|
||||
|
||||
/* Windows uses _open with slightly different flags */
|
||||
fd = _open(path, flags | _O_BINARY, (int)mode);
|
||||
if (fd == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Set close-on-exec for non-standard descriptors */
|
||||
if (fd > 2) {
|
||||
SetHandleInformation((HANDLE)_get_osfhandle(fd), HANDLE_FLAG_INHERIT, 0);
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_io_close(mrb_state *mrb, int fd)
|
||||
{
|
||||
(void)mrb;
|
||||
return _close(fd);
|
||||
}
|
||||
|
||||
int64_t
|
||||
mrb_hal_io_read(mrb_state *mrb, int fd, void *buf, size_t count)
|
||||
{
|
||||
int n;
|
||||
(void)mrb;
|
||||
|
||||
n = _read(fd, buf, (unsigned int)count);
|
||||
return (int64_t)n;
|
||||
}
|
||||
|
||||
int64_t
|
||||
mrb_hal_io_write(mrb_state *mrb, int fd, const void *buf, size_t count)
|
||||
{
|
||||
int n;
|
||||
(void)mrb;
|
||||
|
||||
n = _write(fd, buf, (unsigned int)count);
|
||||
return (int64_t)n;
|
||||
}
|
||||
|
||||
int64_t
|
||||
mrb_hal_io_lseek(mrb_state *mrb, int fd, int64_t offset, int whence)
|
||||
{
|
||||
__int64 pos;
|
||||
int win_whence;
|
||||
(void)mrb;
|
||||
|
||||
/* Convert MRB_IO_SEEK_* to Windows SEEK_* */
|
||||
switch (whence) {
|
||||
case MRB_IO_SEEK_SET: win_whence = SEEK_SET; break;
|
||||
case MRB_IO_SEEK_CUR: win_whence = SEEK_CUR; break;
|
||||
case MRB_IO_SEEK_END: win_whence = SEEK_END; break;
|
||||
default:
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
pos = _lseeki64(fd, (__int64)offset, win_whence);
|
||||
return (int64_t)pos;
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_io_dup(mrb_state *mrb, int fd)
|
||||
{
|
||||
int new_fd;
|
||||
(void)mrb;
|
||||
|
||||
new_fd = _dup(fd);
|
||||
if (new_fd == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Set close-on-exec */
|
||||
if (new_fd > 2) {
|
||||
SetHandleInformation((HANDLE)_get_osfhandle(new_fd), HANDLE_FLAG_INHERIT, 0);
|
||||
}
|
||||
|
||||
return new_fd;
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_io_fcntl(mrb_state *mrb, int fd, int cmd, int arg)
|
||||
{
|
||||
/* Windows has limited fcntl support */
|
||||
(void)mrb;
|
||||
(void)fd;
|
||||
(void)cmd;
|
||||
(void)arg;
|
||||
errno = ENOSYS;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_io_isatty(mrb_state *mrb, int fd)
|
||||
{
|
||||
(void)mrb;
|
||||
return _isatty(fd) ? 1 : 0;
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_io_pipe(mrb_state *mrb, int fds[2])
|
||||
{
|
||||
int ret;
|
||||
(void)mrb;
|
||||
|
||||
ret = _pipe(fds, 4096, _O_BINARY);
|
||||
if (ret == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Set close-on-exec on both ends */
|
||||
SetHandleInformation((HANDLE)_get_osfhandle(fds[0]), HANDLE_FLAG_INHERIT, 0);
|
||||
SetHandleInformation((HANDLE)_get_osfhandle(fds[1]), HANDLE_FLAG_INHERIT, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Process Operations
|
||||
*/
|
||||
|
||||
int
|
||||
mrb_hal_io_spawn_process(mrb_state *mrb, const char *cmd,
|
||||
int stdin_fd, int stdout_fd, int stderr_fd,
|
||||
int *pid)
|
||||
{
|
||||
STARTUPINFO si;
|
||||
PROCESS_INFORMATION pi;
|
||||
HANDLE h_stdin = INVALID_HANDLE_VALUE;
|
||||
HANDLE h_stdout = INVALID_HANDLE_VALUE;
|
||||
HANDLE h_stderr = INVALID_HANDLE_VALUE;
|
||||
char cmdline[8192];
|
||||
BOOL ret;
|
||||
(void)mrb;
|
||||
|
||||
/* Skip leading whitespace */
|
||||
while (*cmd == ' ' || *cmd == '\t' || *cmd == '\n') {
|
||||
cmd++;
|
||||
}
|
||||
|
||||
if (!*cmd) {
|
||||
errno = ENOENT;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Build command line - use cmd.exe to execute */
|
||||
snprintf(cmdline, sizeof(cmdline), "cmd.exe /c %s", cmd);
|
||||
|
||||
/* Setup startup info */
|
||||
memset(&si, 0, sizeof(si));
|
||||
si.cb = sizeof(si);
|
||||
si.dwFlags = STARTF_USESTDHANDLES;
|
||||
|
||||
/* Convert file descriptors to handles and make them inheritable */
|
||||
if (stdin_fd != -1) {
|
||||
h_stdin = (HANDLE)_get_osfhandle(stdin_fd);
|
||||
SetHandleInformation(h_stdin, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
|
||||
si.hStdInput = h_stdin;
|
||||
}
|
||||
else {
|
||||
si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
|
||||
}
|
||||
|
||||
if (stdout_fd != -1) {
|
||||
h_stdout = (HANDLE)_get_osfhandle(stdout_fd);
|
||||
SetHandleInformation(h_stdout, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
|
||||
si.hStdOutput = h_stdout;
|
||||
}
|
||||
else {
|
||||
si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
}
|
||||
|
||||
if (stderr_fd != -1) {
|
||||
h_stderr = (HANDLE)_get_osfhandle(stderr_fd);
|
||||
SetHandleInformation(h_stderr, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
|
||||
si.hStdError = h_stderr;
|
||||
}
|
||||
else {
|
||||
si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
|
||||
}
|
||||
|
||||
/* Create process */
|
||||
memset(&pi, 0, sizeof(pi));
|
||||
ret = CreateProcess(NULL, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
|
||||
|
||||
if (!ret) {
|
||||
set_errno_from_win_error(GetLastError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Close thread handle - we don't need it */
|
||||
CloseHandle(pi.hThread);
|
||||
|
||||
/* Store process handle as PID (will be used in waitpid) */
|
||||
*pid = (int)(intptr_t)pi.hProcess;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_io_waitpid(mrb_state *mrb, int pid, int *status, int options)
|
||||
{
|
||||
HANDLE h = (HANDLE)(intptr_t)pid;
|
||||
DWORD wait_result;
|
||||
DWORD exit_code;
|
||||
DWORD timeout;
|
||||
(void)mrb;
|
||||
|
||||
/* Convert options to timeout */
|
||||
timeout = (options != 0) ? 0 : INFINITE;
|
||||
|
||||
wait_result = WaitForSingleObject(h, timeout);
|
||||
|
||||
if (wait_result == WAIT_TIMEOUT) {
|
||||
return 0; /* Non-blocking wait, no change */
|
||||
}
|
||||
|
||||
if (wait_result != WAIT_OBJECT_0) {
|
||||
set_errno_from_win_error(GetLastError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Get exit code */
|
||||
if (!GetExitCodeProcess(h, &exit_code)) {
|
||||
set_errno_from_win_error(GetLastError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (status != NULL) {
|
||||
/* Store exit code in status (shifted to match Unix convention) */
|
||||
*status = (int)(exit_code << 8);
|
||||
}
|
||||
|
||||
/* Close process handle */
|
||||
CloseHandle(h);
|
||||
|
||||
return pid;
|
||||
}
|
||||
|
||||
/*
|
||||
* I/O Multiplexing
|
||||
*/
|
||||
|
||||
struct mrb_io_fdset {
|
||||
fd_set fds;
|
||||
};
|
||||
|
||||
mrb_io_fdset*
|
||||
mrb_hal_io_fdset_alloc(mrb_state *mrb)
|
||||
{
|
||||
mrb_io_fdset *fdset = (mrb_io_fdset*)mrb_malloc(mrb, sizeof(mrb_io_fdset));
|
||||
FD_ZERO(&fdset->fds);
|
||||
return fdset;
|
||||
}
|
||||
|
||||
void
|
||||
mrb_hal_io_fdset_free(mrb_state *mrb, mrb_io_fdset *fdset)
|
||||
{
|
||||
if (fdset) {
|
||||
mrb_free(mrb, fdset);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
mrb_hal_io_fdset_zero(mrb_state *mrb, mrb_io_fdset *fdset)
|
||||
{
|
||||
(void)mrb;
|
||||
if (fdset) {
|
||||
FD_ZERO(&fdset->fds);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
mrb_hal_io_fdset_set(mrb_state *mrb, int fd, mrb_io_fdset *fdset)
|
||||
{
|
||||
(void)mrb;
|
||||
if (fdset->fds.fd_count >= FD_SETSIZE) {
|
||||
mrb_raise(mrb, E_ARGUMENT_ERROR, "too many sockets for fd_set");
|
||||
return;
|
||||
}
|
||||
|
||||
if (fdset) {
|
||||
FD_SET(fd, &fdset->fds);
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_io_fdset_isset(mrb_state *mrb, int fd, mrb_io_fdset *fdset)
|
||||
{
|
||||
(void)mrb;
|
||||
if (fdset) {
|
||||
return FD_ISSET(fd, &fdset->fds);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
mrb_hal_io_select(mrb_state *mrb, int nfds,
|
||||
mrb_io_fdset *readfds,
|
||||
mrb_io_fdset *writefds,
|
||||
mrb_io_fdset *errorfds,
|
||||
mrb_io_timeval *timeout)
|
||||
{
|
||||
fd_set *r = readfds ? &readfds->fds : NULL;
|
||||
fd_set *w = writefds ? &writefds->fds : NULL;
|
||||
fd_set *e = errorfds ? &errorfds->fds : NULL;
|
||||
struct timeval *tv = NULL;
|
||||
struct timeval tv_storage;
|
||||
(void)mrb;
|
||||
(void)nfds; /* Windows select() doesn't use nfds */
|
||||
|
||||
if (timeout) {
|
||||
tv_storage.tv_sec = (long)timeout->tv_sec;
|
||||
tv_storage.tv_usec = (long)timeout->tv_usec;
|
||||
tv = &tv_storage;
|
||||
}
|
||||
|
||||
return select(0, r, w, e, tv);
|
||||
}
|
||||
|
||||
/*
|
||||
* HAL Initialization/Finalization
|
||||
*/
|
||||
|
||||
void
|
||||
mrb_hal_io_init(mrb_state *mrb)
|
||||
{
|
||||
(void)mrb;
|
||||
/* Initialize Winsock for select() support */
|
||||
WSADATA wsaData;
|
||||
WSAStartup(MAKEWORD(2, 2), &wsaData);
|
||||
}
|
||||
|
||||
void
|
||||
mrb_hal_io_final(mrb_state *mrb)
|
||||
{
|
||||
(void)mrb;
|
||||
/* Cleanup Winsock */
|
||||
WSACleanup();
|
||||
}
|
||||
|
||||
/*
|
||||
* Gem initialization
|
||||
*/
|
||||
|
||||
void
|
||||
mrb_hal_win_io_gem_init(mrb_state *mrb)
|
||||
{
|
||||
(void)mrb;
|
||||
/* HAL interface functions are called by mruby-io gem */
|
||||
}
|
||||
|
||||
void
|
||||
mrb_hal_win_io_gem_final(mrb_state *mrb)
|
||||
{
|
||||
(void)mrb;
|
||||
/* Cleanup handled by mrb_hal_io_final called from mruby-io */
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user