More Load instructions including LoadLocals, LoadFastBorrow (#6886)

* fix stdlib_io

* more scpell dict

* LoadLocals

* LoadFastBorrow

* more ops
This commit is contained in:
Jeong, YunWon authored and GitHub committed 2026-01-29 19:33:46 +09:00
1 parent e363b14e81
commit 95abec4c21
12 files changed
+430 -30

No files matched your search

+97
View File
@@ -1,3 +1,6 @@
ADDOP
aftersign
argdefs
argtypes
asdl
asname
@@ -5,83 +8,177 @@ attro
augassign
badcert
badsyntax
baseinfo
basetype
boolop
BUILDSTDLIB
bxor
byteswap
cached_tsver
cadata
cafile
calldepth
callinfo
callproc
capath
carg
cellarg
cellvar
cellvars
cfield
CLASSDEREF
classdict
cmpop
codedepth
CODEUNIT
CONVFUNC
convparam
copyslot
cpucount
defaultdict
denom
dictbytype
DICTFLAG
dictoffset
distpoint
dynload
elts
eofs
evalloop
excepthandler
exceptiontable
fblock
fblocks
fdescr
ffi_argtypes
fielddesc
fieldlist
fileutils
finalbody
flowgraph
formatfloat
freevar
freevars
fromlist
getdict
getfunc
getiter
getsets
getslice
globalgetvar
HASARRAY
HASBITFIELD
HASPOINTER
HASSTRUCT
HASUNION
heaptype
HIGHRES
IFUNC
IMMUTABLETYPE
INCREF
inlinedepth
inplace
ismine
ISPOINTER
iteminfo
Itertool
keeped
kwnames
kwonlyarg
kwonlyargs
lasti
libffi
linearise
lineiterator
linetable
loadfast
localsplus
Lshift
lsprof
MAXBLOCKS
maxdepth
metavars
miscompiles
mult
multibytecodec
nameop
nconsts
newargs
newfree
NEWLOCALS
newsemlockobject
nfrees
nkwargs
nkwelts
Nondescriptor
noninteger
nops
noraise
nseen
NSIGNALS
numer
opname
opnames
orelse
outparam
outparm
paramfunc
parg
pathconfig
patma
peepholer
phcount
platstdlib
posonlyarg
posonlyargs
prec
preinitialized
pybuilddir
pycore
pydecimal
Pyfunc
pymain
pyrepl
PYTHONTRACEMALLOC
pythonw
PYTHREAD_NAME
releasebuffer
repr
resinfo
Rshift
SA_ONSTACK
scls
setdict
setfunc
SETREF
setresult
setslice
SLOTDEFINED
SOABI
SSLEOF
stackdepth
staticbase
stginfo
storefast
stringlib
structseq
subkwargs
subparams
subscr
sval
swappedbytes
templatelib
ticketer
tmptype
tok_oldval
tvars
typeobject
typeparam
Typeparam
typeparams
typeslots
unaryop
Unhandle
unparse
unparser
VARKEYWORDS
+26
View File
@@ -272,3 +272,29 @@ xmlcharrefreplace
xoptions
xopts
yieldfrom
addcompare
altzone
classmethods
ctype
ctypes
genexpressions
getargs
getopt
getweakref
getweakrefs
inittab
Inittab
interpchannels
interpqueues
markupbase
mymodule
pydatetime
pyio
pymain
setprofileallthreads
settraceallthreads
sitebuiltins
subclassing
subpatterns
sysdict
weakrefset
+4
View File
@@ -28,6 +28,7 @@ hexf
hexversion
idents
illumos
ilog
indexmap
insta
keccak
@@ -86,4 +87,7 @@ wasmer
wasmtime
widestring
winapi
winresource
winsock
bitvec
Bitvec
+4
View File
@@ -69,10 +69,13 @@
"GetSet",
"groupref",
"internable",
"interps",
"jitted",
"jitting",
"lossily",
"makeunicodedata",
"microbenchmark",
"microbenchmarks",
"miri",
"notrace",
"oparg",
@@ -121,6 +124,7 @@
"uninit",
"unraisable",
"unresizable",
"varint",
"wasi",
"zelf",
// unix
-3
View File
@@ -1002,7 +1002,6 @@ class TestMarkingVariablesAsUnKnown(BytecodeTestCase):
self.assertInBytecode(f, 'LOAD_FAST_CHECK', "a73")
self.assertInBytecode(f, 'LOAD_FAST_BORROW', "a73")
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_setting_lineno_no_undefined(self):
code = textwrap.dedent("""\
def f():
@@ -1123,7 +1122,6 @@ class TestMarkingVariablesAsUnKnown(BytecodeTestCase):
self.assertNotInBytecode(f, "LOAD_FAST_CHECK")
return f
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_modifying_local_does_not_add_check(self):
f = self.make_function_with_no_checks()
def trace(frame, event, arg):
@@ -1137,7 +1135,6 @@ class TestMarkingVariablesAsUnKnown(BytecodeTestCase):
self.assertInBytecode(f, "LOAD_FAST_BORROW")
self.assertNotInBytecode(f, "LOAD_FAST_CHECK")
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_initializing_local_does_not_add_check(self):
f = self.make_function_with_no_checks()
def trace(frame, event, arg):
+7 -1
View File
@@ -2048,8 +2048,14 @@ impl Compiler {
let op = match usage {
NameUsage::Load => {
// Special case for class scope
// ClassBlock (not inlined comp): LOAD_LOCALS first, then LOAD_FROM_DICT_OR_DEREF
if self.ctx.in_class && !self.ctx.in_func() {
emit!(self, Instruction::LoadLocals);
Instruction::LoadFromDictOrDeref
// can_see_class_scope: LOAD_DEREF(__classdict__) first
} else if can_see_class_scope {
let classdict_idx = self.get_free_var_index("__classdict__")?;
self.emit_arg(classdict_idx, Instruction::LoadDeref);
Instruction::LoadFromDictOrDeref
} else {
Instruction::LoadDeref
+152
View File
@@ -189,6 +189,9 @@ impl CodeInfo {
self.peephole_optimize();
}
// Always apply LOAD_FAST_BORROW optimization
self.optimize_load_fast_borrow();
let max_stackdepth = self.max_stackdepth()?;
let cell2arg = self.cell2arg();
@@ -687,6 +690,155 @@ impl CodeInfo {
}
}
/// Optimize LOAD_FAST to LOAD_FAST_BORROW where safe.
///
/// A LOAD_FAST can be converted to LOAD_FAST_BORROW if its value is
/// consumed within the same basic block (not passed to another block).
/// This is a reference counting optimization in CPython; in RustPython
/// we implement it for bytecode compatibility.
fn optimize_load_fast_borrow(&mut self) {
use rustpython_compiler_core::bytecode::InstructionMetadata;
// NOT_LOCAL marker: instruction didn't come from a LOAD_FAST
const NOT_LOCAL: usize = usize::MAX;
for block in &mut self.blocks {
if block.instructions.is_empty() {
continue;
}
// Track which instructions' outputs are still on stack at block end
// For each instruction, we track if its pushed value(s) are unconsumed
let mut unconsumed = vec![false; block.instructions.len()];
// Simulate stack: each entry is the instruction index that pushed it
// (or NOT_LOCAL if not from LOAD_FAST/LOAD_FAST_LOAD_FAST).
//
// CPython (flowgraph.c optimize_load_fast) pre-fills the stack with
// dummy refs for values inherited from predecessor blocks. We take
// the simpler approach of aborting the optimisation for the whole
// block on stack underflow.
let mut stack: Vec<usize> = Vec::new();
let mut underflow = false;
for (i, info) in block.instructions.iter().enumerate() {
let Some(instr) = info.instr.real() else {
continue;
};
// Decompose into (pops, pushes).
//
// stack_effect() returns pushes − pops, which is ambiguous for
// instructions that both pop and push (e.g. BinaryOp: effect=-1
// is pop 2 push 1, not pop 1 push 0). We list those explicitly;
// the fallback under-pops and under-pushes, which is conservative
// (may miss optimisation opportunities but never miscompiles).
let effect = instr.stack_effect(info.arg);
let (pops, pushes) = match instr {
// --- pop 2, push 1 ---
Instruction::BinaryOp { .. }
| Instruction::BinaryOpInplaceAddUnicode
| Instruction::CompareOp { .. }
| Instruction::ContainsOp(_)
| Instruction::IsOp(_)
| Instruction::ImportName { .. }
| Instruction::FormatWithSpec => (2, 1),
// --- pop 1, push 1 ---
Instruction::UnaryInvert
| Instruction::UnaryNegative
| Instruction::UnaryNot
| Instruction::ToBool
| Instruction::GetIter
| Instruction::GetAIter
| Instruction::FormatSimple
| Instruction::LoadFromDictOrDeref(_)
| Instruction::LoadFromDictOrGlobals(_) => (1, 1),
// LoadAttr: pop receiver, push attr.
// method=true: push (method, self_or_null) → (1, 2)
Instruction::LoadAttr { idx } => {
let (_, is_method) =
rustpython_compiler_core::bytecode::decode_load_attr_arg(
idx.get(info.arg),
);
if is_method { (1, 2) } else { (1, 1) }
}
// --- pop 3, push 1 ---
Instruction::BinarySlice => (3, 1),
// --- variable pops, push 1 ---
Instruction::Call { nargs } => (nargs.get(info.arg) as usize + 2, 1),
Instruction::CallKw { nargs } => (nargs.get(info.arg) as usize + 3, 1),
// --- conservative fallback ---
// under-pops (≤ actual pops) and under-pushes (≤ actual pushes),
// which keeps extra refs on the stack → marks them unconsumed →
// prevents optimisation. Safe but may miss opportunities.
_ => {
let p = if effect < 0 { (-effect) as usize } else { 0 };
let q = if effect > 0 { effect as usize } else { 0 };
(p, q)
}
};
// Pop values from stack
for _ in 0..pops {
if stack.pop().is_none() {
// Stack underflow — block receives values from a predecessor.
// Abort optimisation for the entire block.
underflow = true;
break;
}
}
if underflow {
break;
}
// Push values to stack with source instruction index
let source = match instr {
Instruction::LoadFast(_) | Instruction::LoadFastLoadFast { .. } => i,
_ => NOT_LOCAL,
};
for _ in 0..pushes {
stack.push(source);
}
}
if underflow {
continue;
}
// Mark instructions whose values remain on stack at block end
for &src in &stack {
if src != NOT_LOCAL {
unconsumed[src] = true;
}
}
// Convert LOAD_FAST to LOAD_FAST_BORROW where value is fully consumed
for (i, info) in block.instructions.iter_mut().enumerate() {
if unconsumed[i] {
continue;
}
let Some(instr) = info.instr.real() else {
continue;
};
match instr {
Instruction::LoadFast(_) => {
info.instr = Instruction::LoadFastBorrow(Arg::marker()).into();
}
Instruction::LoadFastLoadFast { .. } => {
info.instr =
Instruction::LoadFastBorrowLoadFastBorrow { arg: Arg::marker() }.into();
}
_ => {}
}
}
}
}
fn max_stackdepth(&self) -> crate::InternalResult<u32> {
let mut maxdepth = 0u32;
let mut stack = Vec::with_capacity(self.blocks.len());
@@ -1,10 +1,11 @@
---
source: crates/codegen/src/compile.rs
assertion_line: 8782
expression: "compile_exec(\"\\\nasync def test():\n for stop_exc in (StopIteration('spam'), StopAsyncIteration('ham')):\n with self.subTest(type=type(stop_exc)):\n try:\n async with egg():\n raise stop_exc\n except Exception as ex:\n self.assertIs(ex, stop_exc)\n else:\n self.fail(f'{stop_exc} was suppressed')\n\")"
---
1 0 RESUME (0)
3 1 LOAD_CONST (<code object test at ??? file "source_path", line 1>): 1 0 RUSTPYTHON_PLACEHOLDER
3 1 LOAD_CONST (<code object test at ??? file "source_path", line 1>): 1 0 RETURN_GENERATOR
1 POP_TOP
2 RESUME (0)
@@ -25,7 +26,7 @@ expression: "compile_exec(\"\\\nasync def test():\n for stop_exc in (StopIter
16 LOAD_ATTR (7, subTest, method=true)
17 LOAD_GLOBAL (4, type)
18 PUSH_NULL
19 LOAD_FAST (0, stop_exc)
19 LOAD_FAST_BORROW (0, stop_exc)
20 CALL (1)
21 LOAD_CONST (("type"))
22 CALL_KW (1)
@@ -105,8 +106,8 @@ expression: "compile_exec(\"\\\nasync def test():\n for stop_exc in (StopIter
8 92 LOAD_GLOBAL (2, self)
93 LOAD_ATTR (15, assertIs, method=true)
94 LOAD_FAST (1, ex)
95 LOAD_FAST (0, stop_exc)
94 LOAD_FAST_BORROW (1, ex)
95 LOAD_FAST_BORROW (0, stop_exc)
96 CALL (2)
97 POP_TOP
98 JUMP_FORWARD (103)
@@ -126,7 +127,7 @@ expression: "compile_exec(\"\\\nasync def test():\n for stop_exc in (StopIter
10 >> 112 LOAD_GLOBAL (2, self)
113 LOAD_ATTR (17, fail, method=true)
114 LOAD_FAST (0, stop_exc)
114 LOAD_FAST_BORROW (0, stop_exc)
115 FORMAT_SIMPLE
116 LOAD_CONST (" was suppressed")
117 BUILD_STRING (2)
@@ -23,10 +23,10 @@ use crate::{
#[repr(u8)]
pub enum Instruction {
// No-argument instructions (opcode < HAVE_ARGUMENT=44)
Cache = 0, // Placeholder
Cache = 0,
BinarySlice = 1,
BuildTemplate = 2,
BinaryOpInplaceAddUnicode = 3, // Placeholder
BinaryOpInplaceAddUnicode = 3,
CallFunctionEx = 4,
CheckEgMatch = 5,
CheckExcMatch = 6,
@@ -45,13 +45,13 @@ pub enum Instruction {
GetYieldFromIter = 19,
InterpreterExit = 20, // Placeholder
LoadBuildClass = 21,
LoadLocals = 22, // Placeholder
LoadLocals = 22,
MakeFunction = 23,
MatchKeys = 24,
MatchMapping = 25,
MatchSequence = 26,
Nop = 27,
NotTaken = 28, // Placeholder
NotTaken = 28,
PopExcept = 29,
PopIter = 30,
PopTop = 31,
@@ -177,16 +177,16 @@ pub enum Instruction {
LoadDeref(Arg<NameIdx>) = 83,
LoadFast(Arg<NameIdx>) = 84,
LoadFastAndClear(Arg<NameIdx>) = 85,
LoadFastBorrow(Arg<NameIdx>) = 86, // Placeholder
LoadFastBorrow(Arg<NameIdx>) = 86,
LoadFastBorrowLoadFastBorrow {
arg: Arg<u32>,
} = 87, // Placeholder
} = 87,
LoadFastCheck(Arg<NameIdx>) = 88,
LoadFastLoadFast {
arg: Arg<u32>,
} = 89,
LoadFromDictOrDeref(Arg<NameIdx>) = 90,
LoadFromDictOrGlobals(Arg<NameIdx>) = 91, // Placeholder
LoadFromDictOrGlobals(Arg<NameIdx>) = 91,
LoadGlobal(Arg<NameIdx>) = 92,
LoadName(Arg<NameIdx>) = 93,
LoadSmallInt {
@@ -348,13 +348,13 @@ pub enum Instruction {
UnpackSequenceTuple = 210, // Placeholder
UnpackSequenceTwoTuple = 211, // Placeholder
// CPython 3.14 instrumented opcodes (234-254)
InstrumentedEndFor = 234, // Placeholder
InstrumentedPopIter = 235, // Placeholder
InstrumentedEndSend = 236, // Placeholder
InstrumentedForIter = 237, // Placeholder
InstrumentedInstruction = 238, // Placeholder
InstrumentedJumpForward = 239, // Placeholder
InstrumentedNotTaken = 240, // Placeholder
InstrumentedEndFor = 234, // Placeholder
InstrumentedPopIter = 235, // Placeholder
InstrumentedEndSend = 236, // Placeholder
InstrumentedForIter = 237, // Placeholder
InstrumentedInstruction = 238, // Placeholder
InstrumentedJumpForward = 239, // Placeholder
InstrumentedNotTaken = 240,
InstrumentedPopJumpIfTrue = 241, // Placeholder
InstrumentedPopJumpIfFalse = 242, // Placeholder
InstrumentedPopJumpIfNone = 243, // Placeholder
@@ -472,7 +472,7 @@ impl InstructionMetadata for Instruction {
Self::DeleteName(_) => 0,
Self::DeleteGlobal(_) => 0,
Self::DeleteDeref(_) => 0,
Self::LoadFromDictOrDeref(_) => 1,
Self::LoadFromDictOrDeref(_) => 0, // (dict -- value)
Self::StoreSubscr => -3,
Self::DeleteSubscr => -2,
Self::LoadAttr { idx } => {
@@ -861,6 +861,29 @@ impl InstructionMetadata for Instruction {
Self::LoadDeref(idx) => w!(LOAD_DEREF, cell_name = idx),
Self::LoadFast(idx) => w!(LOAD_FAST, varname = idx),
Self::LoadFastAndClear(idx) => w!(LOAD_FAST_AND_CLEAR, varname = idx),
Self::LoadFastBorrow(idx) => w!(LOAD_FAST_BORROW, varname = idx),
Self::LoadFastCheck(idx) => w!(LOAD_FAST_CHECK, varname = idx),
Self::LoadFastLoadFast { arg: packed } => {
let oparg = packed.get(arg);
let idx1 = oparg >> 4;
let idx2 = oparg & 15;
let name1 = varname(idx1);
let name2 = varname(idx2);
write!(f, "{:pad$}({}, {})", "LOAD_FAST_LOAD_FAST", name1, name2)
}
Self::LoadFastBorrowLoadFastBorrow { arg: packed } => {
let oparg = packed.get(arg);
let idx1 = oparg >> 4;
let idx2 = oparg & 15;
let name1 = varname(idx1);
let name2 = varname(idx2);
write!(
f,
"{:pad$}({}, {})",
"LOAD_FAST_BORROW_LOAD_FAST_BORROW", name1, name2
)
}
Self::LoadFromDictOrGlobals(idx) => w!(LOAD_FROM_DICT_OR_GLOBALS, name = idx),
Self::LoadGlobal(idx) => w!(LOAD_GLOBAL, name = idx),
Self::LoadName(idx) => w!(LOAD_NAME, name = idx),
Self::LoadSpecial { method } => w!(LOAD_SPECIAL, method),
@@ -893,6 +916,7 @@ impl InstructionMetadata for Instruction {
Self::Reraise { depth } => w!(RERAISE, depth),
Self::Resume { arg } => w!(RESUME, arg),
Self::ReturnValue => w!(RETURN_VALUE),
Self::ReturnGenerator => w!(RETURN_GENERATOR),
Self::Send { target } => w!(SEND, target),
Self::SetAdd { i } => w!(SET_ADD, i),
Self::SetFunctionAttribute { attr } => w!(SET_FUNCTION_ATTRIBUTE, ?attr),
+17 -1
View File
@@ -578,7 +578,7 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> {
self.stack.push(JitValue::Int(val));
Ok(())
}
Instruction::LoadFast(idx) => {
Instruction::LoadFast(idx) | Instruction::LoadFastBorrow(idx) => {
let local = self.variables[idx.get(arg) as usize]
.as_ref()
.ok_or(JitCompileError::BadBytecode)?;
@@ -588,6 +588,22 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> {
));
Ok(())
}
Instruction::LoadFastLoadFast { arg: packed }
| Instruction::LoadFastBorrowLoadFastBorrow { arg: packed } => {
let oparg = packed.get(arg);
let idx1 = oparg >> 4;
let idx2 = oparg & 0xF;
for idx in [idx1, idx2] {
let local = self.variables[idx as usize]
.as_ref()
.ok_or(JitCompileError::BadBytecode)?;
self.stack.push(JitValue::from_type_and_value(
local.ty.clone(),
self.builder.use_var(local.var),
));
}
Ok(())
}
Instruction::LoadGlobal(idx) => {
let name = &bytecode.names[idx.get(arg) as usize];
+74 -1
View File
@@ -618,6 +618,11 @@ impl ExecutingFrame<'_> {
match instruction {
Instruction::BinaryOp { op } => self.execute_bin_op(vm, op.get(arg)),
// TODO: In CPython, this does in-place unicode concatenation when
// refcount is 1. Falls back to regular iadd for now.
Instruction::BinaryOpInplaceAddUnicode => {
self.execute_bin_op(vm, bytecode::BinaryOperator::InplaceAdd)
}
Instruction::BinarySlice => {
// Stack: [container, start, stop] -> [result]
let stop = self.pop_value();
@@ -1176,13 +1181,24 @@ impl ExecutingFrame<'_> {
Ok(None)
}
Instruction::LoadFromDictOrDeref(i) => {
// Pop dict from stack (locals or classdict depending on context)
let class_dict = self.pop_value();
let i = i.get(arg) as usize;
let name = if i < self.code.cellvars.len() {
self.code.cellvars[i]
} else {
self.code.freevars[i - self.code.cellvars.len()]
};
let value = self.locals.mapping().subscript(name, vm).ok();
// Only treat KeyError as "not found", propagate other exceptions
let value = if let Some(dict_obj) = class_dict.downcast_ref::<PyDict>() {
dict_obj.get_item_opt(name, vm)?
} else {
match class_dict.get_item(name, vm) {
Ok(v) => Some(v),
Err(e) if e.fast_isinstance(vm.ctx.exceptions.key_error) => None,
Err(e) => return Err(e),
}
};
self.push_value(match value {
Some(v) => v,
None => self.cells_frees[i]
@@ -1322,6 +1338,57 @@ impl ExecutingFrame<'_> {
self.push_value(x2);
Ok(None)
}
// TODO: Implement true borrow optimization (skip Arc::clone).
// CPython's LOAD_FAST_BORROW uses PyStackRef_Borrow to avoid refcount
// increment for values that are consumed within the same basic block.
// Possible approaches:
// - Store raw pointers with careful lifetime management
// - Add a "borrowed" variant to stack slots
// - Use arena allocation for short-lived stack values
// Currently this just clones like LoadFast.
Instruction::LoadFastBorrow(idx) => {
let idx = idx.get(arg) as usize;
let x = self.fastlocals.lock()[idx].clone().ok_or_else(|| {
vm.new_exception_msg(
vm.ctx.exceptions.unbound_local_error.to_owned(),
format!(
"local variable '{}' referenced before assignment",
self.code.varnames[idx]
),
)
})?;
self.push_value(x);
Ok(None)
}
// TODO: Same as LoadFastBorrow - implement true borrow optimization.
Instruction::LoadFastBorrowLoadFastBorrow { arg: packed } => {
let oparg = packed.get(arg);
let idx1 = (oparg >> 4) as usize;
let idx2 = (oparg & 15) as usize;
let fastlocals = self.fastlocals.lock();
let x1 = fastlocals[idx1].clone().ok_or_else(|| {
vm.new_exception_msg(
vm.ctx.exceptions.unbound_local_error.to_owned(),
format!(
"local variable '{}' referenced before assignment",
self.code.varnames[idx1]
),
)
})?;
let x2 = fastlocals[idx2].clone().ok_or_else(|| {
vm.new_exception_msg(
vm.ctx.exceptions.unbound_local_error.to_owned(),
format!(
"local variable '{}' referenced before assignment",
self.code.varnames[idx2]
),
)
})?;
drop(fastlocals);
self.push_value(x1);
self.push_value(x2);
Ok(None)
}
Instruction::LoadGlobal(idx) => {
let name = &self.code.names[idx.get(arg) as usize];
let x = self.load_global_or_builtin(name, vm)?;
@@ -1600,6 +1667,12 @@ impl ExecutingFrame<'_> {
Ok(None)
}
Instruction::Nop => Ok(None),
// NOT_TAKEN is a branch prediction hint - functionally a NOP
Instruction::NotTaken => Ok(None),
// Instrumented version of NOT_TAKEN - NOP without monitoring
Instruction::InstrumentedNotTaken => Ok(None),
// CACHE is used by adaptive interpreter for inline caching - NOP for us
Instruction::Cache => Ok(None),
Instruction::ReturnGenerator => {
// In RustPython, generators/coroutines are created in function.rs
// before the frame starts executing. The RETURN_GENERATOR instruction
+4 -4
View File
@@ -10,15 +10,15 @@ assert bb.seekable()
result = bb.read()
assert len(result) <= 8 * 1024
assert len(result) <= 16 * 1024
assert len(result) >= 0
assert isinstance(result, bytes)
with FileIO("README.md") as fio:
res = fio.read()
assert len(result) <= 8 * 1024
assert len(result) >= 0
assert isinstance(result, bytes)
assert len(res) <= 16 * 1024
assert len(res) >= 0
assert isinstance(res, bytes)
fd = os.open("README.md", os.O_RDONLY)